improve some things

This commit is contained in:
Viyurz 2024-07-02 16:21:57 +02:00
parent 194b514098
commit 1a037d78ce
Signed by: Viyurz
SSH key fingerprint: SHA256:IskOHTmhHSJIvAt04N6aaxd5SZCVWW1Guf9tEcxIMj8
49 changed files with 960 additions and 409 deletions

1
.gitignore vendored
View file

@ -1,3 +1,4 @@
target
.idea
assets/index.css
services.toml

816
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -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"

View file

@ -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"]

View file

@ -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

File diff suppressed because one or more lines are too long

Binary file not shown.

Binary file not shown.

Binary file not shown.

Before

Width:  |  Height:  |  Size: 978 B

BIN
assets/favicon-180x180.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

BIN
assets/images/element.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

Before

Width:  |  Height:  |  Size: 5.5 KiB

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
assets/images/etebase.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

View file

Before

Width:  |  Height:  |  Size: 9.3 KiB

After

Width:  |  Height:  |  Size: 9.3 KiB

BIN
assets/images/hedgedoc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
assets/images/matrix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

BIN
assets/images/searxng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

View file

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/images/stump.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

View file

Before

Width:  |  Height:  |  Size: 3.2 KiB

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 230 KiB

After

Width:  |  Height:  |  Size: 43 KiB

BIN
assets/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 576 KiB

View file

@ -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"

View file

@ -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
View 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
View 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))
}

View file

@ -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 => {},
}
}
}

View file

@ -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: [],
}
},
};

View file

@ -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;
}
}

View file

@ -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
View 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>

View file

@ -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>

View file

@ -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">&lt;&#47;&gt; {{ 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
View 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 %}