forked from lavender-legacy/lavender.software
		
	Compare commits
	
		
			26 commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 80ba6a1f02 | |||
| 2e6ca1a884 | |||
| a231108b9c | |||
| 74885b0410 | |||
| afccae891d | |||
| 027451fa6d | |||
| 59e9973a25 | |||
| 1e27c8ec39 | |||
| 4e7a5aefa7 | |||
| b6f992b46a | |||
| a777aea815 | |||
| e2f4635404 | |||
| 1e1fe5b82f | |||
| c32320fec4 | |||
| e9f60bf7e7 | |||
| 9bf90c9715 | |||
| 3e561b8474 | |||
| 4f40930dd5 | |||
| 26b30cd030 | |||
| 89f26b9a0e | |||
| 1e1dfbc18a | |||
| 0da2c0459d | |||
| 4b2d9f5d11 | |||
| 5e29b82c8d | |||
| ab9e7598be | |||
| 1e235dd0d1 | 
					 21 changed files with 746 additions and 569 deletions
				
			
		|  | @ -1,12 +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 | ||||
| root = true | ||||
| 
 | ||||
| [*] | ||||
| indent_style = space | ||||
| indent_size = 2 | ||||
| end_of_line = lf | ||||
| charset = utf-8 | ||||
| trim_trailing_whitespace = false | ||||
| insert_final_newline = true | ||||
| 
 | ||||
| [*.rs] | ||||
| indent_size = 4 | ||||
|  |  | |||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							|  | @ -1,2 +1,2 @@ | |||
| /target | ||||
| /dist | ||||
| /target | ||||
| /dist | ||||
|  |  | |||
							
								
								
									
										63
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										63
									
								
								Cargo.lock
									
									
									
										generated
									
									
									
								
							|  | @ -89,6 +89,15 @@ version = "1.0.1" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "base64" | ||||
| version = "0.10.1" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "0b25d992356d2eb0ed82172f5248873db5560c4721f564b13cb5193bda5e668e" | ||||
| dependencies = [ | ||||
|  "byteorder", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "bitflags" | ||||
| version = "1.2.1" | ||||
|  | @ -416,6 +425,12 @@ dependencies = [ | |||
|  "libc", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "hex" | ||||
| version = "0.3.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "805026a5d0141ffc30abb3be3173848ad46a1b1664fe632428479619a3644d77" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "html5ever" | ||||
| version = "0.25.1" | ||||
|  | @ -492,6 +507,7 @@ dependencies = [ | |||
|  "askama", | ||||
|  "notify", | ||||
|  "siru", | ||||
|  "ssri", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
|  | @ -973,6 +989,18 @@ dependencies = [ | |||
|  "opaque-debug", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "sha2" | ||||
| version = "0.8.2" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a256f46ea78a0c0d9ff00077504903ac881a1dafdc20da66545699e7776b3e69" | ||||
| dependencies = [ | ||||
|  "block-buffer", | ||||
|  "digest", | ||||
|  "fake-simd", | ||||
|  "opaque-debug", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "shell-words" | ||||
| version = "1.0.0" | ||||
|  | @ -1006,6 +1034,21 @@ version = "0.4.3" | |||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "f173ac3d1a7e3b28003f40de0b5ce7fe2710f9b9dc3fc38664cebee46b3b6527" | ||||
| 
 | ||||
| [[package]] | ||||
| name = "ssri" | ||||
| version = "7.0.0" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "a9cec0d388f39fbe79d7aa600e8d38053bf97b1bc8d350da7c0ba800d0f423f2" | ||||
| dependencies = [ | ||||
|  "base64", | ||||
|  "digest", | ||||
|  "hex", | ||||
|  "serde", | ||||
|  "sha-1", | ||||
|  "sha2", | ||||
|  "thiserror", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "static_assertions" | ||||
| version = "1.1.0" | ||||
|  | @ -1080,6 +1123,26 @@ dependencies = [ | |||
|  "unicode-width", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "thiserror" | ||||
| version = "1.0.30" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "854babe52e4df1653706b98fcfc05843010039b406875930a70e4d9644e5c417" | ||||
| dependencies = [ | ||||
|  "thiserror-impl", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "thiserror-impl" | ||||
| version = "1.0.30" | ||||
| source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
| checksum = "aa32fd3f627f367fe16f893e2597ae3c05020f8bba2666a4e6ea73d377e5714b" | ||||
| dependencies = [ | ||||
|  "proc-macro2", | ||||
|  "quote", | ||||
|  "syn", | ||||
| ] | ||||
| 
 | ||||
| [[package]] | ||||
| name = "time" | ||||
| version = "0.1.44" | ||||
|  |  | |||
|  | @ -14,3 +14,4 @@ path = "build_src/main.rs" | |||
| askama = "0.10.5" | ||||
| notify = "4.0.17" | ||||
| siru = { git = "https://github.com/videogame-hacker/siru.git" } | ||||
| ssri = "7.0.0" | ||||
|  |  | |||
							
								
								
									
										76
									
								
								LICENSE.md
									
									
									
									
									
								
							
							
						
						
									
										76
									
								
								LICENSE.md
									
									
									
									
									
								
							|  | @ -1,38 +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. | ||||
| 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. | ||||
|  |  | |||
							
								
								
									
										51
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										51
									
								
								README.md
									
									
									
									
									
								
							|  | @ -1,17 +1,34 @@ | |||
| # lavender.software | ||||
| 
 | ||||
| Static site generated using [siru](https://github.com/videogame-hacker/siru) and hosted at [lavender.software](https://lavender.software/). | ||||
| 
 | ||||
| ## Deploying | ||||
| 
 | ||||
| ```shell | ||||
| $ # To set up, ensure that the 'dist' folder reflects the VPS | ||||
| $ git clone 'root@lavender.software:/srv/http/lavender.software' dist | ||||
| $ | ||||
| $ cargo run # Build the site | ||||
| $ cd dist/ | ||||
| dist/ $ git add -A . && git commit -m "Deploying: $(date)" | ||||
| dist/ $ git pull --rebase | ||||
| dist/ $ git push | ||||
| dist/ $ # Your changes should now be live at lavender.software | ||||
| ``` | ||||
| # lavender.software | ||||
| 
 | ||||
| Static site generated using [siru](https://github.com/videogame-hacker/siru) and hosted at [lavender.software](https://lavender.software/). | ||||
| 
 | ||||
| ## Setting Up | ||||
| 
 | ||||
| ```shell | ||||
| $ git clone 'git@lavender.software:lavender/lavender.software.git' | ||||
| $ cd lavender.software/ | ||||
| lavender.software/ $ mkdir dist # or follow instructions in 'Deploying' | ||||
| lavender.software/ $ cargo run | ||||
| ... | ||||
| lavender.software/ $ # Built files are in dist/ | ||||
| ``` | ||||
| 
 | ||||
| You may want to `cd dist && python -m http.server` to get a local HTTP server. | ||||
| 
 | ||||
| ## Deploying | ||||
| 
 | ||||
| **Note:** You don't need to do this unless you're the one deploying the site to the production environment. | ||||
| 
 | ||||
| ```shell | ||||
| $ # To set up, ensure that the 'dist' folder reflects the VPS | ||||
| $ git clone 'root@lavender.software:/srv/http/lavender.software' dist | ||||
| $ | ||||
| $ cargo run # Build the site | ||||
| $ cd dist/ | ||||
| dist/ $ git add -A . && git commit -m "Deploying: $(date)" | ||||
| dist/ $ git pull --rebase | ||||
| dist/ $ git push | ||||
| dist/ $ # Your changes should now be live at lavender.software | ||||
| ``` | ||||
| 
 | ||||
| If deploying this site, you may also want to examine the sample `nginx-site.conf` file. | ||||
|  |  | |||
							
								
								
									
										6
									
								
								build.rs
									
									
									
									
									
								
							
							
						
						
									
										6
									
								
								build.rs
									
									
									
									
									
								
							|  | @ -1,3 +1,3 @@ | |||
| fn main() { | ||||
|     println!("cargo:rerun-if-changed=build_src"); | ||||
| } | ||||
| fn main() { | ||||
|     println!("cargo:rerun-if-changed=build_src"); | ||||
| } | ||||
|  |  | |||
|  | @ -1,8 +1,8 @@ | |||
| use crate::*; | ||||
| 
 | ||||
| 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(()) | ||||
| } | ||||
| use crate::*; | ||||
| 
 | ||||
| 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(()) | ||||
| } | ||||
|  |  | |||
|  | @ -1,84 +1,81 @@ | |||
| use siru::prelude::*; | ||||
| use std::{convert::TryInto, fs, path::Path, path::PathBuf, sync::Arc, time::Duration}; | ||||
| 
 | ||||
| type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; | ||||
| 
 | ||||
| mod assets; | ||||
| mod main_page; | ||||
| mod webring; | ||||
| 
 | ||||
| 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 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub 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(()) | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
|         webring::copy_webring, | ||||
|     ] | ||||
|     .iter() | ||||
|     .map(|f| { | ||||
|         let ctx = Arc::clone(&ctx); | ||||
|         std::thread::spawn(move || f(&ctx).unwrap()) | ||||
|     }) | ||||
|     .for_each(|t| t.join().unwrap()); | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     build(); | ||||
| 
 | ||||
|     if matches!(std::env::args().nth(1).as_deref(), Some("watch")) { | ||||
|         use notify::*; | ||||
| 
 | ||||
|         let (tx, rx) = std::sync::mpsc::channel(); | ||||
| 
 | ||||
|         let mut watcher = watcher(tx, Duration::from_millis(100)).unwrap(); | ||||
|         watcher.watch("./src", RecursiveMode::Recursive).unwrap(); | ||||
| 
 | ||||
|         loop { | ||||
|             match rx.recv() { | ||||
|                 Ok(_) => build(), | ||||
|                 Err(_) => break, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| use siru::prelude::*; | ||||
| use std::{convert::TryInto, fs, path::Path, path::PathBuf, sync::Arc, time::Duration}; | ||||
| 
 | ||||
| type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>; | ||||
| 
 | ||||
| mod assets; | ||||
| mod main_page; | ||||
| mod webring; | ||||
| 
 | ||||
| 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 | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| pub 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(()) | ||||
| } | ||||
| 
 | ||||
| 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, | ||||
|         webring::copy_webring, | ||||
|     ] | ||||
|     .iter() | ||||
|     .map(|f| { | ||||
|         let ctx = Arc::clone(&ctx); | ||||
|         std::thread::spawn(move || f(&ctx).unwrap()) | ||||
|     }) | ||||
|     .for_each(|t| t.join().unwrap()); | ||||
| } | ||||
| 
 | ||||
| fn main() { | ||||
|     build(); | ||||
| 
 | ||||
|     if matches!(std::env::args().nth(1).as_deref(), Some("watch")) { | ||||
|         use notify::*; | ||||
| 
 | ||||
|         let (tx, rx) = std::sync::mpsc::channel(); | ||||
| 
 | ||||
|         let mut watcher = watcher(tx, Duration::from_millis(100)).unwrap(); | ||||
|         watcher.watch("./src", RecursiveMode::Recursive).unwrap(); | ||||
| 
 | ||||
|         while rx.recv().is_ok() { | ||||
|             build() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -1,58 +1,13 @@ | |||
| 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"), | ||||
|         ("maya", "https://1312.gay/", "chief director of maya"), | ||||
|         ( | ||||
|             "Luna Lulu", | ||||
|             "https://lunaisa.dev", | ||||
|             "critically acclaimed website maker", | ||||
|         ), | ||||
|         ( | ||||
|             "annie versario", | ||||
|             "https://annie.kitty.lgbt", | ||||
|             "regional marquee technician", | ||||
|         ), | ||||
|         ( | ||||
|             "The System", | ||||
|             "https://the-system.eu.org", | ||||
|             "lead systems specialist", | ||||
|         ), | ||||
|     ]; | ||||
| 
 | ||||
|     ctx.write( | ||||
|         "index.html", | ||||
|         MainPageTemplate { | ||||
|             members: members.iter().map(|x| x.into()).collect(), | ||||
|         } | ||||
|         .render()?, | ||||
|     )?; | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| use crate::*; | ||||
| 
 | ||||
| #[derive(Template)] | ||||
| #[template(path = "main_page.html.j2")] | ||||
| struct MainPageTemplate {} | ||||
| 
 | ||||
| pub fn main_page(ctx: &BuildContext) -> Result<()> { | ||||
|     log_info("Rendering main page…"); | ||||
| 
 | ||||
|     ctx.write("index.html", MainPageTemplate {}.render()?)?; | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
|  |  | |||
|  | @ -1,11 +1,23 @@ | |||
| use crate::*; | ||||
| 
 | ||||
| pub fn copy_webring(ctx: &BuildContext) -> Result<()> { | ||||
|     log_info("Copying webring…"); | ||||
|     copy_dir_recursive( | ||||
|         ctx.source_dir.join("webring"), | ||||
|         ctx.output_dir.join("webring"), | ||||
|     )?; | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
| use crate::*; | ||||
| 
 | ||||
| use ssri::{Algorithm, IntegrityOpts}; | ||||
| 
 | ||||
| pub fn copy_webring(ctx: &BuildContext) -> Result<()> { | ||||
|     log_info("Copying webring…"); | ||||
|     copy_dir_recursive( | ||||
|         ctx.source_dir.join("webring"), | ||||
|         ctx.output_dir.join("webring"), | ||||
|     )?; | ||||
| 
 | ||||
|     log_info("Calculating webring integrity…"); | ||||
|     let webring_content = ctx.read_bin("webring/webring-0.2.0.js")?; | ||||
|     let integrity = IntegrityOpts::new() | ||||
|         .algorithm(Algorithm::Sha512) | ||||
|         .algorithm(Algorithm::Sha1) | ||||
|         .chain(&webring_content) | ||||
|         .result(); | ||||
| 
 | ||||
|     ctx.write("webring/webring-0.2.0.js.integ.txt", integrity.to_string())?; | ||||
| 
 | ||||
|     Ok(()) | ||||
| } | ||||
|  |  | |||
							
								
								
									
										11
									
								
								nginx-site.conf
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								nginx-site.conf
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,11 @@ | |||
| # assuming the dist repo lives at /srv/http/lavender.software | ||||
| 
 | ||||
| location / { | ||||
|   root /srv/http/lavender.software/; | ||||
| 
 | ||||
|   location = /webring/data.json { | ||||
|     add_header Access-Control-Allow-Origin *; | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| error_page 404 /404.html | ||||
|  | @ -1,3 +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); | ||||
| /* 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); | ||||
|  |  | |||
|  | @ -1,88 +1,88 @@ | |||
| :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); | ||||
| } | ||||
| 
 | ||||
| html, | ||||
| body { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   min-height: 100vh; | ||||
| } | ||||
| 
 | ||||
| header, | ||||
| main, | ||||
| footer { | ||||
|   width: 100%; | ||||
|   max-width: 90ch; | ||||
|   margin: 0 auto; | ||||
|   padding: 1em; | ||||
| } | ||||
| 
 | ||||
| main { | ||||
|   flex: 1; | ||||
|   margin-bottom: 2em; | ||||
| } | ||||
| 
 | ||||
| header { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
| } | ||||
| 
 | ||||
| header img { | ||||
|   display: inline-block; | ||||
|   margin-right: 2em; | ||||
|   height: 8em; | ||||
| } | ||||
| 
 | ||||
| #purple { | ||||
|   font-style: normal; | ||||
|   color: var(--bg); | ||||
|   background-color: var(--accent); | ||||
|   border-radius: 2px; | ||||
|   padding: 0.25em; | ||||
| } | ||||
| 
 | ||||
| li { | ||||
|   line-height: 1.6em; | ||||
| } | ||||
| 
 | ||||
| footer nav ul { | ||||
|   list-style: none; | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| footer nav ul > li { | ||||
|   display: inline; | ||||
|   line-height: 1rem; | ||||
|   padding-bottom: 0.15em; | ||||
| } | ||||
| 
 | ||||
| footer nav ul > li + li { | ||||
|   border-inline-start: 1px solid var(--fg); | ||||
| 
 | ||||
|   padding-inline-start: 1ch; | ||||
|   margin-inline-start: 1ch; | ||||
| } | ||||
| :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); | ||||
| } | ||||
| 
 | ||||
| html, | ||||
| body { | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| body { | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
|   min-height: 100vh; | ||||
| } | ||||
| 
 | ||||
| header, | ||||
| main, | ||||
| footer { | ||||
|   width: 100%; | ||||
|   max-width: 90ch; | ||||
|   margin: 0 auto; | ||||
|   padding: 1em; | ||||
| } | ||||
| 
 | ||||
| main { | ||||
|   flex: 1; | ||||
|   margin-bottom: 2em; | ||||
| } | ||||
| 
 | ||||
| header { | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
| } | ||||
| 
 | ||||
| header img { | ||||
|   display: inline-block; | ||||
|   margin-right: 2em; | ||||
|   height: 8em; | ||||
| } | ||||
| 
 | ||||
| #purple { | ||||
|   font-style: normal; | ||||
|   color: var(--bg); | ||||
|   background-color: var(--accent); | ||||
|   border-radius: 2px; | ||||
|   padding: 0.25em; | ||||
| } | ||||
| 
 | ||||
| li { | ||||
|   line-height: 1.6em; | ||||
| } | ||||
| 
 | ||||
| footer nav ul { | ||||
|   list-style: none; | ||||
|   display: flex; | ||||
|   flex-direction: row; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| footer nav ul > li { | ||||
|   display: inline; | ||||
|   line-height: 1rem; | ||||
|   padding-bottom: 0.15em; | ||||
| } | ||||
| 
 | ||||
| footer nav ul > li + li { | ||||
|   border-inline-start: 1px solid var(--fg); | ||||
| 
 | ||||
|   padding-inline-start: 1ch; | ||||
|   margin-inline-start: 1ch; | ||||
| } | ||||
|  |  | |||
|  | @ -1,17 +1,37 @@ | |||
| [ | ||||
|     { | ||||
|         "id": "annie", | ||||
|         "name": "annieversary", | ||||
|         "url": "https://versary.town/" | ||||
|     }, | ||||
|     { | ||||
|         "id": "charlotte", | ||||
|         "name": "charlotte", | ||||
|         "url": "https://char.lt/" | ||||
|     }, | ||||
|     { | ||||
|         "id": "mira", | ||||
|         "name": "mira", | ||||
|         "url": "https://boxin.space/" | ||||
|     } | ||||
| ] | ||||
| [ | ||||
|   { | ||||
|     "id": "annie", | ||||
|     "name": "annieversary", | ||||
|     "url": "https://versary.town/" | ||||
|   }, | ||||
|   { | ||||
|     "id": "charlotte", | ||||
|     "name": "charlotte", | ||||
|     "url": "https://char.lt/" | ||||
|   }, | ||||
|   { | ||||
|     "id": "milse113", | ||||
|     "name": "milse113", | ||||
|     "url": "https://milse113.github.io/lavender.html" | ||||
|   }, | ||||
|   { | ||||
|     "id": "mira", | ||||
|     "name": "mira", | ||||
|     "url": "https://boxin.space/" | ||||
|   }, | ||||
|   { | ||||
|     "id": "oatmealine", | ||||
|     "name": "oatmealine", | ||||
|     "url": "https://oat.zone/" | ||||
|   }, | ||||
|   { | ||||
|     "id": "maia", | ||||
|     "name": "maia", | ||||
|     "url": "https://maia.crimew.gay/" | ||||
|   }, | ||||
|   { | ||||
|     "id": "easrng", | ||||
|     "name": "easrng", | ||||
|     "url": "https://easrng.net/" | ||||
|   } | ||||
| ] | ||||
|  |  | |||
							
								
								
									
										162
									
								
								src/webring/webring-0.1.0.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								src/webring/webring-0.1.0.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,162 @@ | |||
| (function () { | ||||
|   function getUID() { | ||||
|     var array = new Uint8Array(8); | ||||
|     window.crypto.getRandomValues(array); | ||||
|     return Array.from(array) | ||||
|       .map((b) => b.toString(16).padStart(2, "0")) | ||||
|       .join(""); | ||||
|   } | ||||
| 
 | ||||
|   const params = document.currentScript.dataset; | ||||
|   const UID = getUID(); | ||||
| 
 | ||||
|   const css = ` | ||||
| #--lavender-cssreset-${UID} { | ||||
| 	all: unset; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} { | ||||
| 	background-color: rgb(28, 23, 36); | ||||
| 	background-blend-mode: multiply; | ||||
| 	position: relative; | ||||
| 
 | ||||
| 	background-color: ${params.backgroundColor || "purple"}; | ||||
| 	color: ${params.textColor || "white"}; | ||||
| 	text-shadow: 0px 1px 1px ${params.textShadowColor || "black"}; | ||||
| 	padding: 1px; | ||||
| 	font-size: 16px; | ||||
| 	font-family: sans-serif; | ||||
| 
 | ||||
| 	margin: 10px 0px; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} a { | ||||
| 	color: inherit !important; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID}::before { | ||||
| 	display: block; | ||||
| 	content: ' '; | ||||
| 	top: 0; left: 0; bottom: 0; right: 0; | ||||
| 	position: absolute; | ||||
| 	box-sizing: border-box; | ||||
| 	pointer-events: none; | ||||
| 	border: 5px outset; | ||||
| 	opacity: 0.25; | ||||
| 	border-color: white black black white; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-title-${UID} { | ||||
| 	margin: 10px 10px 0px; | ||||
| 	text-align: center; | ||||
| 	font-style: italic; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-item-container-${UID} { | ||||
| 	display: flex; | ||||
| 	justify-content: space-around; | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 600px) { | ||||
| 	#--lavender-webring-container-${UID} #--lavender-webring-item-container-${UID} { | ||||
| 		flex-direction: column; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} .--lavender-webring-items-${UID} { | ||||
| 	font-size: 14px; | ||||
| 	display: block; | ||||
| 	text-align: center; | ||||
| 	padding: 0px; | ||||
| 	margin: 10px 10px 8px; | ||||
| 	flex: 1; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-item-1-${UID}::before { | ||||
| 	display: inline; | ||||
| 	content: '\\2190\\00a0'; | ||||
| } | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-item-3-${UID}::after { | ||||
| 	display: inline; | ||||
| 	content: '\\00a0\\2192'; | ||||
| } | ||||
| `;
 | ||||
| 
 | ||||
|   const webring_content = ` | ||||
| <div id="--lavender-cssreset-${UID}"> | ||||
| <div id="--lavender-webring-container-${UID}"> | ||||
| 	<p id="--lavender-webring-title-${UID}">This website is a part of the Lavender Software webring</p> | ||||
| 	<div id="--lavender-webring-item-container-${UID}"> | ||||
| 		<a class="--lavender-webring-items-${UID}" id="--lavender-webring-item-1-${UID}"></a> | ||||
| 		<a class="--lavender-webring-items-${UID}" id="--lavender-webring-item-2-${UID}"></a> | ||||
| 		<a class="--lavender-webring-items-${UID}" id="--lavender-webring-item-3-${UID}"></a> | ||||
| 	</div> | ||||
| </div> | ||||
| </div> | ||||
| `;
 | ||||
| 
 | ||||
|   function renderIdx(data, element, idx) { | ||||
|     idx = (idx + data.length) % data.length; | ||||
| 
 | ||||
|     datum = data[idx]; | ||||
|     element.textContent = datum.name; | ||||
|     element.href = datum.url; | ||||
|   } | ||||
| 
 | ||||
|   function getElementByPostfixedId(path) { | ||||
|     return document.getElementById(path + "-" + UID); | ||||
|   } | ||||
| 
 | ||||
|   function renderContent(currentScript, data) { | ||||
|     const params = currentScript.dataset; | ||||
| 
 | ||||
|     var headstyle = document.createElement("style"); | ||||
|     headstyle.innerHTML = css; | ||||
|     document.head.appendChild(headstyle); | ||||
| 
 | ||||
|     currentScript.insertAdjacentHTML("afterend", webring_content); | ||||
| 
 | ||||
|     const container = getElementByPostfixedId("--lavender-webring-container"); | ||||
| 
 | ||||
|     const item1 = getElementByPostfixedId("--lavender-webring-item-1"); | ||||
|     const item2 = getElementByPostfixedId("--lavender-webring-item-2"); | ||||
|     const item3 = getElementByPostfixedId("--lavender-webring-item-3"); | ||||
| 
 | ||||
|     const id = params.siteId; | ||||
|     var idindex = -1; | ||||
| 
 | ||||
|     for (var i = 0; i < data.length; i++) { | ||||
|       if (data[i].id == id) { | ||||
|         idindex = i; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (idindex == -1) { | ||||
|       item2.textContent = | ||||
|         "this site was not found in the list. please check that you don't have any typos in the id!"; | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     if ("hideTitle" in params) { | ||||
|       getElementByPostfixedId("--lavender-webring-title").style.display = | ||||
|         "none"; | ||||
|     } | ||||
| 
 | ||||
|     renderIdx(data, item1, idindex - 1); | ||||
|     renderIdx(data, item2, idindex); | ||||
|     renderIdx(data, item3, idindex + 1); | ||||
|   } | ||||
| 
 | ||||
|   const currentScript = document.currentScript; | ||||
|   if (currentScript) { | ||||
|     fetch("https://lavender.software/webring/data.json") | ||||
|       .then(function (response) { | ||||
|         return response.json(); | ||||
|       }) | ||||
|       .then(function (data) { | ||||
|         renderContent(currentScript, data); | ||||
|       }); | ||||
|   } else { | ||||
|     console.log("cannot locate document.currentScript element. aborting..."); | ||||
|   } | ||||
| })(); | ||||
							
								
								
									
										52
									
								
								src/webring/webring-0.2.0.css
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								src/webring/webring-0.2.0.css
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,52 @@ | |||
| /* Feel free to style the webring however you like! */ | ||||
| 
 | ||||
| .lavender-webring-container { | ||||
|   all: unset; | ||||
| 
 | ||||
|   /* | ||||
|     assuming Linux users will have a preferable sans-serif font set in their browser, | ||||
|     everyone else gets a nice default | ||||
|   */ | ||||
|   font-family: -apple-system, BlinkMacSystemFont, "SF Pro", "Segoe UI", | ||||
|     "Helvetica Neue", sans-serif; | ||||
| 
 | ||||
|   display: flex; | ||||
|   flex-direction: column; | ||||
| 
 | ||||
|   background-color: rgb(28, 23, 36); | ||||
|   color: rgb(234, 234, 248); | ||||
| 
 | ||||
|   padding: 1em; | ||||
| 
 | ||||
|   text-align: center; | ||||
|   font-size: 1.125rem; | ||||
| } | ||||
| 
 | ||||
| .lavender-webring-description { | ||||
|   margin-block-end: 0.5em; | ||||
| } | ||||
| 
 | ||||
| .lavender-webring-container a { | ||||
|   color: hsl(275, 57%, 68%); | ||||
|   text-decoration: none; | ||||
|   border-bottom: 1px solid hsl(275, 57%, 68%); | ||||
| } | ||||
| 
 | ||||
| .lavender-webring-site-links { | ||||
|   display: grid; | ||||
|   grid-template-columns: repeat(3, minmax(0, 1fr)); | ||||
| 
 | ||||
|   list-style: none; | ||||
|   margin: 0; | ||||
|   padding: 0; | ||||
| } | ||||
| 
 | ||||
| .lavender-webring-prev-site a::before { | ||||
|   content: "←"; | ||||
|   margin-inline-end: 1ch; | ||||
| } | ||||
| 
 | ||||
| .lavender-webring-next-site a::after { | ||||
|   content: "→"; | ||||
|   margin-inline-start: 1ch; | ||||
| } | ||||
							
								
								
									
										54
									
								
								src/webring/webring-0.2.0.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/webring/webring-0.2.0.js
									
									
									
									
									
										Normal file
									
								
							|  | @ -0,0 +1,54 @@ | |||
| const currentScript = document.currentScript; | ||||
| const ctx = currentScript.dataset; | ||||
| 
 | ||||
| // charlotte's ad-hoc terse javascript framework!
 | ||||
| const CSS_PREFIX = "lavender-webring"; | ||||
| const e = (tag, props = {}, children = []) => { | ||||
|   let element = Object.assign(document.createElement(tag), props); | ||||
|   element.append(...children); | ||||
|   return element; | ||||
| }; | ||||
| const t = (text) => document.createTextNode(text); | ||||
| const c = (className) => ({ className: `${CSS_PREFIX}-${className}` }); | ||||
| const h = (href) => ({ href }); | ||||
| 
 | ||||
| const createDescriptionContent = () => | ||||
|   ctx.description != null | ||||
|     ? [t(ctx.description)] | ||||
|     : [ | ||||
|         t("This site is part of the "), | ||||
|         e("a", h("https://lavender.software"), [t("lavender.software")]), | ||||
|         t(" webring!"), | ||||
|       ]; | ||||
| 
 | ||||
| const renderWebring = (currSite, prevSite, nextSite) => { | ||||
|   currentScript.replaceWith( | ||||
|     e("aside", c("container"), [ | ||||
|       e("section", c("description"), createDescriptionContent()), | ||||
|       e("ul", c("site-links"), [ | ||||
|         e("li", c("prev-site"), [e("a", h(prevSite.url), [t(prevSite.name)])]), | ||||
|         e("li", c("curr-site"), [e("a", h(currSite.url), [t(currSite.name)])]), | ||||
|         e("li", c("next-site"), [e("a", h(nextSite.url), [t(nextSite.name)])]), | ||||
|       ]), | ||||
|     ]) | ||||
|   ); | ||||
| }; | ||||
| 
 | ||||
| (async () => { | ||||
|   const data = await fetch("https://lavender.software/webring/data.json").then( | ||||
|     (r) => r.json() | ||||
|   ); | ||||
| 
 | ||||
|   let thisSiteIdx = data.findIndex((site) => site.id == ctx.siteId); | ||||
|   if (thisSiteIdx === -1) { | ||||
|     throw new Error( | ||||
|       `Could not find site by id '${ctx.siteId}' in the webring!` | ||||
|     ); | ||||
|   } | ||||
| 
 | ||||
|   let currSite = data[thisSiteIdx]; | ||||
|   let prevSite = data[(thisSiteIdx + data.length - 1) % data.length]; | ||||
|   let nextSite = data[(thisSiteIdx + 1) % data.length]; | ||||
| 
 | ||||
|   renderWebring(currSite, prevSite, nextSite); | ||||
| })(); | ||||
|  | @ -1,157 +0,0 @@ | |||
| (function() { | ||||
|     function getUID() { | ||||
|         var array = new Uint8Array(8); | ||||
|         window.crypto.getRandomValues(array); | ||||
|         return Array.from(array).map(b => b.toString(16).padStart(2, "0")).join(""); | ||||
|     } | ||||
| 
 | ||||
|     const params = document.currentScript.dataset; | ||||
|     const UID = getUID(); | ||||
| 
 | ||||
|     const css = ` | ||||
| #--lavender-cssreset-${UID} { | ||||
| 	all: unset; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} { | ||||
| 	background-color: rgb(28, 23, 36); | ||||
| 	background-blend-mode: multiply; | ||||
| 	position: relative; | ||||
| 
 | ||||
| 	background-color: ${params.backgroundColor || "purple"}; | ||||
| 	color: ${params.textColor || "white"}; | ||||
| 	text-shadow: 0px 1px 1px ${params.textShadowColor || "black"}; | ||||
| 	padding: 1px; | ||||
| 	font-size: 16px; | ||||
| 	font-family: sans-serif; | ||||
| 
 | ||||
| 	margin: 10px 0px; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} a { | ||||
| 	color: inherit !important; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID}::before { | ||||
| 	display: block; | ||||
| 	content: ' '; | ||||
| 	top: 0; left: 0; bottom: 0; right: 0; | ||||
| 	position: absolute; | ||||
| 	box-sizing: border-box; | ||||
| 	pointer-events: none; | ||||
| 	border: 5px outset; | ||||
| 	opacity: 0.25; | ||||
| 	border-color: white black black white; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-title-${UID} { | ||||
| 	margin: 10px 10px 0px; | ||||
| 	text-align: center; | ||||
| 	font-style: italic; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-item-container-${UID} { | ||||
| 	display: flex; | ||||
| 	justify-content: space-around; | ||||
| } | ||||
| 
 | ||||
| @media (max-width: 600px) { | ||||
| 	#--lavender-webring-container-${UID} #--lavender-webring-item-container-${UID} { | ||||
| 		flex-direction: column; | ||||
| 	} | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} .--lavender-webring-items-${UID} { | ||||
| 	font-size: 14px; | ||||
| 	display: block; | ||||
| 	text-align: center; | ||||
| 	padding: 0px; | ||||
| 	margin: 10px 10px 8px; | ||||
| 	flex: 1; | ||||
| } | ||||
| 
 | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-item-1-${UID}::before { | ||||
| 	display: inline; | ||||
| 	content: '\\2190\\00a0'; | ||||
| } | ||||
| #--lavender-webring-container-${UID} #--lavender-webring-item-3-${UID}::after { | ||||
| 	display: inline; | ||||
| 	content: '\\00a0\\2192'; | ||||
| } | ||||
| `;
 | ||||
| 
 | ||||
|     const webring_content = ` | ||||
| <div id="--lavender-cssreset-${UID}"> | ||||
| <div id="--lavender-webring-container-${UID}"> | ||||
| 	<p id="--lavender-webring-title-${UID}">This website is a part of the Lavender Software webring</p> | ||||
| 	<div id="--lavender-webring-item-container-${UID}"> | ||||
| 		<a class="--lavender-webring-items-${UID}" id="--lavender-webring-item-1-${UID}"></a> | ||||
| 		<a class="--lavender-webring-items-${UID}" id="--lavender-webring-item-2-${UID}"></a> | ||||
| 		<a class="--lavender-webring-items-${UID}" id="--lavender-webring-item-3-${UID}"></a> | ||||
| 	</div> | ||||
| </div> | ||||
| </div> | ||||
| `;
 | ||||
| 
 | ||||
|     function renderIdx(data, element, idx) { | ||||
|         idx = (idx + data.length) % data.length; | ||||
| 
 | ||||
|         datum = data[idx]; | ||||
|         element.textContent = datum.name; | ||||
|         element.href = datum.url; | ||||
|     } | ||||
| 
 | ||||
|     function getElementByPostfixedId(path) { | ||||
|         return document.getElementById(path + "-" + UID); | ||||
|     } | ||||
| 
 | ||||
|     function renderContent(currentScript, data) { | ||||
|         const params = currentScript.dataset; | ||||
| 
 | ||||
|         var headstyle = document.createElement('style'); | ||||
|         headstyle.innerHTML = css; | ||||
|         document.head.appendChild(headstyle); | ||||
| 
 | ||||
|         currentScript.insertAdjacentHTML("afterend", webring_content); | ||||
| 
 | ||||
|         const container = getElementByPostfixedId('--lavender-webring-container'); | ||||
| 
 | ||||
|         const item1 = getElementByPostfixedId('--lavender-webring-item-1'); | ||||
|         const item2 = getElementByPostfixedId('--lavender-webring-item-2'); | ||||
|         const item3 = getElementByPostfixedId('--lavender-webring-item-3'); | ||||
| 
 | ||||
|         const id = params.siteId; | ||||
|         var idindex = -1; | ||||
| 
 | ||||
|         for (var i = 0; i < data.length; i++) { | ||||
|             if (data[i].id == id) { | ||||
|                 idindex = i; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (idindex == -1) { | ||||
|             item2.textContent = "this site was not found in the list. please check that you don't have any typos in the id!"; | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         if ("hideTitle" in params) { | ||||
|             getElementByPostfixedId('--lavender-webring-title').style.display = 'none'; | ||||
|         } | ||||
| 
 | ||||
|         renderIdx(data, item1, idindex - 1); | ||||
|         renderIdx(data, item2, idindex); | ||||
|         renderIdx(data, item3, idindex + 1); | ||||
|     } | ||||
| 
 | ||||
|     const currentScript = document.currentScript; | ||||
|     if (currentScript) { | ||||
|       fetch("https://lavender.software/webring/data.json").then(function(response) { | ||||
|             return response.json(); | ||||
|         }).then(function(data) { | ||||
|             renderContent(currentScript, data); | ||||
|         }); | ||||
|     } else { | ||||
|         console.log("cannot locate document.currentScript element. aborting..."); | ||||
|     } | ||||
| 
 | ||||
| })(); | ||||
|  | @ -1,9 +1,9 @@ | |||
| <footer> | ||||
|   <nav> | ||||
|     <ul> | ||||
|       <li><strong class="brand">lavender software ltd</strong></li> | ||||
|       <li><a href="https://git.lavender.software/lavender/lavender.software">source code</a></li> | ||||
|       <!-- <li><a href="/about/">about us</a></li> --> | ||||
|     </ul> | ||||
|   </nav> | ||||
| </footer> | ||||
| <footer> | ||||
|   <nav> | ||||
|     <ul> | ||||
|       <li><strong class="brand">lavender software ltd</strong></li> | ||||
|       <li><a href="https://git.lavender.software/lavender/lavender.software">source code</a></li> | ||||
|       <!-- <li><a href="/about/">about us</a></li> --> | ||||
|     </ul> | ||||
|   </nav> | ||||
| </footer> | ||||
|  |  | |||
|  | @ -1,62 +1,52 @@ | |||
| <!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.svg"> | ||||
| 
 | ||||
|       <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> | ||||
|           <li>more soon!</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> --> | ||||
| 
 | ||||
|       <section> | ||||
|         <h2>services</h2> | ||||
| 
 | ||||
|         <p>we offer:</p> | ||||
| 
 | ||||
|         <ul> | ||||
|           <li>individual software consulting</li> | ||||
|           <li>system operations for-hire</li> | ||||
|           <li>contractual project work</li> | ||||
|         </ul> | ||||
|       </section> | ||||
|     </main> | ||||
| 
 | ||||
|     {% include "_footer.html.j2" %} | ||||
| 
 | ||||
|     <script async src="/assets/main_page/confetti.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
| <!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.svg"> | ||||
| 
 | ||||
|       <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> | ||||
|           <li><a href="https://git.lavender.software/lavender/watch-party">watch-party</a> - a webapp to allow for synced video playback among friends</li> | ||||
|           <li>catsette <em>(upcoming)</em> - an independent music marketplace platform for artists</li> | ||||
|           <li>Hermes <em>(upcoming)</em> - a native <a href="https://scuttlebutt.nz/">Secure Scuttlebutt</a> client for Linux</li> | ||||
|           <li>… and more soon!</li> | ||||
|         </ul> | ||||
|       </section> | ||||
| 
 | ||||
|       <section> | ||||
|         <h2>services</h2> | ||||
| 
 | ||||
|         <p>we offer:</p> | ||||
| 
 | ||||
|         <ul> | ||||
|           <li>individual software consulting</li> | ||||
|           <li>system operations for-hire</li> | ||||
|           <li>contractual project work</li> | ||||
|         </ul> | ||||
|       </section> | ||||
|     </main> | ||||
| 
 | ||||
|     {% include "_footer.html.j2" %} | ||||
| 
 | ||||
|     <script async src="/assets/main_page/confetti.js"></script> | ||||
|   </body> | ||||
| </html> | ||||
|  |  | |||
		Loading…
	
		Reference in a new issue