First commit.

This commit is contained in:
Viyurz 2024-02-25 13:20:06 +01:00
commit 6e56cd61f7
Signed by: Viyurz
SSH key fingerprint: SHA256:IskOHTmhHSJIvAt04N6aaxd5SZCVWW1Guf9tEcxIMj8
29 changed files with 2173 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
target
.idea

1107
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

20
Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[package]
name = "homepage"
authors = ["Viyurz <viyurz@viyurz.fr>"]
edition = "2021"
readme = "README.md"
repository = "https://git.ahur.ac/Viyurz/homepage"
license = "WTFPL"
publish = false
[dependencies]
axum = "0.7.4"
# fluent = "0.16.0"
minijinja = "1.0.12"
serde = { version = "1.0.197", features = ["derive"] }
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"] }

47
Dockerfile Normal file
View file

@ -0,0 +1,47 @@
# Build image
FROM docker.io/rust:alpine AS build
RUN apk add --no-cache musl-dev
WORKDIR /usr/src
# Create blank project
RUN cargo new homepage
WORKDIR /usr/src/homepage
COPY Cargo.* .
# Dummy build to cache dependencies
RUN cargo build --release
COPY src ./src
COPY templates ./templates
# Build the actual app
RUN touch src/main.rs && \
cargo build --release
# Runtime image
FROM docker.io/alpine
WORKDIR /homepage
ARG GID=8686
ARG UID=8686
RUN addgroup -g ${GID} homepage && \
adduser -u ${UID} -D -H -G homepage homepage
USER homepage:homepage
EXPOSE 8080
COPY --from=build /usr/src/homepage/target/release/homepage /usr/local/bin/homepage
COPY assets ./assets
ENTRYPOINT ["homepage"]

13
LICENSE.txt Normal file
View file

@ -0,0 +1,13 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.

7
README.md Normal file
View file

@ -0,0 +1,7 @@
# homepage
Source for my [homepage](https://viyurz.fr/), built with Axum & Tailwind (CSS gives me nightmares).
## Usage
Rename `services.toml.example` to `services.toml` & define your services.

BIN
assets/element.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.1 KiB

BIN
assets/etesync.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

BIN
assets/favicon-16x16.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 978 B

BIN
assets/favicon-32x32.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
assets/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

BIN
assets/git.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

BIN
assets/hedgedoc.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.3 KiB

744
assets/index.css Normal file
View file

@ -0,0 +1,744 @@
/*
! tailwindcss v3.4.1 | MIT License | https://tailwindcss.com
*/
/*
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
*/
*,
::before,
::after {
box-sizing: border-box;
/* 1 */
border-width: 0;
/* 2 */
border-style: solid;
/* 2 */
border-color: currentColor;
/* 2 */
}
::before,
::after {
--tw-content: '';
}
/*
1. Use a consistent sensible line-height in all browsers.
2. Prevent adjustments of font size after orientation changes in iOS.
3. Use a more readable tab size.
4. Use the user's configured `sans` font-family by default.
5. Use the user's configured `sans` font-feature-settings by default.
6. Use the user's configured `sans` font-variation-settings by default.
7. Disable tap highlights on iOS
*/
html,
:host {
line-height: 1.5;
/* 1 */
-webkit-text-size-adjust: 100%;
/* 2 */
-moz-tab-size: 4;
/* 3 */
-o-tab-size: 4;
tab-size: 4;
/* 3 */
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
/* 4 */
font-feature-settings: normal;
/* 5 */
font-variation-settings: normal;
/* 6 */
-webkit-tap-highlight-color: transparent;
/* 7 */
}
/*
1. Remove the margin in all browsers.
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
*/
body {
margin: 0;
/* 1 */
line-height: inherit;
/* 2 */
}
/*
1. Add the correct height in Firefox.
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
3. Ensure horizontal rules are visible by default.
*/
hr {
height: 0;
/* 1 */
color: inherit;
/* 2 */
border-top-width: 1px;
/* 3 */
}
/*
Add the correct text decoration in Chrome, Edge, and Safari.
*/
abbr:where([title]) {
-webkit-text-decoration: underline dotted;
text-decoration: underline dotted;
}
/*
Remove the default font size and weight for headings.
*/
h1,
h2,
h3,
h4,
h5,
h6 {
font-size: inherit;
font-weight: inherit;
}
/*
Reset links to optimize for opt-in styling instead of opt-out.
*/
a {
color: inherit;
text-decoration: inherit;
}
/*
Add the correct font weight in Edge and Safari.
*/
b,
strong {
font-weight: bolder;
}
/*
1. Use the user's configured `mono` font-family by default.
2. Use the user's configured `mono` font-feature-settings by default.
3. Use the user's configured `mono` font-variation-settings by default.
4. Correct the odd `em` font sizing in all browsers.
*/
code,
kbd,
samp,
pre {
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
/* 1 */
font-feature-settings: normal;
/* 2 */
font-variation-settings: normal;
/* 3 */
font-size: 1em;
/* 4 */
}
/*
Add the correct font size in all browsers.
*/
small {
font-size: 80%;
}
/*
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
*/
sub,
sup {
font-size: 75%;
line-height: 0;
position: relative;
vertical-align: baseline;
}
sub {
bottom: -0.25em;
}
sup {
top: -0.5em;
}
/*
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
3. Remove gaps between table borders by default.
*/
table {
text-indent: 0;
/* 1 */
border-color: inherit;
/* 2 */
border-collapse: collapse;
/* 3 */
}
/*
1. Change the font styles in all browsers.
2. Remove the margin in Firefox and Safari.
3. Remove default padding in all browsers.
*/
button,
input,
optgroup,
select,
textarea {
font-family: inherit;
/* 1 */
font-feature-settings: inherit;
/* 1 */
font-variation-settings: inherit;
/* 1 */
font-size: 100%;
/* 1 */
font-weight: inherit;
/* 1 */
line-height: inherit;
/* 1 */
color: inherit;
/* 1 */
margin: 0;
/* 2 */
padding: 0;
/* 3 */
}
/*
Remove the inheritance of text transform in Edge and Firefox.
*/
button,
select {
text-transform: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Remove default button styles.
*/
button,
[type='button'],
[type='reset'],
[type='submit'] {
-webkit-appearance: button;
/* 1 */
background-color: transparent;
/* 2 */
background-image: none;
/* 2 */
}
/*
Use the modern Firefox focus style for all focusable elements.
*/
:-moz-focusring {
outline: auto;
}
/*
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
*/
:-moz-ui-invalid {
box-shadow: none;
}
/*
Add the correct vertical alignment in Chrome and Firefox.
*/
progress {
vertical-align: baseline;
}
/*
Correct the cursor style of increment and decrement buttons in Safari.
*/
::-webkit-inner-spin-button,
::-webkit-outer-spin-button {
height: auto;
}
/*
1. Correct the odd appearance in Chrome and Safari.
2. Correct the outline style in Safari.
*/
[type='search'] {
-webkit-appearance: textfield;
/* 1 */
outline-offset: -2px;
/* 2 */
}
/*
Remove the inner padding in Chrome and Safari on macOS.
*/
::-webkit-search-decoration {
-webkit-appearance: none;
}
/*
1. Correct the inability to style clickable types in iOS and Safari.
2. Change font properties to `inherit` in Safari.
*/
::-webkit-file-upload-button {
-webkit-appearance: button;
/* 1 */
font: inherit;
/* 2 */
}
/*
Add the correct display in Chrome and Safari.
*/
summary {
display: list-item;
}
/*
Removes the default spacing and border for appropriate elements.
*/
blockquote,
dl,
dd,
h1,
h2,
h3,
h4,
h5,
h6,
hr,
figure,
p,
pre {
margin: 0;
}
fieldset {
margin: 0;
padding: 0;
}
legend {
padding: 0;
}
ol,
ul,
menu {
list-style: none;
margin: 0;
padding: 0;
}
/*
Reset default styling for dialogs.
*/
dialog {
padding: 0;
}
/*
Prevent resizing textareas horizontally by default.
*/
textarea {
resize: vertical;
}
/*
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
2. Set the default placeholder color to the user's configured gray 400 color.
*/
input::-moz-placeholder, textarea::-moz-placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
input::placeholder,
textarea::placeholder {
opacity: 1;
/* 1 */
color: #9ca3af;
/* 2 */
}
/*
Set the default cursor for buttons.
*/
button,
[role="button"] {
cursor: pointer;
}
/*
Make sure disabled buttons don't get the pointer cursor.
*/
:disabled {
cursor: default;
}
/*
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
This can trigger a poorly considered lint error in some tools but is included by design.
*/
img,
svg,
video,
canvas,
audio,
iframe,
embed,
object {
display: block;
/* 1 */
vertical-align: middle;
/* 2 */
}
/*
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
*/
img,
video {
max-width: 100%;
height: auto;
}
/* Make elements with the HTML hidden attribute stay hidden by default */
[hidden] {
display: none;
}
h1, h2 {
padding: 0.75rem;
font-weight: 700;
text-align: center;
}
h1 {
font-size: 1.875rem;
line-height: 2.25rem;
}
h2 {
font-size: 1.5rem;
line-height: 2rem;
}
h3 {
font-size: 1.25rem;
line-height: 1.75rem;
}
*, ::before, ::after {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
::backdrop {
--tw-border-spacing-x: 0;
--tw-border-spacing-y: 0;
--tw-translate-x: 0;
--tw-translate-y: 0;
--tw-rotate: 0;
--tw-skew-x: 0;
--tw-skew-y: 0;
--tw-scale-x: 1;
--tw-scale-y: 1;
--tw-pan-x: ;
--tw-pan-y: ;
--tw-pinch-zoom: ;
--tw-scroll-snap-strictness: proximity;
--tw-gradient-from-position: ;
--tw-gradient-via-position: ;
--tw-gradient-to-position: ;
--tw-ordinal: ;
--tw-slashed-zero: ;
--tw-numeric-figure: ;
--tw-numeric-spacing: ;
--tw-numeric-fraction: ;
--tw-ring-inset: ;
--tw-ring-offset-width: 0px;
--tw-ring-offset-color: #fff;
--tw-ring-color: rgb(59 130 246 / 0.5);
--tw-ring-offset-shadow: 0 0 #0000;
--tw-ring-shadow: 0 0 #0000;
--tw-shadow: 0 0 #0000;
--tw-shadow-colored: 0 0 #0000;
--tw-blur: ;
--tw-brightness: ;
--tw-contrast: ;
--tw-grayscale: ;
--tw-hue-rotate: ;
--tw-invert: ;
--tw-saturate: ;
--tw-sepia: ;
--tw-drop-shadow: ;
--tw-backdrop-blur: ;
--tw-backdrop-brightness: ;
--tw-backdrop-contrast: ;
--tw-backdrop-grayscale: ;
--tw-backdrop-hue-rotate: ;
--tw-backdrop-invert: ;
--tw-backdrop-opacity: ;
--tw-backdrop-saturate: ;
--tw-backdrop-sepia: ;
}
.m-4 {
margin: 1rem;
}
.ml-4 {
margin-left: 1rem;
}
.mt-4 {
margin-top: 1rem;
}
.block {
display: block;
}
.flex {
display: flex;
}
.grid {
display: grid;
}
.size-16 {
width: 4rem;
height: 4rem;
}
.h-20 {
height: 5rem;
}
.h-60 {
height: 15rem;
}
.h-7 {
height: 1.75rem;
}
.min-h-screen {
min-height: 100vh;
}
.w-16 {
width: 4rem;
}
.basis-1\/3 {
flex-basis: 33.333333%;
}
.basis-3\/12 {
flex-basis: 25%;
}
.grid-cols-2 {
grid-template-columns: repeat(2, minmax(0, 1fr));
}
.flex-row {
flex-direction: row;
}
.flex-col {
flex-direction: column;
}
.flex-wrap {
flex-wrap: wrap;
}
.place-content-around {
place-content: space-around;
}
.items-end {
align-items: flex-end;
}
.items-center {
align-items: center;
}
.justify-end {
justify-content: flex-end;
}
.justify-center {
justify-content: center;
}
.divide-x-2 > :not([hidden]) ~ :not([hidden]) {
--tw-divide-x-reverse: 0;
border-right-width: calc(2px * var(--tw-divide-x-reverse));
border-left-width: calc(2px * calc(1 - var(--tw-divide-x-reverse)));
}
.rounded-lg {
border-radius: 0.5rem;
}
.border-2 {
border-width: 2px;
}
.border-transparent {
border-color: transparent;
}
.bg-background {
--tw-bg-opacity: 1;
background-color: rgb(41 7 24 / var(--tw-bg-opacity));
}
.bg-primary {
--tw-bg-opacity: 1;
background-color: rgb(96 18 55 / var(--tw-bg-opacity));
}
.p-0 {
padding: 0px;
}
.p-2 {
padding: 0.5rem;
}
.p-3 {
padding: 0.75rem;
}
.p-4 {
padding: 1rem;
}
.p-6 {
padding: 1.5rem;
}
.px-12 {
padding-left: 3rem;
padding-right: 3rem;
}
.px-2 {
padding-left: 0.5rem;
padding-right: 0.5rem;
}
.px-4 {
padding-left: 1rem;
padding-right: 1rem;
}
.text-center {
text-align: center;
}
.text-white {
--tw-text-opacity: 1;
color: rgb(255 255 255 / var(--tw-text-opacity));
}
.opacity-50 {
opacity: 0.5;
}
.opacity-75 {
opacity: 0.75;
}
.hover\:border-white:hover {
--tw-border-opacity: 1;
border-color: rgb(255 255 255 / var(--tw-border-opacity));
}

BIN
assets/logo.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 KiB

BIN
assets/mail-server.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

BIN
assets/matrix.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

BIN
assets/nextcloud.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

BIN
assets/searxng.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

BIN
assets/stump.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

BIN
assets/vaultwarden.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

BIN
assets/wallpaper.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 576 KiB

7
services.toml.example Normal file
View file

@ -0,0 +1,7 @@
[[services]]
name = "My service"
description = "My service description."
domain = "myservice.viyurz.fr"
image = "/assets/myservice.png"
language = "MyLanguage"
repository_url = "https://github.com/myservicerepository"

121
src/main.rs Normal file
View file

@ -0,0 +1,121 @@
use std::fs;
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 toml::from_str;
use tokio::signal;
use tower_http::timeout::TimeoutLayer;
use tower_http::trace::TraceLayer;
use serde::{Deserialize, Serialize};
use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt};
struct AppState {
env: Environment<'static>,
services: Vec<Service>,
}
#[derive(Deserialize)]
struct ServiceVec {
services: Vec<Service>,
}
#[derive(Deserialize, Serialize)]
struct Service {
name: String,
description: String,
domain: String,
image: String,
language: String,
repository_url: String,
}
#[tokio::main]
async fn main() {
tracing_subscriber::registry()
.with(
tracing_subscriber::EnvFilter::try_from_default_env().unwrap_or_else(|_| {
// axum logs rejections from built-in extractors with the `axum::rejection`
// target, at `TRACE` level. `axum::rejection=trace` enables showing those events
"homepage=debug,tower_http=debug,axum::rejection=trace".into()
}),
)
.with(tracing_subscriber::fmt::layer())
.init();
// Read services from file
let services_str = fs::read_to_string("services.toml")
.expect("Failed to read services.toml file.");
let service_vec: ServiceVec = from_str::<ServiceVec>(&services_str).unwrap();
// init template engine and add templates
let mut env = Environment::new();
env.add_template("_base", include_str!("../templates/_base.html"))
.unwrap();
env.add_template("_navbar", include_str!("../templates/_navbar.html"))
.unwrap();
env.add_template("home", include_str!("../templates/home.html"))
.unwrap();
// pass env & services to handlers via state
let app_state = Arc::new(AppState { env, services: service_vec.services });
// define routes
let app = Router::new()
.route("/", get(handler_home))
.nest_service("/assets", ServeDir::new("assets"))
.layer((
TraceLayer::new_for_http(),
// Graceful shutdown will wait for outstanding requests to complete. Add a timeout so
// requests don't hang forever.
TimeoutLayer::new(Duration::from_secs(10)),
))
.with_state(app_state);
// run it
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080")
.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() {
let ctrl_c = async {
signal::ctrl_c()
.await
.expect("failed to install Ctrl+C handler");
};
#[cfg(unix)]
let terminate = async {
signal::unix::signal(signal::unix::SignalKind::terminate())
.expect("failed to install signal handler")
.recv()
.await;
};
#[cfg(not(unix))]
let terminate = std::future::pending::<()>();
tokio::select! {
_ = ctrl_c => {},
_ = terminate => {},
}
}

16
tailwind.config.js Normal file
View file

@ -0,0 +1,16 @@
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./templates/**/*.html"],
theme: {
colors: {
transparent: 'transparent',
current: 'currentColor',
white: '#ffffff',
primary: "#601237",
background: "#290718",
},
},
plugins: [],
}

26
tailwind.css Normal file
View file

@ -0,0 +1,26 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer base {
h1, h2 {
@apply p-3;
}
h1, h2 {
@apply font-bold;
@apply text-center;
}
h1 {
@apply text-3xl;
}
h2 {
@apply text-2xl;
}
h3 {
@apply text-xl;
}
}

22
templates/_base.html Normal file
View file

@ -0,0 +1,22 @@
<!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" 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">
</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>
</html>

18
templates/_navbar.html Normal file
View file

@ -0,0 +1,18 @@
<nav class="flex flex-row place-content-around h-20 bg-primary px-12">
<div class="basis-1/3 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>
</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="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>
</nav>

23
templates/home.html Normal file
View file

@ -0,0 +1,23 @@
{% 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>
{% endblock %}