🚀 Init public commit
3
.cargo/config.toml
Normal file
|
@ -0,0 +1,3 @@
|
|||
[build]
|
||||
target-dir = "target/cargo"
|
||||
rustflags = ["-A", "unused_imports"]
|
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target/
|
5528
Cargo.lock
generated
Normal file
78
Cargo.toml
Normal file
|
@ -0,0 +1,78 @@
|
|||
[workspace]
|
||||
members = ["crates/*"]
|
||||
resolver = "3"
|
||||
edition = "2024"
|
||||
|
||||
[workspace.package]
|
||||
version = "0.1.0"
|
||||
edition = "2024"
|
||||
name = "mascully_website"
|
||||
license = "MIT"
|
||||
|
||||
[workspace.metadata.crane]
|
||||
name = "mascully_website"
|
||||
|
||||
[[workspace.metadata.leptos]]
|
||||
name = "mascully_website"
|
||||
bin-package = "mascully_website_server"
|
||||
lib-package = "mascully_website_frontend"
|
||||
assets-dir = "crates/frontend/assets"
|
||||
style-file = "crates/frontend/assets/css/styles.css"
|
||||
site-root = "target/site/mascully_website"
|
||||
server-fn-prefix = "/custom/prefix"
|
||||
# disable-server-fn-hash = true
|
||||
# server-fn-mod-path = true
|
||||
site-addr = "127.0.0.1:3002"
|
||||
# hash-files = true
|
||||
# disable-erase-components = true
|
||||
|
||||
[workspace.dependencies]
|
||||
async-trait = "0.1.74"
|
||||
axum = "0.8.4"
|
||||
comrak = "0.29.0"
|
||||
console_error_panic_hook = { version = "0.1.7" }
|
||||
console_log = { version = "1.0" }
|
||||
dotenvy = { version = "0.15.6" }
|
||||
encase = { version = "0.11.1", features = ["glam"] }
|
||||
futures = { version = "0.3.31" }
|
||||
glam = { version = "0.30.0" }
|
||||
gloo = { version = "0.11.0", features = ["timers"] }
|
||||
gloo-net = { version = "0.6", features = ["http"] }
|
||||
js-sys = "0.3.77"
|
||||
leptos = { version = "0.8.4", features = ["nightly"] }
|
||||
leptos_axum = "0.8.4"
|
||||
leptos_dom = { version = "0.8.4" }
|
||||
leptos_meta = { version = "0.8.4" }
|
||||
leptos_router = { version = "0.8.4" }
|
||||
log = "0.4.17"
|
||||
mime_guess = "2.0.4"
|
||||
reqwest = { version = "0.12.8", features = [
|
||||
"json",
|
||||
"rustls-tls",
|
||||
], default-features = false }
|
||||
serde = { version = "1", features = ["derive"] }
|
||||
serde_json = { version = "1.0.128" }
|
||||
simple_logger = { version = "5.0.0" }
|
||||
tokio = { version = "1.33.0", features = ["full"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.17", features = ["env-filter", "json"] }
|
||||
wasm-bindgen = { version = "=0.2.100" }
|
||||
wasm-bindgen-futures = "0.4.50"
|
||||
web-sys = { version = "0.3.77", features = [
|
||||
"HtmlCanvasElement",
|
||||
"Window",
|
||||
"Document",
|
||||
"Element",
|
||||
"DomRect",
|
||||
"Performance",
|
||||
"WebGl2RenderingContext",
|
||||
"WebGlBuffer",
|
||||
"WebGlVertexArrayObject",
|
||||
"WebGlProgram",
|
||||
"WebGlShader",
|
||||
"WebGlUniformLocation",
|
||||
] }
|
||||
wgpu = { version = "26.0.1", features = ["webgl"] }
|
||||
tower = { version = "0.5.2", features = ["full"] }
|
||||
tower-http = { version = "0.6.6", features = ["full"] }
|
||||
indoc = "2.0.6"
|
21
LICENSE
Normal file
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2025 Markus Scully
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
39
README.md
Normal file
|
@ -0,0 +1,39 @@
|
|||
# Mascully Website
|
||||
|
||||
My personal website. Hosted at [mascully.com](https://mascully.com). The currently hosted version may not always exactly reflect the contents of this repo.
|
||||
|
||||
Built using [Leptos](https://leptos.dev/) with [Axum](https://github.com/tokio-rs/axum).
|
||||
|
||||
## Running
|
||||
|
||||
### Nix
|
||||
|
||||
Run directly with
|
||||
```bash
|
||||
nix develop -c cargo-leptos serve
|
||||
```
|
||||
|
||||
Develop with
|
||||
```bash
|
||||
nix develop
|
||||
cargo-leptos watch
|
||||
```
|
||||
|
||||
I'm still working on getting the flake working, so for now you can't use `nix run`. I also haven't figured out how to bundle it as a standalone binary yet.
|
||||
|
||||
### Other systems
|
||||
|
||||
- Install Rust nightly.
|
||||
- Install [Cargo Leptos](https://github.com/leptos-rs/cargo-leptos).
|
||||
- `cargo-leptos serve`
|
||||
|
||||
## Code structure
|
||||
|
||||
- `crates/app`: The actual website.
|
||||
- `crates/frontend`: Images, fonts, CSS.
|
||||
- `crates/server`: A minimal Axum server for serving pages.
|
||||
- `flake.nix`: This doesn't work; ignore it.
|
||||
|
||||
## License
|
||||
|
||||
MIT
|
26
crates/app/Cargo.toml
Normal file
|
@ -0,0 +1,26 @@
|
|||
[package]
|
||||
name = "mascully_website_app"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
[features]
|
||||
default = ["hydrate"]
|
||||
hydrate = ["leptos/hydrate"]
|
||||
ssr = ["leptos/ssr"]
|
||||
|
||||
[dependencies]
|
||||
leptos.workspace = true
|
||||
leptos_meta.workspace = true
|
||||
leptos_router.workspace = true
|
||||
leptos_axum = { workspace = true, optional = true }
|
||||
wgpu.workspace = true
|
||||
web-sys.workspace = true
|
||||
encase.workspace = true
|
||||
glam.workspace = true
|
||||
log.workspace = true
|
||||
gloo.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
js-sys.workspace = true
|
||||
wasm-bindgen-futures.workspace = true
|
||||
serde.workspace = true
|
||||
indoc.workspace = true
|
78
crates/app/src/article.rs
Normal file
|
@ -0,0 +1,78 @@
|
|||
use leptos::prelude::*;
|
||||
use leptos_router::{components::A, hooks::use_params_map};
|
||||
|
||||
use crate::wgpu_renderer::WgpuTriangle;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct ArticleMetadata {
|
||||
pub title: &'static str,
|
||||
pub slug: &'static str,
|
||||
pub description: &'static str,
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ArticlePage() -> impl IntoView {
|
||||
let params = use_params_map();
|
||||
// let slug = move || params.with(|params| params.get("slug").unwrap_or_default());
|
||||
|
||||
// let article_content = move || match slug().as_str() {
|
||||
// "creating-an-actually-perfect-grid-shader" => view! {
|
||||
// <div class="article grid-shader-article">
|
||||
// <h1>"Creating an actually perfect grid shader"</h1>
|
||||
// <div class="article-metadata">
|
||||
// <p class="date">"March 9, 2024"</p>
|
||||
// </div>
|
||||
|
||||
// <p>"Grid shaders are essential for many visualization and design applications, but creating the perfect grid involves some nuanced considerations."</p>
|
||||
|
||||
// <h2>"Basic Triangle Rendering"</h2>
|
||||
// <p>"Before we get to grids, let's start with a basic triangle rendering implementation using WebGL:"</p>
|
||||
|
||||
// <div class="visualization">
|
||||
// <WgpuTriangle />
|
||||
// </div>
|
||||
|
||||
// <h2>"Building the Grid Shader"</h2>
|
||||
// <p>"The key to a perfect grid shader lies in the precision of the grid lines and the handling of coordinate spaces."</p>
|
||||
|
||||
// <h2>"Avoiding Common Pitfalls"</h2>
|
||||
// <p>"Many grid implementations suffer from aliasing issues or precision problems at certain scales."</p>
|
||||
|
||||
// <h2>"Conclusion"</h2>
|
||||
// <p>"With the right approach, you can create grid shaders that look perfect at any scale."</p>
|
||||
// </div>
|
||||
// },
|
||||
// "minimal-example-article" => view! {
|
||||
// <div class="article minimal-article">
|
||||
// <h1>"Minimal Example Article"</h1>
|
||||
// <div class="article-metadata">
|
||||
// <p class="date">"March 9, 2024"</p>
|
||||
// </div>
|
||||
|
||||
// <p>"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nullam auctor, nisl eget ultricies tincidunt, nisl nisl aliquam nisl, eget ultricies nisl nisl eget nisl."</p>
|
||||
|
||||
// <h2>"Section One"</h2>
|
||||
// <p>"Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua."</p>
|
||||
|
||||
// <h2>"Section Two"</h2>
|
||||
// <p>"Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat."</p>
|
||||
// </div>
|
||||
// },
|
||||
// _ => view! {
|
||||
// <div class="article-not-found">
|
||||
// <h1>"Article Not Found"</h1>
|
||||
// <p>"The article you're looking for doesn't exist."</p>
|
||||
// <A href="/">"Return to Home"</A>
|
||||
// </div>
|
||||
// },
|
||||
// };
|
||||
|
||||
view! {
|
||||
<div class="article-page">
|
||||
<nav class="article-nav">
|
||||
<A href="/">"← Back to Home"</A>
|
||||
</nav>
|
||||
// {article_content}
|
||||
</div>
|
||||
}
|
||||
}
|
6
crates/app/src/command_line.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn CommandLine() -> impl IntoView {
|
||||
view! { <input class="terminal-input" type="text" placeholder="$ " /> }
|
||||
}
|
30
crates/app/src/contact.rs
Normal file
|
@ -0,0 +1,30 @@
|
|||
#![allow(unused)]
|
||||
#![warn(unused_must_use)]
|
||||
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn Contact() -> impl IntoView {
|
||||
view! {
|
||||
<section>
|
||||
<h1>"Contact"</h1>
|
||||
|
||||
<section>
|
||||
<span>"Email: "</span>
|
||||
<a href="mailto:contact@mascully.com">"contact@mascully.com"</a>
|
||||
</section>
|
||||
<section>
|
||||
<span>"GitHub: "</span>
|
||||
<a href="https://github.com/mascully">"mascully"</a>
|
||||
</section>
|
||||
<section>
|
||||
<span>"Matrix: "</span>
|
||||
<a href="matrix:u/blue_flycatcher:matrix.org">"@blue_flycatcher:matrix.org"</a>
|
||||
</section>
|
||||
</section>
|
||||
<section>
|
||||
<span>"PGP:"</span>
|
||||
<pre>{include_str!("security/pgp_master.txt")}</pre>
|
||||
</section>
|
||||
}
|
||||
}
|
154
crates/app/src/lib.rs
Normal file
|
@ -0,0 +1,154 @@
|
|||
#![allow(unused)]
|
||||
#![warn(unused_must_use)]
|
||||
|
||||
use leptos::hydration::{AutoReload, HydrationScripts};
|
||||
use leptos::prelude::*;
|
||||
use leptos_meta::{Link, MetaTags, Stylesheet, Title, provide_meta_context};
|
||||
use leptos_router::components::{A, FlatRoutes, Route, Router, Routes, RoutingProgress};
|
||||
use leptos_router::{ParamSegment, StaticSegment, path};
|
||||
use std::time::Duration;
|
||||
|
||||
use contact::Contact;
|
||||
use posts::{Posts, plot_demo::PlotDemo};
|
||||
use projects::Projects;
|
||||
use theme_switcher::SiteHeader;
|
||||
|
||||
mod command_line;
|
||||
mod contact;
|
||||
mod plot;
|
||||
mod posts;
|
||||
mod projects;
|
||||
mod sigil;
|
||||
mod theme_switcher;
|
||||
mod version;
|
||||
|
||||
pub fn shell(options: LeptosOptions) -> impl IntoView {
|
||||
view! {
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
|
||||
<AutoReload options=options.clone() />
|
||||
<HydrationScripts options />
|
||||
<MetaTags />
|
||||
</head>
|
||||
<body>
|
||||
<App />
|
||||
</body>
|
||||
</html>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn App() -> impl IntoView {
|
||||
provide_meta_context();
|
||||
|
||||
view! {
|
||||
<Stylesheet href="/css/styles.css" />
|
||||
<Link rel="icon" href="/icon_2.png" />
|
||||
<Link
|
||||
rel="preload"
|
||||
href="/fonts/space_grotesk_variable.woff2"
|
||||
as_="font"
|
||||
type_="font/woff2"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<Link
|
||||
rel="preload"
|
||||
href="/fonts/ibm_plex_sans_variable.ttf"
|
||||
as_="font"
|
||||
type_="font/ttf"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<Link
|
||||
rel="preload"
|
||||
href="/fonts/zed_mono_variable.woff2"
|
||||
as_="font"
|
||||
type_="font/woff2"
|
||||
crossorigin="anonymous"
|
||||
/>
|
||||
<Title text="mascully.com" />
|
||||
<Root>
|
||||
<SiteHeader />
|
||||
<div class="content-wrapper">
|
||||
<div class="main-content">
|
||||
<Router>
|
||||
<aside class="sidebar">
|
||||
<nav class="main-nav">
|
||||
<ul>
|
||||
<li>
|
||||
<A href="/">"Home"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/posts">"Posts"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/projects">"Projects"</A>
|
||||
</li>
|
||||
<li>
|
||||
<hr />
|
||||
</li>
|
||||
<li>
|
||||
<a href="https://git.mascully.com/mascully?tab=repositories">
|
||||
"Git"
|
||||
</a>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/contact">"Contact"</A>
|
||||
</li>
|
||||
<li>
|
||||
<A href="/hire_me">"Hire me"</A>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
</aside>
|
||||
<main>
|
||||
<Routes fallback=NotFound>
|
||||
<Route path=path!("/") view=HomePage />
|
||||
<Route path=path!("/posts") view=Posts />
|
||||
<Route path=path!("/posts/plot_demo") view=PlotDemo />
|
||||
<Route path=path!("/projects") view=Projects />
|
||||
<Route path=path!("/contact") view=Contact />
|
||||
<Route path=path!("/hire_me") view=HireMe />
|
||||
</Routes>
|
||||
</main>
|
||||
</Router>
|
||||
</div>
|
||||
</div>
|
||||
</Root>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn Root(children: Children) -> impl IntoView {
|
||||
view! { <div id="root">{children()}</div> }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn NotFound() -> impl IntoView {
|
||||
view! { <h1>404</h1> }
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn HomePage() -> impl IntoView {
|
||||
view! {
|
||||
<p>"Hi. I'm Markus. I mostly do full stack development with Rust and Nix."</p>
|
||||
<p>
|
||||
"You can check out some of my projects "<A href="/projects">"here"</A>" or on my "<A href="https://git.mascully.com/mascully?tab=repositories">"Git forge"</A>", including the "
|
||||
<A href="https://git.mascully.com/mascully/mascully_website">
|
||||
"source code for this website"
|
||||
</A>"."
|
||||
</p>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
fn HireMe() -> impl IntoView {
|
||||
view! {
|
||||
<p>"I am available for freelancing work (remote, European time zone)."</p>
|
||||
<p>"I can work comfortably with most tech stacks in most domains."</p>
|
||||
<p>"Email me at "<a href="mailto:contact@mascully.com">"contact@mascully.com"</a>.</p>
|
||||
}
|
||||
}
|
43
crates/app/src/plot.rs
Normal file
|
@ -0,0 +1,43 @@
|
|||
#![allow(unused)]
|
||||
#![warn(unused_must_use)]
|
||||
|
||||
use leptos::prelude::*;
|
||||
use wasm_bindgen::prelude::*;
|
||||
use web_sys::HtmlCanvasElement;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Camera {
|
||||
// TODO: Add camera fields (position, zoom, etc.)
|
||||
}
|
||||
|
||||
impl Default for Camera {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
// TODO: Initialize default camera values
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Element {
|
||||
// TODO: Add element fields (position, shape, color, etc.)
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Plot(camera: Camera, elements: Vec<Element>) -> impl IntoView {
|
||||
let canvas_ref = NodeRef::<leptos::html::Canvas>::new();
|
||||
|
||||
// TODO: Implement rendering logic
|
||||
// - Set up canvas context
|
||||
// - Handle resize events
|
||||
// - Render elements based on camera view
|
||||
// - Handle user interactions (pan, zoom, etc.)
|
||||
|
||||
view! {
|
||||
<div class="plot-container">
|
||||
<canvas node_ref=canvas_ref class="plot-canvas" width="800" height="600">
|
||||
"Your browser does not support the HTML5 Canvas element."
|
||||
</canvas>
|
||||
</div>
|
||||
}
|
||||
}
|
53
crates/app/src/posts.rs
Normal file
|
@ -0,0 +1,53 @@
|
|||
#![allow(unused)]
|
||||
#![warn(unused_must_use)]
|
||||
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::components::A;
|
||||
|
||||
pub mod plot_demo;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct PostMeta {
|
||||
pub slug: &'static str,
|
||||
pub title: &'static str,
|
||||
pub description: &'static str,
|
||||
}
|
||||
|
||||
fn get_all_posts() -> Vec<PostMeta> {
|
||||
vec![
|
||||
// PostMeta {
|
||||
// slug: "plot_demo",
|
||||
// title: "Interactive Plot Demo",
|
||||
// description: "A demonstration of the Plot component with interactive graphics rendering.",
|
||||
// },
|
||||
]
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn Posts() -> impl IntoView {
|
||||
let posts = get_all_posts();
|
||||
|
||||
view! {
|
||||
<section>
|
||||
<header>
|
||||
<h1>"Posts"</h1>
|
||||
</header>
|
||||
"I haven't published anything yet. Try again later."
|
||||
<section class="posts-list">
|
||||
{posts
|
||||
.into_iter()
|
||||
.map(|post| {
|
||||
view! {
|
||||
<article class="post-preview">
|
||||
<h2>
|
||||
<A href=format!("/posts/{}", post.slug)>{post.title}</A>
|
||||
</h2>
|
||||
<p class="post-description">{post.description}</p>
|
||||
</article>
|
||||
}
|
||||
})
|
||||
.collect::<Vec<_>>()}
|
||||
</section>
|
||||
</section>
|
||||
}
|
||||
}
|
37
crates/app/src/posts/plot_demo.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
#![allow(unused)]
|
||||
#![warn(unused_must_use)]
|
||||
|
||||
use crate::plot::{Camera, Element, Plot};
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[component]
|
||||
pub fn PlotDemo() -> impl IntoView {
|
||||
let camera = Camera::default();
|
||||
|
||||
// TODO: Create sample elements for the plot
|
||||
let elements = vec![
|
||||
Element {
|
||||
// TODO: Initialize with sample data
|
||||
},
|
||||
Element {
|
||||
// TODO: Initialize with sample data
|
||||
},
|
||||
];
|
||||
|
||||
view! {
|
||||
<article class="plot-demo-post">
|
||||
<header>
|
||||
<h1>"Interactive Plot Demo"</h1>
|
||||
<p class="post-description">
|
||||
"A demonstration of the Plot component with interactive graphics rendering."
|
||||
</p>
|
||||
</header>
|
||||
<section class="plot-section">
|
||||
<h2>"Sample Plot"</h2>
|
||||
<p>"This plot component will render interactive graphics:"</p>
|
||||
<Plot camera=camera elements=elements />
|
||||
<p>"TODO: Implement rendering logic, camera controls, and element interactions."</p>
|
||||
</section>
|
||||
</article>
|
||||
}
|
||||
}
|
122
crates/app/src/projects.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
#![allow(unused)]
|
||||
#![warn(unused_must_use)]
|
||||
|
||||
use indoc::indoc;
|
||||
use leptos::prelude::*;
|
||||
use leptos_router::components::A;
|
||||
|
||||
#[component]
|
||||
pub fn Projects() -> impl IntoView {
|
||||
view! {
|
||||
<h1>"Personal projects"</h1>
|
||||
<section class="projects-list">
|
||||
<ProjectSummary image="/parva_1.png".into()>
|
||||
<ProjectSummaryTitle slot>"LittleBigPlanet Computer"</ProjectSummaryTitle>
|
||||
<ProjectSummaryMain slot>
|
||||
<p>
|
||||
"A 120Hz 24-bit computer built using logic gates inside the game "
|
||||
<A href="https://en.wikipedia.org/wiki/LittleBigPlanet_3">
|
||||
"LittleBigPlanet 3"
|
||||
</A>" that I call \"Parva\"."
|
||||
</p>
|
||||
<p>
|
||||
"There's been several iterations of it. Currently I'm trying to expand the memory from ~2kiB to 24kiB using "
|
||||
<A href="https://www.youtube.com/watch?v=jzAx65my6N0">"this"</A>
|
||||
" technique but I'm running into problems with non-determinism in how the game simulates the logic."
|
||||
</p>
|
||||
<p>
|
||||
"In the process of building it, some other members of the LittleBigPlanet logic community and I did a lot of research into the game's mechanics. Some of the findings are documented "
|
||||
<A href="https://github.com/Nomkid/littlebiglogic/blob/master/wiki/README.md">
|
||||
"here"
|
||||
</A>"."
|
||||
</p>
|
||||
<p>
|
||||
"I will write up a blogpost about Parva and the other computers people have built in the game along with instructions on how to use them at some point."
|
||||
</p>
|
||||
</ProjectSummaryMain>
|
||||
<ProjectSummaryLinks slot>
|
||||
<a href="https://wiki.littlebigcomputer.com">"Wiki"</a>
|
||||
<a href="https://littlebigcomputer.com">"Assembler"</a>
|
||||
<a href="https://git.mascully.com/mascully/littlebigcomputer/src/branch/master/programs/parva_0.1">
|
||||
"Example assembly programs"
|
||||
</a>
|
||||
</ProjectSummaryLinks>
|
||||
</ProjectSummary>
|
||||
<ProjectSummary image="/carplexity_1.png".into()>
|
||||
<ProjectSummaryTitle slot>"Carplexity"</ProjectSummaryTitle>
|
||||
<ProjectSummaryMain slot>
|
||||
<p>
|
||||
"Very early in development. A physics car game built using Bevy meant to be similar to Rocket League."
|
||||
</p>
|
||||
<p>
|
||||
"360 ticks per second instead of 120, so less input lag, which is basically the only thing that matters."
|
||||
</p>
|
||||
</ProjectSummaryMain>
|
||||
<ProjectSummaryLinks slot>
|
||||
<A href="https://carplexity.com">"Website"</A>
|
||||
</ProjectSummaryLinks>
|
||||
</ProjectSummary>
|
||||
<ProjectSummary image="/endolingual_1.png".into()>
|
||||
<ProjectSummaryTitle slot>"Endolingual"</ProjectSummaryTitle>
|
||||
<ProjectSummaryMain slot>
|
||||
<p>"Compile-time string localization for Rust."</p>
|
||||
// <pre>
|
||||
// {
|
||||
// indoc! {
|
||||
// r#"
|
||||
// let current_language = get_current_language();
|
||||
// let my_string = translate!("Enter your password: ");
|
||||
// println!("{}", my_string[current_language]);
|
||||
// "#
|
||||
// }
|
||||
// }
|
||||
// </pre>
|
||||
// <p>
|
||||
// "You can try this out by clicking the translate button at the top right of this website. All of the translation"
|
||||
// </p>
|
||||
</ProjectSummaryMain>
|
||||
<ProjectSummaryLinks slot>
|
||||
<A href="https://github.com/mascully/endolingual">"Repo"</A>
|
||||
</ProjectSummaryLinks>
|
||||
</ProjectSummary>
|
||||
</section>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ProjectSummary(
|
||||
project_summary_title: ProjectSummaryTitle,
|
||||
project_summary_main: ProjectSummaryMain,
|
||||
project_summary_links: ProjectSummaryLinks,
|
||||
image: String,
|
||||
) -> impl IntoView {
|
||||
view! {
|
||||
<div class="project-grid">
|
||||
<h2 class="project-title">{(project_summary_title.children)()}</h2>
|
||||
<picture class="project-image">
|
||||
<img
|
||||
src=image
|
||||
// srcset={image.concat(" x1")}
|
||||
loading="lazy"
|
||||
/>
|
||||
</picture>
|
||||
<div class="project-text">{(project_summary_main.children)()}</div>
|
||||
<div class="project-links">{(project_summary_links.children)()}</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct ProjectSummaryTitle {
|
||||
children: ChildrenFn,
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct ProjectSummaryLinks {
|
||||
children: ChildrenFn,
|
||||
}
|
||||
|
||||
#[slot]
|
||||
pub struct ProjectSummaryMain {
|
||||
children: ChildrenFn,
|
||||
}
|
0
crates/app/src/security/bug_bounties.rs
Normal file
31
crates/app/src/security/pgp_master.txt
Normal file
|
@ -0,0 +1,31 @@
|
|||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
mJMEaHK66RMJKyQDAwIIAQENBAMEU30JORhgrFANgMzvqeaX/T1ZfA9FjLPVsJQx
|
||||
MTrQ0m6oCBjqi4vSnMmB+AO9BhEGMnaJmrx1xf96eWNWv6OEBAwED6nxB+zxV1O2
|
||||
ixh+LxMdJCXJfDDTb/DDNkna0nTfNRdjduqNYa5TfXQ56Jz4/srHwJQJg723nrXO
|
||||
zeEc4wC0Hm1hc2N1bGx5IDxtYXJrdXNAbWFzY3VsbHkuY29tPojTBBMTCgA7FiEE
|
||||
dGuDuzFFGDwlt8+KXne6BGBk3ZAFAmhyuukCGwMFCwkIBwICIgIGFQoJCAsCBBYC
|
||||
AwECHgcCF4AACgkQXne6BGBk3ZBeMwIAjrmfwrh/3A0fFAKL8Ov/VzBdh9ErpLI8
|
||||
MrD2GiFhGUNDZfSQ6pJsu/k9US0YM7HygNBVSs+VCiZIs91XIVcuewH/XOI2yKIG
|
||||
y/e5r7G9cYm1i5C7tPfux4cI83gd14b368KlrxsrgyVh3RwC6mogB4FO+S0q9mMe
|
||||
qzTlBXC/8Xxov7iXBGhyuukSCSskAwMCCAEBDQQDBJa9IbHmgiKRbm8r6JCVOCoi
|
||||
4ZRI4ms90IaI0wctMlhMntnbJFWgW+/SoHuV9fBfIYbuN3L77d6wYA0xhi59UT85
|
||||
qfTia06Kd6q6KJvHjCLN2nFYHECFU0Qz4Q8U4q+WD2nStYylI8BZixEih7dYzyjh
|
||||
g7GmA+d7dNCBqiR6aiViAwEKCYi4BBgTCgAgFiEEdGuDuzFFGDwlt8+KXne6BGBk
|
||||
3ZAFAmhyuukCGwwACgkQXne6BGBk3ZCk2AH6A/w7IvHl4qRZJm+8rjMAgRUf9YyT
|
||||
XZO2ndkzOHFXsfWIRi/FKd7qP5MXokBRAfwe07zFy4f6rrhhb6XPCKPARAH+OAon
|
||||
wgnE5CWj8wvUgZ0vjImAp4PlLvF1hhMN3Eb4ptT5ytfTo8Cssgu+Et6YpKoQh2uz
|
||||
yTjBNEAgyvGT/ORQaLiTBGiEg5UTCSskAwMCCAEBDQQDBBSg23AZV79tF5upAgT5
|
||||
pLoAXPkMLU5daER2JUElNX+bzSnEoDp6OyMgVawYsXePaCa7k9PUm6PPQU9L2bpi
|
||||
NxwoUKhISZ+BID87trh2dRMSjta4JU+4SbR8uRz4OlJShjupb2M1MycFLHJ5Z4QS
|
||||
0tz7DW5OUEZ/XuvpIl9sPy44iQF1BBgTCgAmFiEEdGuDuzFFGDwlt8+KXne6BGBk
|
||||
3ZAFAmiEg5UCGwIFCQGYxmoAwQkQXne6BGBk3ZC2IAQZEwoAHRYhBEytVj5gfxiX
|
||||
tnSKV5PKWBS2mBAcBQJohIOVAAoJEJPKWBS2mBActqQCAJSLMh4z9JgpY++JIQfg
|
||||
pz7NRXOYNWKmGco6OD/msZps4ZoW9AyI8TMGwtioq0cPV7dztsm07JLQteezhIPd
|
||||
fjAB/2CUnzTmVXzIJmP4LflwaASkVaDx6B5ZmUo+hrgDJ2ka/fSkUUowUboQOTvV
|
||||
i8q5QxFYMmrG3GHTuLgUYkEPGceyMQIAmCz5SfRmEmKxHQTPhymIgbKT073vkmtI
|
||||
uy3F+/YolAjKangwm5EPf+huOnddxUEbqlSoxoMM2Zs1rSMubsvnKQH/c+VBdyGR
|
||||
AtTRFhprNN9qb6F9SRX5F/lOgCuReDRAKAW+FqFWQs1wgBDYm/Y/SA4h4OKzMt09
|
||||
lUHKVwcI4MRRHw==
|
||||
=fxmK
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
5
crates/app/src/sigil.rs
Normal file
|
@ -0,0 +1,5 @@
|
|||
use leptos::{prelude::*, svg::Svg};
|
||||
|
||||
pub fn website_icon() -> impl IntoView {
|
||||
view! { "test" }
|
||||
}
|
175
crates/app/src/theme_switcher.rs
Normal file
|
@ -0,0 +1,175 @@
|
|||
#![allow(unused)]
|
||||
#![warn(unused_must_use)]
|
||||
|
||||
use leptos::html::Input;
|
||||
use leptos::prelude::*;
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
use wasm_bindgen::prelude::*;
|
||||
#[cfg(feature = "hydrate")]
|
||||
use web_sys::{Storage, window};
|
||||
|
||||
use crate::command_line::CommandLine;
|
||||
|
||||
#[derive(Clone, Copy, PartialEq)]
|
||||
pub enum Theme {
|
||||
Light,
|
||||
Dark,
|
||||
Auto,
|
||||
}
|
||||
|
||||
impl Theme {
|
||||
fn as_str(&self) -> &'static str {
|
||||
match self {
|
||||
Theme::Light => "light",
|
||||
Theme::Dark => "dark",
|
||||
Theme::Auto => "auto",
|
||||
}
|
||||
}
|
||||
|
||||
fn from_str(s: &str) -> Self {
|
||||
match s {
|
||||
"light" => Theme::Light,
|
||||
"dark" => Theme::Dark,
|
||||
_ => Theme::Auto,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
fn get_stored_theme() -> Theme {
|
||||
if let Some(window) = window()
|
||||
&& let Ok(Some(storage)) = window.local_storage()
|
||||
&& let Ok(Some(theme_str)) = storage.get_item("theme")
|
||||
{
|
||||
return Theme::from_str(&theme_str);
|
||||
}
|
||||
Theme::Auto
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
fn get_stored_theme() -> Theme {
|
||||
Theme::Auto
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
fn set_stored_theme(theme: Theme) {
|
||||
if let Some(window) = window()
|
||||
&& let Ok(Some(storage)) = window.local_storage()
|
||||
{
|
||||
let _ = storage.set_item("theme", theme.as_str());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
fn set_stored_theme(_theme: Theme) {
|
||||
// No-op on server side
|
||||
}
|
||||
|
||||
#[cfg(feature = "hydrate")]
|
||||
fn apply_theme(theme: Theme) {
|
||||
if let Some(window) = window()
|
||||
&& let Some(document) = window.document()
|
||||
&& let Some(html) = document.document_element()
|
||||
{
|
||||
let class_list = html.class_list();
|
||||
let _ = class_list.remove_2("light", "dark");
|
||||
match theme {
|
||||
Theme::Light => {
|
||||
let _ = class_list.add_1("light");
|
||||
}
|
||||
Theme::Dark => {
|
||||
let _ = class_list.add_1("dark");
|
||||
}
|
||||
Theme::Auto => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "hydrate"))]
|
||||
fn apply_theme(_theme: Theme) {
|
||||
// No-op on server side
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn ThemeSwitcher() -> impl IntoView {
|
||||
let (current_theme, set_current_theme) = signal(Theme::Auto);
|
||||
let (dropdown_open, set_dropdown_open) = signal(false);
|
||||
|
||||
// Load theme from localStorage and apply it on client side only
|
||||
Effect::new(move |_| {
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
let stored_theme = get_stored_theme();
|
||||
set_current_theme.set(stored_theme);
|
||||
apply_theme(stored_theme);
|
||||
}
|
||||
});
|
||||
|
||||
// Apply theme when it changes (client side only)
|
||||
Effect::new(move |_| {
|
||||
#[cfg(feature = "hydrate")]
|
||||
{
|
||||
let theme = current_theme.get();
|
||||
apply_theme(theme);
|
||||
}
|
||||
});
|
||||
|
||||
let toggle_dropdown = move |_| {
|
||||
set_dropdown_open.update(|open| *open = !*open);
|
||||
};
|
||||
|
||||
let select_theme = move |theme: Theme| {
|
||||
set_current_theme.set(theme);
|
||||
#[cfg(feature = "hydrate")]
|
||||
set_stored_theme(theme);
|
||||
set_dropdown_open.set(false);
|
||||
};
|
||||
|
||||
let theme_icon = move || match current_theme.get() {
|
||||
Theme::Light => "☀️",
|
||||
Theme::Dark => "🌙",
|
||||
Theme::Auto => "🌓",
|
||||
};
|
||||
|
||||
view! {
|
||||
<div class="theme-switcher">
|
||||
<button class="theme-button" on:click=toggle_dropdown>
|
||||
{theme_icon}
|
||||
</button>
|
||||
<div class="theme-dropdown" class:hidden=move || !dropdown_open.get()>
|
||||
<button
|
||||
class="theme-option"
|
||||
class:active=move || current_theme.get() == Theme::Light
|
||||
on:click=move |_| select_theme(Theme::Light)
|
||||
>
|
||||
"Light"
|
||||
</button>
|
||||
<button
|
||||
class="theme-option"
|
||||
class:active=move || current_theme.get() == Theme::Dark
|
||||
on:click=move |_| select_theme(Theme::Dark)
|
||||
>
|
||||
"Dark"
|
||||
</button>
|
||||
<button
|
||||
class="theme-option"
|
||||
class:active=move || current_theme.get() == Theme::Auto
|
||||
on:click=move |_| select_theme(Theme::Auto)
|
||||
>
|
||||
"Auto"
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn SiteHeader() -> impl IntoView {
|
||||
view! {
|
||||
<header class="site-header">
|
||||
<CommandLine />
|
||||
<ThemeSwitcher />
|
||||
</header>
|
||||
}
|
||||
}
|
1
crates/app/src/version.rs
Normal file
|
@ -0,0 +1 @@
|
|||
|
183
crates/app/src/wgpu_renderer.rs
Normal file
|
@ -0,0 +1,183 @@
|
|||
use leptos::prelude::*;
|
||||
use log::*;
|
||||
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use wasm_bindgen::{prelude::*, JsCast};
|
||||
use web_sys::{HtmlCanvasElement, WebGl2RenderingContext as GL, WebGlProgram, WebGlShader};
|
||||
|
||||
// Vertex shader source
|
||||
const VERTEX_SHADER_SRC: &str = r#"#version 300 es
|
||||
precision mediump float;
|
||||
|
||||
void main() {
|
||||
// Define the vertices of a triangle
|
||||
const vec2 positions[3] = vec2[3](
|
||||
vec2(0.0, 0.5),
|
||||
vec2(-0.5, -0.5),
|
||||
vec2(0.5, -0.5)
|
||||
);
|
||||
|
||||
gl_Position = vec4(positions[gl_VertexID], 0.0, 1.0);
|
||||
}
|
||||
"#;
|
||||
|
||||
// Fragment shader source
|
||||
const FRAGMENT_SHADER_SRC: &str = r#"#version 300 es
|
||||
precision mediump float;
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(1.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
"#;
|
||||
|
||||
// Implementation for WASM/browser targets
|
||||
fn set_up_wgpu_triangle(canvas_id: &str) {
|
||||
let window = web_sys::window().unwrap();
|
||||
let document = window.document().unwrap();
|
||||
|
||||
// Check if document is ready
|
||||
if let Some(element) = document.get_element_by_id(canvas_id) {
|
||||
let canvas = element.dyn_into::<HtmlCanvasElement>().unwrap();
|
||||
|
||||
if let Err(e) = set_up_webgl(&canvas) {
|
||||
error!("WebGL setup failed: {:?}", e);
|
||||
} else {
|
||||
info!("WebGL triangle renderer initialized");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up WebGL and renders a triangle
|
||||
fn set_up_webgl(canvas: &HtmlCanvasElement) -> Result<(), JsValue> {
|
||||
// Get WebGL context
|
||||
let context = canvas.get_context("webgl2")?.unwrap().dyn_into::<GL>()?;
|
||||
|
||||
// Set clear color and viewport
|
||||
context.clear_color(0.1, 0.2, 0.3, 1.0);
|
||||
context.clear(GL::COLOR_BUFFER_BIT);
|
||||
|
||||
// Create shaders
|
||||
let vert_shader = compile_shader(&context, GL::VERTEX_SHADER, VERTEX_SHADER_SRC)?;
|
||||
|
||||
let frag_shader = compile_shader(&context, GL::FRAGMENT_SHADER, FRAGMENT_SHADER_SRC)?;
|
||||
|
||||
// Create program
|
||||
let program = link_program(&context, &vert_shader, &frag_shader)?;
|
||||
context.use_program(Some(&program));
|
||||
|
||||
// Create empty vertex array object (required for core profile)
|
||||
let vao = context
|
||||
.create_vertex_array()
|
||||
.ok_or("Could not create vertex array object")?;
|
||||
context.bind_vertex_array(Some(&vao));
|
||||
|
||||
// Start rendering
|
||||
render_loop(context);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Compiles a shader from source
|
||||
fn compile_shader(context: &GL, shader_type: u32, source: &str) -> Result<WebGlShader, String> {
|
||||
let shader = context
|
||||
.create_shader(shader_type)
|
||||
.ok_or_else(|| String::from("Unable to create shader object"))?;
|
||||
|
||||
context.shader_source(&shader, source);
|
||||
context.compile_shader(&shader);
|
||||
|
||||
if context
|
||||
.get_shader_parameter(&shader, GL::COMPILE_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(shader)
|
||||
} else {
|
||||
Err(context
|
||||
.get_shader_info_log(&shader)
|
||||
.unwrap_or_else(|| String::from("Unknown error creating shader")))
|
||||
}
|
||||
}
|
||||
|
||||
// Links a shader program
|
||||
fn link_program(
|
||||
context: &GL,
|
||||
vert_shader: &WebGlShader,
|
||||
frag_shader: &WebGlShader,
|
||||
) -> Result<WebGlProgram, String> {
|
||||
let program = context
|
||||
.create_program()
|
||||
.ok_or_else(|| String::from("Unable to create shader program"))?;
|
||||
|
||||
context.attach_shader(&program, vert_shader);
|
||||
context.attach_shader(&program, frag_shader);
|
||||
context.link_program(&program);
|
||||
|
||||
if context
|
||||
.get_program_parameter(&program, GL::LINK_STATUS)
|
||||
.as_bool()
|
||||
.unwrap_or(false)
|
||||
{
|
||||
Ok(program)
|
||||
} else {
|
||||
Err(context
|
||||
.get_program_info_log(&program)
|
||||
.unwrap_or_else(|| String::from("Unknown error creating program")))
|
||||
}
|
||||
}
|
||||
|
||||
// Sets up an animation loop
|
||||
fn render_loop(context: GL) {
|
||||
// Create a render function
|
||||
let f = Rc::new(RefCell::new(None));
|
||||
let g = f.clone();
|
||||
|
||||
*g.borrow_mut() = Some(Closure::new(move || {
|
||||
// Clear the canvas
|
||||
context.clear_color(0.1, 0.2, 0.3, 1.0);
|
||||
context.clear(GL::COLOR_BUFFER_BIT);
|
||||
|
||||
// Draw the triangle (3 vertices starting at index 0)
|
||||
context.draw_arrays(GL::TRIANGLES, 0, 3);
|
||||
|
||||
// Request next frame
|
||||
request_animation_frame(f.borrow().as_ref().unwrap());
|
||||
}));
|
||||
|
||||
// Start the render loop
|
||||
request_animation_frame(g.borrow().as_ref().unwrap());
|
||||
}
|
||||
|
||||
// Helper function to request animation frame
|
||||
fn request_animation_frame(f: &Closure<dyn FnMut()>) {
|
||||
web_sys::window()
|
||||
.unwrap()
|
||||
.request_animation_frame(f.as_ref().unchecked_ref())
|
||||
.expect("should register `requestAnimationFrame` OK");
|
||||
}
|
||||
|
||||
#[component]
|
||||
pub fn WgpuTriangle() -> impl IntoView {
|
||||
// Use a canvas with a fixed ID
|
||||
let canvas_id = "triangle-canvas";
|
||||
|
||||
// create_effect(move |_| {
|
||||
// // This will only run client-side code
|
||||
// if cfg!(target_arch = "wasm32") {
|
||||
// set_up_wgpu_triangle(canvas_id);
|
||||
// }
|
||||
// });
|
||||
|
||||
view! {
|
||||
<div class="triangle-container" style="width: 100%; height: 100%">
|
||||
<canvas
|
||||
id=canvas_id
|
||||
width="800"
|
||||
height="600"
|
||||
style="width: 100%; height: 100%; border: 1px solid #ccc"
|
||||
></canvas>
|
||||
</div>
|
||||
}
|
||||
}
|
15
crates/frontend/Cargo.toml
Normal file
|
@ -0,0 +1,15 @@
|
|||
[package]
|
||||
name = "mascully_website_frontend"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[dependencies]
|
||||
mascully_website_app = { path = "../app", default-features = false, features = ["hydrate"] }
|
||||
leptos = { workspace = true, features = [ "hydrate" ] }
|
||||
log.workspace = true
|
||||
wasm-bindgen.workspace = true
|
||||
console_log.workspace = true
|
||||
console_error_panic_hook.workspace = true
|
BIN
crates/frontend/assets/carplexity_1.png
Normal file
After Width: | Height: | Size: 597 KiB |
443
crates/frontend/assets/css/styles.css
Normal file
|
@ -0,0 +1,443 @@
|
|||
@font-face {
|
||||
font-family: "Space Grotesk";
|
||||
font-style: normal;
|
||||
font-weight: 300 700;
|
||||
font-display: swap;
|
||||
src: url("/fonts/space_grotesk_variable.woff2") format("woff2-variations");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "IBM Plex Sans";
|
||||
font-style: normal;
|
||||
font-weight: 100 700;
|
||||
font-display: swap;
|
||||
src: url("/fonts/ibm_plex_sans_variable.ttf") format("truetype-variations");
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: "Zed Mono";
|
||||
font-style: normal;
|
||||
font-weight: 400 700;
|
||||
font-display: swap;
|
||||
src: url("/fonts/zed_mono_variable.woff2") format("woff2-variations");
|
||||
}
|
||||
|
||||
:root {
|
||||
--body-bg: oklch(15% 0.02 240); /* darker background */
|
||||
--main-bg: oklch(20% 0 0 / 0.7); /* more transparency */
|
||||
--text-color: oklch(80% 0 0);
|
||||
--link-color: oklch(70% 0.15 240); /* lighter blue for links */
|
||||
--link-hover-color: oklch(80% 0.15 240); /* blue */
|
||||
--border-color: oklch(40% 0 0);
|
||||
--post-description-color: oklch(60% 0 0);
|
||||
--header-bg: oklch(15% 0.02 240); /* same as body background */
|
||||
--input-bg: oklch(25% 0 0);
|
||||
--button-bg: oklch(30% 0 0);
|
||||
--button-hover-bg: oklch(35% 0 0);
|
||||
--bg-tile: url("/tile_1_dark.png");
|
||||
--bg-color: oklch(20% 0 0);
|
||||
}
|
||||
|
||||
:root.light {
|
||||
--navbar-opacity: 0.5;
|
||||
--navbar-opacity-hover: 1;
|
||||
--transition-time: 0.3s;
|
||||
--body-bg: oklch(98% 0 0); /* white */
|
||||
--main-bg: oklch(100% 0 0 / 0.85); /* white with transparency */
|
||||
--text-color: oklch(0% 0 0); /* black */
|
||||
--link-color: oklch(50% 0.15 240); /* blue for links */
|
||||
--link-hover-color: oklch(60% 0.15 240); /* blue */
|
||||
--border-color: oklch(90% 0 0);
|
||||
--post-description-color: oklch(60% 0 0);
|
||||
--header-bg: oklch(98% 0 0); /* same as body background */
|
||||
--input-bg: oklch(95% 0 0);
|
||||
--button-bg: oklch(90% 0 0);
|
||||
--button-hover-bg: oklch(85% 0 0);
|
||||
--bg-tile: url("/tile_1_light.png");
|
||||
--bg-color: oklch(100% 0 0); /* white background */
|
||||
}
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: var(--body-bg);
|
||||
background-image: var(--bg-tile);
|
||||
background-repeat: repeat;
|
||||
color: var(--text-color);
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
min-height: 100vh;
|
||||
font-family: "IBM Plex Sans", sans-serif;
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
}
|
||||
|
||||
p {
|
||||
padding: 1rem 0rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
font-size: xx-large;
|
||||
}
|
||||
|
||||
#root {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.content-wrapper {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.main-content {
|
||||
display: grid;
|
||||
grid-template-columns: min-content 1fr;
|
||||
max-width: 1200px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
opacity: var(--navbar-opacity);
|
||||
transition: opacity var(--transition-time) ease;
|
||||
display: grid;
|
||||
grid-auto-rows: max-content;
|
||||
grid-auto-flow: row;
|
||||
padding: 1rem;
|
||||
align-content: start;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.sidebar:hover {
|
||||
opacity: var(--navbar-opacity-hover);
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
list-style: none;
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
text-decoration: none;
|
||||
color: var(--link-color);
|
||||
transition: color var(--transition-time) ease;
|
||||
}
|
||||
|
||||
a:hover {
|
||||
color: var(--link-hover-color);
|
||||
}
|
||||
|
||||
.main-nav a {
|
||||
color: inherit;
|
||||
}
|
||||
|
||||
.main-nav hr {
|
||||
border: 0;
|
||||
border-top: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
main {
|
||||
position: relative;
|
||||
z-index: 0;
|
||||
background-color: var(--main-bg);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6 {
|
||||
font-family: "Space Grotesk", sans-serif;
|
||||
}
|
||||
|
||||
/* Posts page styling */
|
||||
.posts-page {
|
||||
max-width: 800px;
|
||||
}
|
||||
|
||||
.posts-page header {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.posts-list {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.post-preview {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.post-preview:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.post-preview h2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.post-preview h2 a {
|
||||
text-decoration: none;
|
||||
color: inherit;
|
||||
transition: color var(--transition-time) ease;
|
||||
}
|
||||
|
||||
.post-preview h2 a:hover {
|
||||
color: var(--link-hover-color);
|
||||
}
|
||||
|
||||
.post-description {
|
||||
color: var(--post-description-color);
|
||||
padding: 0;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Plot component styling */
|
||||
.plot-container {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
padding: 1rem;
|
||||
background-color: var(--main-bg);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border-color);
|
||||
}
|
||||
|
||||
.plot-canvas {
|
||||
border: 1px solid var(--border-color);
|
||||
background-color: oklch(100% 0 0);
|
||||
}
|
||||
|
||||
.plot-demo-post {
|
||||
max-width: 900px;
|
||||
}
|
||||
|
||||
.plot-demo-post header {
|
||||
padding-bottom: 2rem;
|
||||
}
|
||||
|
||||
.plot-section {
|
||||
display: grid;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.plot-section h2 {
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
/* Header styling */
|
||||
.site-header {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
display: grid;
|
||||
grid-template-columns: 1fr auto;
|
||||
align-items: center;
|
||||
padding: 1rem 2rem;
|
||||
background-color: var(--header-bg);
|
||||
backdrop-filter: blur(10px);
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.terminal-input {
|
||||
font-family: "Zed Mono", monospace;
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem;
|
||||
font-size: 0.9rem;
|
||||
width: 100%;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.terminal-input::placeholder {
|
||||
color: var(--post-description-color);
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
position: relative;
|
||||
display: grid;
|
||||
}
|
||||
|
||||
.theme-button {
|
||||
background-color: var(--button-bg);
|
||||
border: 1px solid var(--border-color);
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color var(--transition-time) ease;
|
||||
font-size: 1.2rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.theme-button:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
}
|
||||
|
||||
.theme-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
background-color: var(--header-bg);
|
||||
backdrop-filter: blur(10px);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 4px;
|
||||
padding: 0.5rem;
|
||||
display: grid;
|
||||
gap: 0.25rem;
|
||||
min-width: 80px;
|
||||
z-index: 9999;
|
||||
}
|
||||
|
||||
.theme-dropdown.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.theme-option {
|
||||
background: none;
|
||||
border: none;
|
||||
color: var(--text-color);
|
||||
padding: 0.5rem;
|
||||
text-align: left;
|
||||
cursor: pointer;
|
||||
border-radius: 2px;
|
||||
transition: background-color var(--transition-time) ease;
|
||||
}
|
||||
|
||||
.theme-option:hover {
|
||||
background-color: var(--button-hover-bg);
|
||||
}
|
||||
|
||||
.theme-option.active {
|
||||
background-color: var(--button-bg);
|
||||
}
|
||||
|
||||
/* Projects page styling */
|
||||
.projects-list {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
.project_listing {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 1rem;
|
||||
background-color: var(--main-bg);
|
||||
}
|
||||
|
||||
.project-grid {
|
||||
display: grid;
|
||||
grid-template-columns: 3fr 2fr;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.project-title {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.project-image {
|
||||
grid-column: 2;
|
||||
grid-row: 1 / 4;
|
||||
}
|
||||
|
||||
.project-image img {
|
||||
width: 100%;
|
||||
height: auto;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.project-text {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
}
|
||||
|
||||
.project-links {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.main-content {
|
||||
grid-template-columns: 1fr;
|
||||
grid-template-rows: auto 1fr;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
grid-auto-flow: column;
|
||||
justify-items: center;
|
||||
align-content: center;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.main-nav ul {
|
||||
grid-auto-flow: column;
|
||||
}
|
||||
|
||||
.plot-canvas {
|
||||
max-width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.terminal-input {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.theme-switcher {
|
||||
justify-self: center;
|
||||
}
|
||||
|
||||
.project-grid {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.project-title {
|
||||
grid-column: 1;
|
||||
grid-row: 1;
|
||||
}
|
||||
|
||||
.project-image {
|
||||
grid-column: 1;
|
||||
grid-row: 2;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.project-text {
|
||||
grid-column: 1;
|
||||
grid-row: 3;
|
||||
}
|
||||
|
||||
.project-links {
|
||||
grid-column: 1;
|
||||
grid-row: 4;
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
}
|
||||
}
|
BIN
crates/frontend/assets/endolingual_1.png
Normal file
After Width: | Height: | Size: 8.8 KiB |
BIN
crates/frontend/assets/fonts/ibm_plex_sans_variable.ttf
Normal file
BIN
crates/frontend/assets/fonts/space_grotesk_variable.woff2
Normal file
BIN
crates/frontend/assets/fonts/zed_mono_variable.woff2
Normal file
BIN
crates/frontend/assets/icon_1.png
Normal file
After Width: | Height: | Size: 1.4 KiB |
BIN
crates/frontend/assets/icon_2.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
crates/frontend/assets/parva_1.png
Normal file
After Width: | Height: | Size: 5.1 MiB |
BIN
crates/frontend/assets/tile_1_dark.png
Normal file
After Width: | Height: | Size: 41 KiB |
BIN
crates/frontend/assets/tile_1_light.png
Normal file
After Width: | Height: | Size: 79 KiB |
11
crates/frontend/src/lib.rs
Normal file
|
@ -0,0 +1,11 @@
|
|||
use leptos::prelude::*;
|
||||
use mascully_website_app::App;
|
||||
use wasm_bindgen::prelude::wasm_bindgen;
|
||||
|
||||
#[wasm_bindgen]
|
||||
pub fn hydrate() {
|
||||
_ = console_log::init_with_level(log::Level::Debug);
|
||||
console_error_panic_hook::set_once();
|
||||
|
||||
leptos::mount::hydrate_body(App);
|
||||
}
|
11
crates/grid-shader/Cargo.toml
Normal file
|
@ -0,0 +1,11 @@
|
|||
[package]
|
||||
name = "grid-shader"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
publish = false
|
||||
|
||||
[dependencies]
|
||||
env_logger = "0.11.6"
|
||||
log = "0.4.25"
|
||||
wgpu = "26.0.1"
|
||||
winit = "0.30.11"
|
32
crates/grid-shader/src/lib.rs
Normal file
|
@ -0,0 +1,32 @@
|
|||
// use winit::{
|
||||
// event::*,
|
||||
// event_loop::EventLoop,
|
||||
// keyboard::{KeyCode, PhysicalKey},
|
||||
// };
|
||||
|
||||
// TODO
|
||||
pub fn run() {
|
||||
// env_logger::init();
|
||||
// let event_loop = EventLoop::new().unwrap();
|
||||
// let window = WindowBuilder::new().build(&event_loop).unwrap();
|
||||
|
||||
// let _ = event_loop.run(move |event, control_flow| match event {
|
||||
// Event::WindowEvent {
|
||||
// ref event,
|
||||
// window_id,
|
||||
// } if window_id == window.id() => match event {
|
||||
// WindowEvent::CloseRequested
|
||||
// | WindowEvent::KeyboardInput {
|
||||
// event:
|
||||
// KeyEvent {
|
||||
// state: ElementState::Pressed,
|
||||
// physical_key: PhysicalKey::Code(KeyCode::Escape),
|
||||
// ..
|
||||
// },
|
||||
// ..
|
||||
// } => control_flow.exit(),
|
||||
// _ => {}
|
||||
// },
|
||||
// _ => {}
|
||||
// });
|
||||
}
|
27
crates/server/Cargo.toml
Normal file
|
@ -0,0 +1,27 @@
|
|||
[package]
|
||||
name = "mascully_website_server"
|
||||
version.workspace = true
|
||||
edition.workspace = true
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
mascully_website_app = { path = "../app", default-features = false, features = [
|
||||
"ssr",
|
||||
] }
|
||||
leptos = { workspace = true, features = ["ssr"] }
|
||||
leptos_meta.workspace = true
|
||||
leptos_router.workspace = true
|
||||
leptos_dom.workspace = true
|
||||
leptos_axum.workspace = true
|
||||
gloo-net.workspace = true
|
||||
log.workspace = true
|
||||
futures.workspace = true
|
||||
simple_logger.workspace = true
|
||||
serde_json.workspace = true
|
||||
reqwest.workspace = true
|
||||
dotenvy.workspace = true
|
||||
axum.workspace = true
|
||||
tokio.workspace = true
|
||||
tower.workspace = true
|
||||
tower-http.workspace = true
|
23
crates/server/src/main.rs
Normal file
|
@ -0,0 +1,23 @@
|
|||
use leptos::{logging::log, prelude::*};
|
||||
use leptos_axum::{LeptosRoutes, generate_route_list};
|
||||
use mascully_website_app::{App, shell};
|
||||
|
||||
#[tokio::main]
|
||||
pub async fn main() {
|
||||
let conf = get_configuration(None).unwrap();
|
||||
let addr = conf.leptos_options.site_addr;
|
||||
let leptos_options = conf.leptos_options;
|
||||
let routes = generate_route_list(App);
|
||||
|
||||
let app = axum::Router::new()
|
||||
.leptos_routes(&leptos_options, routes, {
|
||||
let leptos_options = leptos_options.clone();
|
||||
move || shell(leptos_options.clone())
|
||||
})
|
||||
.fallback(leptos_axum::file_and_error_handler(shell))
|
||||
.with_state(leptos_options);
|
||||
|
||||
log!("listening on http://{}", &addr);
|
||||
let listener = tokio::net::TcpListener::bind(&addr).await.unwrap();
|
||||
axum::serve(listener, app.into_make_service()).await.unwrap();
|
||||
}
|
116
flake.lock
generated
Normal file
|
@ -0,0 +1,116 @@
|
|||
{
|
||||
"nodes": {
|
||||
"crane": {
|
||||
"locked": {
|
||||
"lastModified": 1752946753,
|
||||
"narHash": "sha256-g5uP3jIj+STUcfTJDKYopxnSijs2agRg13H0SGL5iE4=",
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"rev": "544d09fecc8c2338542c57f3f742f1a0c8c71e13",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "ipetkov",
|
||||
"repo": "crane",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"fenix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
],
|
||||
"rust-analyzer-src": "rust-analyzer-src"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1752561972,
|
||||
"narHash": "sha256-pPqES/udciKmKo422mfwRQ3YzjUCVyCTOsgZYA1xh+g=",
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"rev": "d17ca03c15660ecb8e5a01ca34e441f594feec62",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "fenix",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-parts": {
|
||||
"inputs": {
|
||||
"nixpkgs-lib": "nixpkgs-lib"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1751413152,
|
||||
"narHash": "sha256-Tyw1RjYEsp5scoigs1384gIg6e0GoBVjms4aXFfRssQ=",
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"rev": "77826244401ea9de6e3bac47c2db46005e1f30b5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "hercules-ci",
|
||||
"repo": "flake-parts",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1752446735,
|
||||
"narHash": "sha256-Nz2vtUEaRB/UjvPfuhHpez060P/4mvGpXW4JCDIboA4=",
|
||||
"owner": "nixos",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "a421ac6595024edcfbb1ef950a3712b89161c359",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nixos",
|
||||
"ref": "nixpkgs-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-lib": {
|
||||
"locked": {
|
||||
"lastModified": 1751159883,
|
||||
"narHash": "sha256-urW/Ylk9FIfvXfliA1ywh75yszAbiTEVgpPeinFyVZo=",
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"rev": "14a40a1d7fb9afa4739275ac642ed7301a9ba1ab",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-community",
|
||||
"repo": "nixpkgs.lib",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"crane": "crane",
|
||||
"fenix": "fenix",
|
||||
"flake-parts": "flake-parts",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"rust-analyzer-src": {
|
||||
"flake": false,
|
||||
"locked": {
|
||||
"lastModified": 1752475356,
|
||||
"narHash": "sha256-V2nHrCJ0/Pv30j8NWJ4GfDlaNzfkOdYI0jS69GdVpq8=",
|
||||
"owner": "rust-lang",
|
||||
"repo": "rust-analyzer",
|
||||
"rev": "e10d64eb402a25a32d9f1ef60cacc89d82a01b85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "rust-lang",
|
||||
"ref": "nightly",
|
||||
"repo": "rust-analyzer",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
137
flake.nix
Normal file
|
@ -0,0 +1,137 @@
|
|||
{
|
||||
description = "Flake for building mascully with Crane";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:nixos/nixpkgs/nixpkgs-unstable";
|
||||
flake-parts.url = "github:hercules-ci/flake-parts";
|
||||
fenix = {
|
||||
url = "github:nix-community/fenix";
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
crane.url = "github:ipetkov/crane";
|
||||
};
|
||||
|
||||
nixConfig = { };
|
||||
|
||||
outputs =
|
||||
inputs@{
|
||||
flake-parts,
|
||||
nixpkgs,
|
||||
fenix,
|
||||
crane,
|
||||
...
|
||||
}:
|
||||
flake-parts.lib.mkFlake { inherit inputs; } {
|
||||
debug = true;
|
||||
systems = nixpkgs.lib.systems.flakeExposed;
|
||||
|
||||
perSystem =
|
||||
{
|
||||
config,
|
||||
self',
|
||||
inputs',
|
||||
pkgs,
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
pkgs = import nixpkgs {
|
||||
inherit system;
|
||||
config.allowUnfree = true;
|
||||
};
|
||||
|
||||
rustToolchain = fenix.packages.${system}.combine ([
|
||||
(fenix.packages.${system}.complete.withComponents [
|
||||
"cargo"
|
||||
"clippy"
|
||||
"rust-src"
|
||||
"rustc"
|
||||
"rustfmt"
|
||||
])
|
||||
fenix.packages.${system}.complete.rust-analyzer
|
||||
fenix.packages.${system}.targets.wasm32-unknown-unknown.latest.rust-std
|
||||
]);
|
||||
|
||||
craneLib = (crane.mkLib pkgs).overrideToolchain rustToolchain;
|
||||
|
||||
# extraPackages = with pkgs; [
|
||||
# cargo-leptos
|
||||
# binaryen
|
||||
# leptosfmt
|
||||
# ];
|
||||
|
||||
commonArgs = {
|
||||
# src = craneLib.cleanCargoSource ./.;
|
||||
src = ./.;
|
||||
strictDeps = true;
|
||||
buildInputs = with pkgs; [
|
||||
cargo-leptos
|
||||
leptosfmt
|
||||
binaryen
|
||||
];
|
||||
nativeBuildInputs = with pkgs; [
|
||||
cargo-leptos
|
||||
leptosfmt
|
||||
binaryen
|
||||
];
|
||||
};
|
||||
|
||||
leptosOptions = builtins.elemAt (builtins.fromTOML (builtins.readFile ./Cargo.toml)).workspace.metadata.leptos 0;
|
||||
|
||||
|
||||
# --profile=${leptosOptions.lib-profile-release}
|
||||
cargoArtifacts = craneLib.buildDepsOnly (
|
||||
commonArgs
|
||||
// {
|
||||
# if work is duplicated by the `mascullyWebsiteServer` package, update these
|
||||
# commands from the logs of `cargo leptos build --release -vvv`
|
||||
buildPhaseCargoCommand = ''
|
||||
cargo build --package=${leptosOptions.lib-package} --lib --target-dir=/build/source/target/front --target=wasm32-unknown-unknown --no-default-features
|
||||
cargo build --package=${leptosOptions.bin-package} --no-default-features --release
|
||||
'';
|
||||
}
|
||||
);
|
||||
|
||||
mascullyWebsiteServer = craneLib.buildPackage (
|
||||
commonArgs
|
||||
// {
|
||||
pname = "mascully_website_server";
|
||||
version = "0.1.0";
|
||||
|
||||
nativeBuildInputs = commonArgs.nativeBuildInputs ++ [ pkgs.makeWrapper ];
|
||||
|
||||
buildPhaseCargoCommand = ''
|
||||
RUST_BACKTRACE=full cargo-leptos build --release -vvv
|
||||
'';
|
||||
|
||||
installPhaseCommand = ''
|
||||
mkdir -p $out/bin
|
||||
ls target/
|
||||
pwd
|
||||
cp target/server/release/mascully_website_server $out/bin/
|
||||
# cp target/release/hash.txt $out/bin/
|
||||
cp -r target/site $out/bin/
|
||||
wrapProgram $out/bin/mascully_website_server --set LEPTOS_SITE_ROOT $out/bin/site
|
||||
'';
|
||||
|
||||
doCheck = false;
|
||||
doNotPostBuildInstallCargoBinaries = true;
|
||||
cargoArtifacts = cargoArtifacts;
|
||||
}
|
||||
);
|
||||
|
||||
in
|
||||
{
|
||||
packages = rec {
|
||||
inherit mascullyWebsiteServer;
|
||||
default = mascullyWebsiteServer;
|
||||
};
|
||||
|
||||
devShells.default = craneLib.devShell {
|
||||
inputsFrom = [ mascullyWebsiteServer ];
|
||||
# packages = extraPackages;
|
||||
RUST_SRC_PATH = "${rustToolchain}/lib/rustlib/src/rust/library";
|
||||
};
|
||||
};
|
||||
};
|
||||
}
|
2
rust-analyzer.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
[rustfmt]
|
||||
overrideCommand = ["leptosfmt", "--stdin", "--rustfmt"]
|
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
|||
edition = "2024"
|
||||
max_width = 120
|