forked from lavender-legacy/lavender.software
Initial commit
commit
62c50d368b
|
@ -0,0 +1,12 @@
|
|||
root = true
|
||||
|
||||
[*]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
end_of_line = crlf
|
||||
charset = utf-8
|
||||
trim_trailing_whitespace = false
|
||||
insert_final_newline = true
|
||||
|
||||
[*.rs]
|
||||
indent_size = 4
|
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
/dist
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,16 @@
|
|||
[package]
|
||||
name = "lavender_software"
|
||||
version = "0.1.0"
|
||||
authors = ["videogame hacker <half-kh-hacker@hackery.site>"]
|
||||
edition = "2018"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
[[bin]]
|
||||
name = "lavender_software"
|
||||
path = "build_src/main.rs"
|
||||
|
||||
[dependencies]
|
||||
askama = "0.10.5"
|
||||
notify = "4.0.17"
|
||||
siru = { git = "https://github.com/videogame-hacker/siru.git" }
|
|
@ -0,0 +1,38 @@
|
|||
The Charlotte Public License version 0.1
|
||||
|
||||
Copyright 2021, Lavender Software (collectively, the "Author" henceforth).
|
||||
|
||||
This license gives everyone permission to examine, modify, and use this software
|
||||
and the associated documentation (the "Inator"), without patent obstacles, while protecting
|
||||
the Author and any contributors (the "Composers") from liability.
|
||||
|
||||
Each Composer permits you to examine, modify, utilize, and distribute the Inator
|
||||
where it would otherwise infringe upon that Composer's copyright or any patent claims that
|
||||
they hold.
|
||||
|
||||
No contributor may revoke this license, but the Author may choose to release the Inator
|
||||
(including the contributed works of any other Composer) under a different license.
|
||||
|
||||
You may not use the Inator to accrue revenue without explicit permission from the Author.
|
||||
|
||||
You may not use the Inator to do Malevolence. If you are
|
||||
notified that you have committed a Malevolence instrumented by the Inator, your license is
|
||||
terminated unless you take all practical steps to comply within a reasonable timeframe.
|
||||
|
||||
The definition of Malevolence is at the discretion of the Author. It may include, but is not
|
||||
limited to:
|
||||
|
||||
- The promotion of bigotry, including: sexism, transphobia, homophobia, ableism, or the
|
||||
perpetuation of racial oppression.
|
||||
- Causing a detriment to public health.
|
||||
- Instigating political, economic, or corporeal violence.
|
||||
- Entrenching an empire.
|
||||
- Where applicable, use of the Inator without the informed consent of a second party who may
|
||||
object to its use.
|
||||
|
||||
The Inator is provided without any warranty, "as-is". No Composer is liable for any damages
|
||||
related to the Inator.
|
||||
|
||||
In order to receive this license, you must agree to the terms set out in this document.
|
||||
This license, authorial attribution, and copyright notice must be distributed with
|
||||
any copies or large portions of the Inator.
|
|
@ -0,0 +1,3 @@
|
|||
fn main() {
|
||||
println!("cargo:rerun-if-changed=build_src");
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
use std::{fs, path::Path};
|
||||
|
||||
use crate::*;
|
||||
|
||||
fn copy_dir_recursive(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
|
||||
fs::create_dir_all(&dst)?;
|
||||
for entry in fs::read_dir(src)? {
|
||||
let entry = entry?;
|
||||
let ty = entry.file_type()?;
|
||||
if ty.is_dir() {
|
||||
copy_dir_recursive(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
} else {
|
||||
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn copy_assets(ctx: &BuildContext) -> Result<()> {
|
||||
log_info("Copying assets…");
|
||||
copy_dir_recursive(ctx.source_dir.join("assets"), ctx.output_dir.join("assets"))?;
|
||||
|
||||
Ok(())
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
use siru::prelude::*;
|
||||
use std::{convert::TryInto, path::PathBuf, sync::Arc, time::Duration};
|
||||
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||
|
||||
mod assets;
|
||||
mod main_page;
|
||||
|
||||
pub struct BuildContext {
|
||||
source_dir: PathBuf,
|
||||
output_dir: PathBuf,
|
||||
write_pipeline: WritePipeline,
|
||||
}
|
||||
|
||||
impl SiruFS for BuildContext {
|
||||
fn get_source_dir(&self) -> &PathBuf {
|
||||
&self.source_dir
|
||||
}
|
||||
|
||||
fn get_output_dir(&self) -> &PathBuf {
|
||||
&self.output_dir
|
||||
}
|
||||
|
||||
fn get_write_pipeline(&self) -> &WritePipeline {
|
||||
&self.write_pipeline
|
||||
}
|
||||
}
|
||||
|
||||
fn build() {
|
||||
let ctx = BuildContext {
|
||||
source_dir: "src".try_into().unwrap(),
|
||||
output_dir: "dist".try_into().unwrap(),
|
||||
write_pipeline: WritePipeline::new(),
|
||||
};
|
||||
|
||||
let ctx = Arc::new(ctx);
|
||||
|
||||
[main_page::main_page, assets::copy_assets]
|
||||
.iter()
|
||||
.map(|f| {
|
||||
let ctx = Arc::clone(&ctx);
|
||||
std::thread::spawn(move || f(&ctx).unwrap())
|
||||
})
|
||||
.for_each(|t| t.join().unwrap());
|
||||
}
|
||||
|
||||
fn main() {
|
||||
println!("Watching...\n");
|
||||
|
||||
use notify::*;
|
||||
|
||||
let (tx, rx) = std::sync::mpsc::channel();
|
||||
|
||||
let mut watcher = watcher(tx, Duration::from_millis(100)).unwrap();
|
||||
build();
|
||||
|
||||
watcher.watch("./src", RecursiveMode::Recursive).unwrap();
|
||||
|
||||
loop {
|
||||
match rx.recv() {
|
||||
Ok(_) => build(),
|
||||
Err(_) => break,
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
use crate::*;
|
||||
|
||||
struct Member {
|
||||
name: String,
|
||||
website: String,
|
||||
title: String,
|
||||
}
|
||||
|
||||
impl From<&(&str, &str, &str)> for Member {
|
||||
fn from(tuple: &(&str, &str, &str)) -> Self {
|
||||
Member {
|
||||
name: tuple.0.to_string(),
|
||||
website: tuple.1.to_string(),
|
||||
title: tuple.2.to_string(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Template)]
|
||||
#[template(path = "main_page.html.j2")]
|
||||
struct MainPageTemplate {
|
||||
members: Vec<Member>,
|
||||
}
|
||||
|
||||
pub fn main_page(ctx: &BuildContext) -> Result<()> {
|
||||
log_info("Rendering main page…");
|
||||
|
||||
let members = [
|
||||
("charlotte som", "https://som.codes/", "founder"),
|
||||
("agatha rose", "https://agatharose.dev/", "meow"),
|
||||
];
|
||||
|
||||
ctx.write(
|
||||
"index.html",
|
||||
MainPageTemplate {
|
||||
members: members.iter().map(|x| x.into()).collect(),
|
||||
}
|
||||
.render()?,
|
||||
)?;
|
||||
|
||||
Ok(())
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 108 KiB |
|
@ -0,0 +1,3 @@
|
|||
/* confettijs.org MIT */ const Confetti = (() => { "use strict"; const e = 10; let t, o, n = 75, i = 25, r = 1, s = !1, l = !0, a = [], d = (new Date).getTime(); function p(e) { if (t = document.createElement("canvas"), o = t.getContext("2d"), t.width = 2 * window.innerWidth, t.height = 2 * window.innerHeight, t.style.position = "fixed", t.style.top = 0, t.style.left = 0, t.style.width = "calc(100%)", t.style.height = "calc(100%)", t.style.margin = 0, t.style.padding = 0, t.style.zIndex = 999999999, t.style.pointerEvents = "none", document.body.appendChild(t), null != e) { let t = document.getElementById(e); null != t && t.addEventListener("click", e => { !function (e, t) { let o = []; for (let i = 0; i < n; i++)o.push(c(e, t)); a.push({ particles: o }) }(2 * e.clientX, 2 * e.clientY), l && (e.target.style.visibility = "hidden") }) } window.addEventListener("resize", () => { t.width = 2 * window.innerWidth, t.height = 2 * window.innerHeight }) } function y(e) { return e.pos.y - 2 * e.size.x > 2 * window.innerHeight } function c(e, t) { let o = (16 * Math.random() + 4) * r, n = (4 * Math.random() + 4) * r; return { pos: { x: e - o / 2, y: t - n / 2 }, vel: h(), size: { x: o, y: n }, rotation: 360 * Math.random(), rotation_speed: 10 * (Math.random() - .5), hue: 360 * Math.random(), opacity: 100, lifetime: Math.random() + .25 } } function h() { let e = Math.random() - .5, t = Math.random() - .7, o = Math.sqrt(e * e + t * t); return t /= o, { x: (e /= o) * (Math.random() * i), y: t * (Math.random() * i) } } return p.prototype.setCount = (e => { "number" == typeof e ? n = e : console.error("[ERROR] Confetti.setCount() - Input needs to be of type 'number'") }), p.prototype.setPower = (e => { "number" == typeof e ? i = e : console.error("[ERROR] Confetti.setPower() - Input needs to be of type 'number'") }), p.prototype.setSize = (e => { "number" == typeof e ? r = e : console.error("[ERROR] Confetti.setSize() - Input needs to be of type 'number'") }), p.prototype.setFade = (e => { "boolean" == typeof e ? s = e : console.error("[ERROR] Confetti.setFade() - Input needs to be of type 'boolean'") }), p.prototype.destroyTarget = (e => { "boolean" == typeof e ? l = e : console.error("[ERROR] Confetti.destroyTarget() - Input needs to be of type 'boolean'") }), window.requestAnimationFrame(function t(n) { let i = (n - d) / 1e3; d = n; for (let t = a.length - 1; t >= 0; t--) { let o = a[t]; for (let t = o.particles.length - 1; t >= 0; t--) { let n = o.particles[t]; n.vel.y += e * (n.size.y / (10 * r)) * i, n.vel.x += 25 * (Math.random() - .5) * i, n.vel.x *= .98, n.vel.y *= .98, n.pos.x += n.vel.x, n.pos.y += n.vel.y, n.rotation += n.rotation_speed, s && (n.opacity -= n.lifetime), y(n) && o.particles.splice(t, 1) } 0 == o.particles.length && a.splice(t, 1) } !function () { o.clearRect(0, 0, 2 * window.innerWidth, 2 * window.innerHeight); for (const d of a) for (const a of d.particles) e = a.pos.x, t = a.pos.y, n = a.size.x, i = a.size.y, r = a.rotation, s = a.hue, l = a.opacity, o.save(), o.beginPath(), o.translate(e + n / 2, t + i / 2), o.rotate(r * Math.PI / 180), o.rect(-n / 2, -i / 2, n, i), o.fillStyle = `hsla(275deg, ${s}%, ${s / 3.6}%, ${l}%)`, o.fill(), o.restore(); var e, t, n, i, r, s, l }(), window.requestAnimationFrame(t) }), p })();
|
||||
const c = new Confetti("purple");
|
||||
c.destroyTarget(false);
|
|
@ -0,0 +1,46 @@
|
|||
:root {
|
||||
--bg: rgb(28, 23, 36);
|
||||
--fg: rgb(234, 234, 248);
|
||||
--accent: hsl(275, 57%, 68%);
|
||||
}
|
||||
|
||||
html {
|
||||
background-color: var(--bg);
|
||||
color: var(--fg);
|
||||
font-size: 1.125em;
|
||||
font-family: sans-serif;
|
||||
}
|
||||
|
||||
a {
|
||||
color: var(--accent);
|
||||
text-decoration: none;
|
||||
border-bottom: 1px solid var(--accent);
|
||||
}
|
||||
|
||||
header, main, footer {
|
||||
max-width: 90ch;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
header img {
|
||||
display: inline-block;
|
||||
margin-right: 2em;
|
||||
width: 8em;
|
||||
}
|
||||
|
||||
#purple {
|
||||
font-style: normal;
|
||||
color: var(--bg);
|
||||
background-color: var(--accent);
|
||||
border-radius: 2px;
|
||||
padding: 0.25em;
|
||||
}
|
||||
|
||||
li {
|
||||
line-height: 1.6em;
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<title>lavender software | digital product studio</title>
|
||||
|
||||
<link rel="stylesheet" href="/assets/styles.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<header>
|
||||
<img alt="Lavender Logo" src="/assets/logo.png"> <!-- TODO: We should compress this image -->
|
||||
|
||||
<div>
|
||||
<h1>lavender software</h1>
|
||||
<p>the kind of software that we make is … <em id="purple">purple</em></p>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main>
|
||||
<section>
|
||||
<h2>projects</h2>
|
||||
|
||||
<ul>
|
||||
<li><a href="https://git.lain.faith/videogame-hacker/discord-css-injector">lavender cord</a> - a theming platform for Discord</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h2>who are we?</h2>
|
||||
|
||||
<ul>
|
||||
{% for member in members %}
|
||||
<li>
|
||||
<a href="{{ member.website }}">{{ member.name }}</a> - {{ member.title }}
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</section>
|
||||
</main>
|
||||
|
||||
<script async src="/assets/main_page/confetti.js"></script>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue