improve some things
1
.gitignore
vendored
|
@ -1,3 +1,4 @@
|
|||
target
|
||||
.idea
|
||||
assets/index.css
|
||||
services.toml
|
||||
|
|
816
Cargo.lock
generated
|
@ -10,12 +10,13 @@ publish = false
|
|||
|
||||
[dependencies]
|
||||
axum = "0.7.4"
|
||||
# fluent = "0.16.0"
|
||||
minify-html = "0.15.0"
|
||||
minijinja = { version = "1.0.12", features = ["loader"] }
|
||||
serde = { version = "1.0.197", features = ["derive"] }
|
||||
config = { version = "0.14.0", features = ["toml"] }
|
||||
tokio = { version = "1.36.0", features = ["full"] }
|
||||
toml = "0.8.10"
|
||||
tower-http = { version = "0.5.1", features = ["fs", "timeout", "trace"] }
|
||||
tracing = "0.1.40"
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
tracing-subscriber = { version = "0.3.18", features = ["env-filter"] }
|
||||
serde_derive = "1.0.199"
|
||||
serde = "1.0.199"
|
||||
|
|
27
Dockerfile
|
@ -1,8 +1,8 @@
|
|||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable as build_amd64
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable as build_arm64
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:x86_64-musl-stable AS build_amd64
|
||||
FROM --platform=$BUILDPLATFORM ghcr.io/blackdex/rust-musl:aarch64-musl-stable AS build_arm64
|
||||
|
||||
# Build image
|
||||
FROM --platform=$BUILDPLATFORM build_${TARGETARCH} as build
|
||||
FROM --platform=$BUILDPLATFORM build_${TARGETARCH} AS build
|
||||
|
||||
ARG TARGETARCH
|
||||
ARG TARGETVARIANT
|
||||
|
@ -21,23 +21,26 @@ RUN source /env-cargo && \
|
|||
rustup target add "${CARGO_TARGET}"
|
||||
|
||||
COPY Cargo.* ./
|
||||
|
||||
|
||||
# Dummy build to cache dependencies
|
||||
RUN source /env-cargo && \
|
||||
cargo build --release --target "${CARGO_TARGET}"
|
||||
|
||||
RUN curl -Lo tailwindcss https://github.com/tailwindlabs/tailwindcss/releases/latest/download/tailwindcss-linux-x64 && \
|
||||
chmod +x tailwindcss
|
||||
|
||||
|
||||
COPY src ./src
|
||||
|
||||
COPY templates ./templates
|
||||
COPY config.toml ./config.toml
|
||||
|
||||
# Build the actual app
|
||||
RUN touch src/main.rs && \
|
||||
source /env-cargo && \
|
||||
cargo install --path . --target "${CARGO_TARGET}"
|
||||
|
||||
# Required for Tailwind to know what classes are used
|
||||
COPY templates ./templates
|
||||
|
||||
COPY tailwind.* ./
|
||||
|
||||
RUN ./tailwindcss -i tailwind.css -o index.css --minify
|
||||
|
@ -45,27 +48,27 @@ RUN ./tailwindcss -i tailwind.css -o index.css --minify
|
|||
|
||||
|
||||
# Runtime image
|
||||
FROM docker.io/alpine
|
||||
FROM docker.io/alpine:latest
|
||||
|
||||
WORKDIR /homepage
|
||||
WORKDIR /etc/homepage
|
||||
|
||||
ARG GID=8686
|
||||
ARG UID=8686
|
||||
|
||||
RUN addgroup -g ${GID} homepage && \
|
||||
adduser -u ${UID} -D -H -G homepage homepage
|
||||
adduser -u ${UID} -D -H -G homepage homepage
|
||||
|
||||
USER homepage:homepage
|
||||
|
||||
EXPOSE 8080
|
||||
EXPOSE 8686
|
||||
|
||||
COPY --from=build /root/.cargo/bin/homepage /usr/local/bin/homepage
|
||||
|
||||
COPY templates ./templates
|
||||
|
||||
COPY assets ./assets
|
||||
|
||||
# Copy CSS generated by Tailwind
|
||||
COPY --from=build /usr/src/homepage/index.css assets/
|
||||
|
||||
COPY services.toml .
|
||||
|
||||
ENTRYPOINT ["homepage"]
|
||||
|
|
44
README.md
|
@ -1,3 +1,45 @@
|
|||
# homepage
|
||||
|
||||
Source for my [homepage](https://viyurz.fr/), built with Axum & Tailwind (CSS gives me nightmares).
|
||||
Source for my [homepage](https://viyurz.fr/), built with Axum & Tailwind (CSS gives me nightmares).
|
||||
|
||||
## Configuration
|
||||
|
||||
Change the default configuration by creating a file at `/etc/homepage/config.toml`
|
||||
or setting the environment variable `HP_CONFIG_FILE` if using another path.
|
||||
|
||||
Configuration options can also be set using environment variables by
|
||||
preceding the variable name by `HP_` (ex. `HP_LISTEN_ADDRESS`).
|
||||
|
||||
## Example service
|
||||
|
||||
```
|
||||
services.toml
|
||||
|
||||
[[services]]
|
||||
name = "Vaultwarden"
|
||||
description = "Rust rewrite of the Bitwarden server, a password management service."
|
||||
domain = "vw.viyurz.fr"
|
||||
language = "Rust"
|
||||
repository_url = "https://github.com/dani-garcia/vaultwarden"
|
||||
```
|
||||
|
||||
## Create & push multi-platform image
|
||||
|
||||
Create a builder that use the `docker-container`` driver, which supports multi-platform builds:
|
||||
|
||||
```
|
||||
docker buildx create --name multiarch --bootstrap
|
||||
```
|
||||
|
||||
Build the multi-platform image using this new builder:
|
||||
|
||||
```
|
||||
docker buildx build --builder multiarch --load --platform linux/amd64,linux/arm64 --tag git.ahur.ac/viyurz/homepage:latest .
|
||||
```
|
||||
|
||||
Publish the image:
|
||||
|
||||
```
|
||||
docker login git.ahur.ac
|
||||
docker push git.ahur.ac/viyurz/homepage:latest
|
||||
```
|
||||
|
|
9
assets/fa/all.min.css
vendored
Normal file
BIN
assets/fa/fa-brands-400.woff2
Normal file
BIN
assets/fa/fa-solid-900.woff2
Normal file
Before Width: | Height: | Size: 978 B |
BIN
assets/favicon-180x180.png
Normal file
After Width: | Height: | Size: 7.1 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 2.7 KiB |
Before Width: | Height: | Size: 34 KiB |
BIN
assets/favicon.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
assets/git.png
Before Width: | Height: | Size: 1.9 KiB |
Before Width: | Height: | Size: 4.1 KiB After Width: | Height: | Size: 4.3 KiB |
BIN
assets/images/element.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 5.5 KiB After Width: | Height: | Size: 5.5 KiB |
BIN
assets/images/etebase.png
Normal file
After Width: | Height: | Size: 2.3 KiB |
Before Width: | Height: | Size: 9.3 KiB After Width: | Height: | Size: 9.3 KiB |
BIN
assets/images/hedgedoc.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
assets/images/matrix-dark.png
Normal file
After Width: | Height: | Size: 2 KiB |
BIN
assets/images/matrix.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/images/searxng-dark.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/images/searxng.png
Normal file
After Width: | Height: | Size: 2.7 KiB |
BIN
assets/images/stalwart mail server-dark.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
BIN
assets/images/stalwart mail server.png
Normal file
After Width: | Height: | Size: 5.3 KiB |
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 10 KiB |
BIN
assets/images/stump.png
Normal file
After Width: | Height: | Size: 2.9 KiB |
Before Width: | Height: | Size: 3.2 KiB After Width: | Height: | Size: 3.2 KiB |
BIN
assets/images/vaultwarden.png
Normal file
After Width: | Height: | Size: 3.1 KiB |
BIN
assets/logo.jpg
Before Width: | Height: | Size: 230 KiB After Width: | Height: | Size: 43 KiB |
BIN
assets/logo.png
Normal file
After Width: | Height: | Size: 22 KiB |
Before Width: | Height: | Size: 12 KiB |
Before Width: | Height: | Size: 2.4 KiB |
Before Width: | Height: | Size: 6.1 KiB |
Before Width: | Height: | Size: 2 KiB |
Before Width: | Height: | Size: 576 KiB |
|
@ -1,2 +1,4 @@
|
|||
ip = "0.0.0.0"
|
||||
port = 8080
|
||||
# Default configuration interpreted at compile time
|
||||
LISTEN_ADDRESS = "0.0.0.0"
|
||||
LISTEN_PORT = "8686"
|
||||
SERVICES_FILE = "/etc/homepage/services.toml"
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
name = "Element"
|
||||
description = "Web client of Element, an instant messaging client implementing the Matrix protocol."
|
||||
domain = "element.viyurz.fr"
|
||||
image = "/assets/element.png"
|
||||
language = "TypeScript"
|
||||
repository_url = "https://github.com/element-hq/element-web"
|
||||
|
||||
|
@ -10,23 +9,13 @@ repository_url = "https://github.com/element-hq/element-web"
|
|||
name = "EteBase"
|
||||
description = "Server for EteSync, an end-to-end encrypted contacts, calendars, tasks and notes provider."
|
||||
domain = "etebase.viyurz.fr"
|
||||
image = "/assets/etesync.png"
|
||||
language = "Python"
|
||||
repository_url = "https://github.com/etesync/server"
|
||||
|
||||
[[services]]
|
||||
name = "EteSync (Soon™)"
|
||||
description = "Web client of EteSync, an end-to-end encrypted contacts, calendars, tasks and notes provider."
|
||||
domain = "etesync.viyurz.fr"
|
||||
image = "/assets/etesync.png"
|
||||
language = "TypeScript"
|
||||
repository_url = "https://github.com/etesync/etesync-web"
|
||||
|
||||
[[services]]
|
||||
name = "HedgeDoc"
|
||||
description = "A real-time collaborative markdown editor."
|
||||
domain = "hedgedoc.viyurz.fr"
|
||||
image = "/assets/hedgedoc.png"
|
||||
language = "TypeScript"
|
||||
repository_url = "https://github.com/hedgedoc/hedgedoc"
|
||||
|
||||
|
@ -34,7 +23,6 @@ repository_url = "https://github.com/hedgedoc/hedgedoc"
|
|||
name = "Matrix"
|
||||
description = "Synapse homeserver implemeting the Matrix protocol, an open standard for real-time communication supporting encryption and VoIP."
|
||||
domain = "matrix.viyurz.fr"
|
||||
image = "/assets/matrix.png"
|
||||
language = "Python"
|
||||
repository_url = "https://github.com/element-hq/synapse"
|
||||
|
||||
|
@ -42,23 +30,20 @@ repository_url = "https://github.com/element-hq/synapse"
|
|||
name = "SearXNG"
|
||||
description = "A privacy-respecting, hackable metasearch engine."
|
||||
domain = "searx.viyurz.fr"
|
||||
image = "/assets/searxng.png"
|
||||
language = "Python"
|
||||
repository_url = "https://github.com/searxng/searxng"
|
||||
|
||||
[[services]]
|
||||
name = "Stalwart Mail Server (Soon™)"
|
||||
name = "Stalwart Mail Server"
|
||||
description = "Secure & Modern All-in-One Mail Server (IMAP, JMAP, SMTP)."
|
||||
domain = "smtp.viyurz.fr"
|
||||
image = "/assets/mail-server.png"
|
||||
domain = "mail.viyurz.fr"
|
||||
language = "Rust"
|
||||
repository_url = "https://github.com/stalwartlabs/mail-server"
|
||||
|
||||
[[services]]
|
||||
name = "Stump (Soon™)"
|
||||
description = "A comics, manga and digital book server with OPDS support (WIP)."
|
||||
name = "Stump"
|
||||
description = "A comics, manga and digital book server with OPDS support."
|
||||
domain = "stump.viyurz.fr"
|
||||
image = "/assets/stump.png"
|
||||
language = "Rust / TypeScript"
|
||||
repository_url = "https://github.com/stumpapp/stump"
|
||||
|
||||
|
@ -66,6 +51,5 @@ repository_url = "https://github.com/stumpapp/stump"
|
|||
name = "Vaultwarden"
|
||||
description = "Rust rewrite of the Bitwarden server, a password management service."
|
||||
domain = "vw.viyurz.fr"
|
||||
image = "/assets/vaultwarden.png"
|
||||
language = "Rust"
|
||||
repository_url = "https://github.com/dani-garcia/vaultwarden"
|
||||
repository_url = "https://github.com/dani-garcia/vaultwarden"
|
||||
|
|
34
src/config.rs
Normal file
|
@ -0,0 +1,34 @@
|
|||
use std::env;
|
||||
|
||||
use config::{Config, Environment, File, FileFormat};
|
||||
use serde_derive::Deserialize;
|
||||
|
||||
#[derive(Debug, Default, Deserialize)]
|
||||
pub struct AppConfig {
|
||||
pub listen_address: String,
|
||||
pub listen_port: u16,
|
||||
pub services_file: String,
|
||||
}
|
||||
|
||||
pub fn get() -> AppConfig {
|
||||
// Interpreted at compile time
|
||||
static DEFAULT: &str = include_str!("../config.toml");
|
||||
|
||||
let mut config_file = String::from("/etc/homepage/config.toml");
|
||||
match env::var("HP_CONFIG_FILE") {
|
||||
Ok(val) => config_file = val,
|
||||
Err(_e) => (),
|
||||
};
|
||||
|
||||
let config: Config = Config::builder()
|
||||
// Load default configuration stored in DEFAULT.
|
||||
.add_source(File::from_str(DEFAULT, FileFormat::Toml))
|
||||
// Add configuration file.
|
||||
.add_source(File::with_name(&*config_file).required(false))
|
||||
// Add environment variables.
|
||||
.add_source(Environment::with_prefix("HP"))
|
||||
.build()
|
||||
.unwrap();
|
||||
|
||||
return config.try_deserialize().unwrap();
|
||||
}
|
33
src/handlers.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use std::sync::Arc;
|
||||
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::response::Html;
|
||||
use minijinja::context;
|
||||
|
||||
use crate::AppState;
|
||||
|
||||
pub async fn home(State(state): State<Arc<AppState>>) -> Result<Html<String>, StatusCode> {
|
||||
let template = state.env.get_template("home").unwrap();
|
||||
|
||||
let rendered = template
|
||||
.render(context! {
|
||||
title => "Home"
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(Html(rendered))
|
||||
}
|
||||
|
||||
pub async fn services(State(state): State<Arc<AppState>>) -> Result<Html<String>, StatusCode> {
|
||||
let template = state.env.get_template("services").unwrap();
|
||||
|
||||
let rendered = template
|
||||
.render(context! {
|
||||
title => "Services",
|
||||
services => state.services
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(Html(rendered))
|
||||
}
|
106
src/main.rs
|
@ -1,30 +1,24 @@
|
|||
use std::fs;
|
||||
use std::fs::{read, read_to_string};
|
||||
use std::time::Duration;
|
||||
use axum::extract::State;
|
||||
use axum::http::StatusCode;
|
||||
use axum::{response::Html, routing::get, Router};
|
||||
use minijinja::{context, Environment};
|
||||
use std::sync::Arc;
|
||||
use tower_http::services::ServeDir;
|
||||
use std::{
|
||||
fs::{read, read_dir, read_to_string},
|
||||
sync::Arc,
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use axum::{routing::get, Router};
|
||||
use minijinja::Environment;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
use tokio::signal;
|
||||
use tower_http::timeout::TimeoutLayer;
|
||||
use tower_http::trace::TraceLayer;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_http::{services::ServeDir, timeout::TimeoutLayer, trace::TraceLayer};
|
||||
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
|
||||
|
||||
mod config;
|
||||
mod handlers;
|
||||
|
||||
struct AppState {
|
||||
env: Environment<'static>,
|
||||
services: Vec<Service>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
struct Config {
|
||||
ip: Option<String>,
|
||||
port: Option<u16>,
|
||||
services: Option<Vec<Service>>,
|
||||
}
|
||||
|
||||
#[derive(Deserialize)]
|
||||
struct ServiceVec {
|
||||
services: Vec<Service>,
|
||||
|
@ -35,36 +29,32 @@ struct Service {
|
|||
name: String,
|
||||
description: String,
|
||||
domain: String,
|
||||
image: String,
|
||||
language: String,
|
||||
repository_url: String,
|
||||
}
|
||||
|
||||
fn load_config() -> Config {
|
||||
let config_str = read_to_string("config.toml").expect("Failed to read config.toml file.");
|
||||
let services_str = read_to_string("services.toml").expect("Failed to read services.toml file.");
|
||||
|
||||
let mut config: Config = toml::from_str(&config_str).unwrap();
|
||||
|
||||
let service_vec: ServiceVec = toml::from_str(&services_str).unwrap();
|
||||
config.services = Option::from(service_vec.services);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
// Minify templates before loading them
|
||||
fn load_templates() -> Environment<'static> {
|
||||
let mut env = Environment::new();
|
||||
let cfg = minify_html::Cfg::spec_compliant();
|
||||
|
||||
for template_entry in fs::read_dir("./templates").expect("Failed to read directory ./templates.") {
|
||||
for template_entry in read_dir("./templates").expect("Failed to read directory ./templates.") {
|
||||
let template_file_path = template_entry.unwrap().path();
|
||||
let template_name = template_file_path.file_stem().unwrap().to_os_string().into_string().unwrap();
|
||||
let template_name = template_file_path
|
||||
.file_stem()
|
||||
.unwrap()
|
||||
.to_os_string()
|
||||
.into_string()
|
||||
.unwrap();
|
||||
|
||||
let template_code = read(template_file_path).unwrap();
|
||||
let template_code_minified = minify_html::minify(&template_code, &cfg);
|
||||
|
||||
env.add_template_owned(template_name, String::from_utf8(template_code_minified).unwrap()).unwrap();
|
||||
env.add_template_owned(
|
||||
template_name,
|
||||
String::from_utf8(template_code_minified).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
return env;
|
||||
|
@ -72,7 +62,7 @@ fn load_templates() -> Environment<'static> {
|
|||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
let config: Config = load_config();
|
||||
let config = config::get();
|
||||
|
||||
tracing_subscriber::registry()
|
||||
.with(
|
||||
|
@ -88,12 +78,22 @@ async fn main() {
|
|||
// init template engine and add templates
|
||||
let env: Environment = load_templates();
|
||||
|
||||
// load services files
|
||||
let services_str = read_to_string(&config.services_file)
|
||||
.expect(&format!("Failed to read {}.", &config.services_file));
|
||||
let services_vec: ServiceVec = toml::from_str(&services_str).expect(&format!(
|
||||
"Failed to interpret services file {}.",
|
||||
&config.services_file
|
||||
));
|
||||
let services = services_vec.services;
|
||||
|
||||
// pass env & services to handlers via state
|
||||
let app_state = Arc::new(AppState { env, services: config.services.unwrap() });
|
||||
let app_state = Arc::new(AppState { env, services });
|
||||
|
||||
// define routes
|
||||
let app = Router::new()
|
||||
.route("/", get(handler_home))
|
||||
.route("/", get(handlers::home))
|
||||
.route("/services", get(handlers::services))
|
||||
.nest_service("/assets", ServeDir::new("assets"))
|
||||
.layer((
|
||||
TraceLayer::new_for_http(),
|
||||
|
@ -105,25 +105,15 @@ async fn main() {
|
|||
|
||||
// run it
|
||||
let listener = tokio::net::TcpListener::bind(
|
||||
config.ip.expect("Missing 'ip' config parameter.") +
|
||||
":" + &config.port.expect("Missing 'port' config parameter.").to_string())
|
||||
config.listen_address + ":" + &config.listen_port.to_string(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
println!("listening on http://{}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, app)
|
||||
.with_graceful_shutdown(shutdown_signal())
|
||||
.await
|
||||
.unwrap();
|
||||
println!("listening on {}", listener.local_addr().unwrap());
|
||||
axum::serve(listener, app).with_graceful_shutdown(shutdown_signal()).await.unwrap();
|
||||
}
|
||||
|
||||
async fn handler_home(State(state): State<Arc<AppState>>) -> Result<Html<String>, StatusCode> {
|
||||
let template = state.env.get_template("home").unwrap();
|
||||
|
||||
let rendered = template
|
||||
.render(context! {
|
||||
title => "Home",
|
||||
services => state.services
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
Ok(Html(rendered))
|
||||
}
|
||||
|
||||
async fn shutdown_signal() {
|
||||
|
@ -134,7 +124,7 @@ async fn shutdown_signal() {
|
|||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
let terminate = async {
|
||||
let terminate = async {
|
||||
signal::unix::signal(signal::unix::SignalKind::terminate())
|
||||
.expect("failed to install signal handler")
|
||||
.recv()
|
||||
|
@ -142,10 +132,10 @@ async fn shutdown_signal() {
|
|||
};
|
||||
|
||||
#[cfg(not(unix))]
|
||||
let terminate = std::future::pending::<()>();
|
||||
let terminate = std::future::pending::<()>();
|
||||
|
||||
tokio::select! {
|
||||
_ = ctrl_c => {},
|
||||
_ = terminate => {},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,16 +1,40 @@
|
|||
/** @type {import('tailwindcss').Config} */
|
||||
module.exports = {
|
||||
content: ["./templates/**/*.html"],
|
||||
theme: {
|
||||
colors: {
|
||||
transparent: 'transparent',
|
||||
current: 'currentColor',
|
||||
white: '#ffffff',
|
||||
primary: "#601237",
|
||||
background: "#290718",
|
||||
content: ["./templates/**/*.html"],
|
||||
theme: {
|
||||
extend: {
|
||||
colors: {
|
||||
transparent: "transparent",
|
||||
current: "currentColor",
|
||||
base: {
|
||||
DEFAULT: "#ffffff",
|
||||
dark: "#26010c",
|
||||
},
|
||||
surface: {
|
||||
DEFAULT: "#fffcfd",
|
||||
dark: "#4d041a",
|
||||
},
|
||||
overlay: {
|
||||
DEFAULT: "#d85079",
|
||||
dark: "#800d2f",
|
||||
},
|
||||
muted: {
|
||||
DEFAULT: "#4d3239",
|
||||
dark: "#807e7f",
|
||||
},
|
||||
subtle: {
|
||||
DEFAULT: "#331a21",
|
||||
dark: "#d9d7d7",
|
||||
},
|
||||
text: {
|
||||
DEFAULT: "#1a030a",
|
||||
dark: "#fcfafb",
|
||||
},
|
||||
orange: "#BA562A",
|
||||
rose: "#ba2a56",
|
||||
turquoise: "#2ABABA",
|
||||
},
|
||||
},
|
||||
plugins: [],
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
};
|
||||
|
|
63
tailwind.css
|
@ -3,24 +3,55 @@
|
|||
@tailwind utilities;
|
||||
|
||||
@layer base {
|
||||
h1, h2 {
|
||||
@apply p-4;
|
||||
}
|
||||
@font-face {
|
||||
font-family: "JetBrains Mono";
|
||||
src: url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff2/JetBrainsMono-Regular.woff2")
|
||||
format("woff2"),
|
||||
url("https://cdn.jsdelivr.net/gh/JetBrains/JetBrainsMono/web/woff/JetBrainsMono-Regular.woff")
|
||||
format("woff");
|
||||
font-weight: 400;
|
||||
font-style: normal;
|
||||
font-display: auto;
|
||||
}
|
||||
|
||||
h1, h2 {
|
||||
@apply font-bold;
|
||||
@apply text-center;
|
||||
}
|
||||
html {
|
||||
font-family: "JetBrains Mono", sans-serif, system-ui;
|
||||
}
|
||||
|
||||
h1 {
|
||||
@apply text-3xl;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3 {
|
||||
@apply font-bold;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
h1,
|
||||
h2 {
|
||||
@apply text-center;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-xl;
|
||||
}
|
||||
h1 {
|
||||
@apply text-2xl;
|
||||
}
|
||||
|
||||
h2 {
|
||||
@apply text-xl;
|
||||
}
|
||||
|
||||
h3 {
|
||||
@apply text-lg;
|
||||
}
|
||||
|
||||
h4 {
|
||||
@apply text-base;
|
||||
}
|
||||
|
||||
nav a,
|
||||
footer a {
|
||||
@apply hover:text-turquoise;
|
||||
@apply hover:text-opacity-75;
|
||||
}
|
||||
|
||||
.active-page {
|
||||
@apply text-turquoise;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,22 +1,33 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<title>{{ title }} - Viyurz</title>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/favicon.png">
|
||||
<link rel="icon" type="image/png" sizes="32x32" href="/assets/favicon-32x32.png">
|
||||
<link rel="icon" type="image/png" sizes="16x16" href="/assets/favicon-16x16.png">
|
||||
<meta property="og:title" content="{{ title }} - Viyurz">
|
||||
<meta property="og:type" content="website">
|
||||
<meta property="og:url" content="https://viyurz.fr">
|
||||
<meta property="og:description" content="{% block description %}Home of Viyurz.{% endblock %}">
|
||||
<meta property="og:image" content="https://viyurz.fr/assets/logo.jpg">
|
||||
<link href="/assets/index.css" rel="stylesheet">
|
||||
<link rel="apple-touch-icon" type="image/png" sizes="180x180" href="/assets/favicon-180x180.png">
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:title" content="{{ title }} - Viyurz" />
|
||||
<meta property="og:description" content="{% block description %}Viyurz's website.{% endblock %}" />
|
||||
<meta property="og:url" content="https://viyurz.fr/" />
|
||||
<meta property="og:image" content="https://viyurz.fr/assets/logo.jpg" />
|
||||
|
||||
<link rel="stylesheet" href="/assets/index.css">
|
||||
<link rel="stylesheet" href="/assets/fa/all.min.css">
|
||||
<link rel="preload" href="/assets/index.css" as="style">
|
||||
<link rel="preload" href="/assets/fa/all.min.css" as="style">
|
||||
</head>
|
||||
<body class="min-h-screen bg-background text-white">
|
||||
{% include "_navbar" %}
|
||||
<main class="flex flex-col items-center p-6">
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
|
||||
<body class="flex flex-col min-h-screen bg-base dark:bg-base-dark text-sm text-text dark:text-text-dark">
|
||||
{% include "_navbar" %}
|
||||
<main class="mb-auto flex flex-col items-center p-3 md:p-6">
|
||||
{% block main %}{% endblock %}
|
||||
</main>
|
||||
{% include "_footer" %}
|
||||
</body>
|
||||
|
||||
</html>
|
13
templates/_footer.html
Normal file
|
@ -0,0 +1,13 @@
|
|||
<footer class="flex flex-row place-content-center space-x-12 md:space-x-6 p-2 w-full bg-overlay-dark text-text-dark">
|
||||
<div class="max-md:basis-1/2 flex flex-col md:flex-row md:space-x-6 max-md:space-y-2 text-right">
|
||||
<p><i class="fa-brands fa-discord"></i> viyurz</p>
|
||||
<a href="https://www.reddit.com/user/Viyurz/" target="_blank"><i class="fa-brands fa-reddit-alien"></i>
|
||||
u/Viyurz</a>
|
||||
</div>
|
||||
<div class="max-md:basis-1/2 flex flex-col md:flex-row md:space-x-6 max-md:space-y-2 text-left">
|
||||
<a href="https://x.com/Viyurz" target="_blank"><i class="fa-brands fa-x-twitter"></i> @Viyurz</a>
|
||||
<a href="https://www.youtube.com/@Viyurz" target="_blank"><i class="fa-brands fa-youtube"></i>
|
||||
@Viyurz</a>
|
||||
<a href="mailto:viyurz@viyurz.fr"><i class="fa-solid fa-envelope"></i> viyurz@viyurz.fr</a>
|
||||
</div>
|
||||
</footer>
|
|
@ -1,19 +1,40 @@
|
|||
<nav class="flex flex-row place-content-around h-20 bg-primary px-12">
|
||||
<div class="basis-1/3 flex flex-row items-center">
|
||||
<nav class="flex flex-row place-content-around lg:px-12 md:h-14 bg-overlay-dark text-text-dark">
|
||||
<div class="basis-1/6 flex flex-row items-center">
|
||||
<a href="/" class="flex flex-row items-center p-0">
|
||||
<img src="/assets/logo.jpg" alt="logo.jpg" class="size-16 rounded-lg p-3">
|
||||
<h2>Viyurz</h2>
|
||||
<img src="/assets/logo.png" alt="logo.png" class="size-16 p-2">
|
||||
<h2 class="max-md:hidden">Viyurz</h2>
|
||||
</a>
|
||||
</div>
|
||||
<ul class="basis-1/3 flex flex-row justify-center items-center">
|
||||
<li><h2><a href="/">Home</a></h2></li>
|
||||
<li><h2><a href="https://status.viyurz.fr/">Status</a></h2></li>
|
||||
<li><h2><a href="mailto:viyurz@viyurz.fr">Contact</a></h2></li>
|
||||
</ul>
|
||||
<div class="basis-1/3 flex flex-row justify-end items-center">
|
||||
<div class="grid grid-cols-2 divide-x-2 rounded-lg bg-background p-2">
|
||||
<button class="px-2">English</button>
|
||||
<button class="opacity-50 px-2">Français</button>
|
||||
</div>
|
||||
<div
|
||||
class="basis-3/6 flex flex-col md:flex-row place-content-center place-items-center max-md:p-2 max-md:space-y-2 md:space-x-10">
|
||||
<h2><a class="{% if title == 'Home' %} active-page {% endif %}" href="/">Home</a></h2>
|
||||
<h2><a class="{% if title == 'Services' %} active-page {% endif %}" href="/services">Services</a></h2>
|
||||
<h2><a href="https://status.viyurz.fr/" target="_blank">Status</a></h2>
|
||||
<h2><a href="https://auth.viyurz.fr/" target="_blank">Account</a></h2>
|
||||
</div>
|
||||
</nav>
|
||||
<div class="basis-1/6 flex flex-row justify-end items-center">
|
||||
<div class="hidden relative inline-block opacity-10">
|
||||
<div>
|
||||
<button type="button"
|
||||
class="md:before:content-['Language'] inline-flex w-full justify-center gap-x-1.5 px-3 py-2"
|
||||
id="menu-button" aria-expanded="true" aria-haspopup="true">
|
||||
<svg class="-mr-1 h-5 w-5" viewBox="0 0 20 20" fill="currentColor" aria-hidden="true">
|
||||
<path fill-rule="evenodd"
|
||||
d="M5.23 7.21a.75.75 0 011.06.02L10 11.168l3.71-3.938a.75.75 0 111.08 1.04l-4.25 4.5a.75.75 0 01-1.08 0l-4.25-4.5a.75.75 0 01.02-1.06z"
|
||||
clip-rule="evenodd" />
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="hidden absolute right-0 z-10 mt-2 text-right origin-top-right rounded-lg border border-overlay dark:border-overlay-dark bg-surface dark:bg-surface-dark focus:outline-none"
|
||||
role="menu" aria-orientation="vertical" aria-labelledby="menu-button" tabindex="-1">
|
||||
<div class="py-1" role="none">
|
||||
<a href="#" class="text-gray-700 block px-4 py-2 text-sm" role="menuitem" tabindex="-1"
|
||||
id="menu-item-0">English</a>
|
||||
<a href="#" class="text-gray-700 block px-4 py-2 text-sm" role="menuitem" tabindex="-1"
|
||||
id="menu-item-1">Français</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</nav>
|
|
@ -1,23 +1,8 @@
|
|||
{% extends "_base" %}
|
||||
{% block main %}
|
||||
<h1>Services</h1>
|
||||
<div class="flex flex-row flex-wrap justify-center p-4">
|
||||
{% for service in services %}
|
||||
<a href="https://{{ service.domain }}/"
|
||||
class="flex flex-col basis-3/12 bg-primary rounded-lg p-4 m-4 h-60 border-transparent border-2 hover:border-white">
|
||||
<div class="flex flex-row flex-center basis-1/3">
|
||||
<img src="{{ service.image }}" alt="{{ service.name }} logo" class="w-16">
|
||||
<div class="flex flex-col justify-center ml-4">
|
||||
<h3>{{ service.name }}</h3>
|
||||
<p class="opacity-75">{{ service.domain }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 basis-1/3">{{ service.description }}</p>
|
||||
<div class="flex flex-row items-end basis-1/3">
|
||||
<button class="rounded-lg bg-background h-7 px-4 text-center"></> {{ service.language }}
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<h1>Home</h1>
|
||||
<p
|
||||
class="text-center rounded-lg p-4 m-4 bg-overlay dark:bg-overlay-dark border border-base-dark dark:border-overlay-dark">
|
||||
Personal website of someone who spends too much time watching Anime and playing video games.<br>
|
||||
Also I had nothing better to do so I built this pointless website.</p>
|
||||
{% endblock %}
|
27
templates/services.html
Normal file
|
@ -0,0 +1,27 @@
|
|||
{% extends "_base" %}
|
||||
{% block main %}
|
||||
<h1>Services</h1>
|
||||
<div class="flex flex-row flex-wrap place-content-center place-items-center">
|
||||
{% for service in services %}
|
||||
<a href="https://{{ service.domain }}/"
|
||||
class="flex flex-col basis-11/12 md:basis-5/12 xl:basis-3/12 rounded-lg p-4 m-4 h-60 hover:scale-105 transition bg-surface dark:bg-surface-dark border hover:border-2 border-base-dark dark:border-surface-dark hover:border-surface-dark dark:hover:border-surface">
|
||||
<div class="flex flex-row flex-center basis-1/3">
|
||||
<img src="/assets/images/{{ service.name | lower }}.png" alt="{{ service.name }} logo"
|
||||
class="w-16 dark:hidden">
|
||||
<img src="/assets/images/{{ service.name | lower }}-dark.png" alt="{{ service.name }} logo"
|
||||
class="w-16 hidden dark:inline-block">
|
||||
<div class="flex flex-col justify-center ml-4">
|
||||
<h3>{{ service.name }}</h3>
|
||||
<p class="text-subtle dark:text-subtle-dark">{{ service.domain }}</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="mt-4 basis-1/3">{{ service.description }}</p>
|
||||
<div class="flex flex-row items-end basis-1/3">
|
||||
<button class="rounded-lg h-7 px-4 text-center text-text-dark bg-overlay dark:bg-overlay-dark"><i
|
||||
class="fa-solid fa-code"></i> {{ service.language }}
|
||||
</button>
|
||||
</div>
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|