feat: initialization migration to Nuxt

This commit initializes both the Nuxt frontend and the Rust backend of
the new version of phundrak.com
This commit is contained in:
Lucien Cartier-Tilet 2025-11-04 09:17:18 +01:00
parent cc62d0bb95
commit 1abbbdbf79
133 changed files with 17247 additions and 8335 deletions

1
.devenv-root Normal file
View File

@ -0,0 +1 @@
/home/phundrak/code/web/phundrak.com

View File

@ -7,6 +7,10 @@ insert_final_newline = true
charset = utf-8 charset = utf-8
trim_trailing_whitespace = true trim_trailing_whitespace = true
[*.{rs, toml}]
indent_style = space
indent_size = 4
[*.{json,ts,css}] [*.{json,ts,css}]
indent_style = space indent_style = space
indent_size = 2 indent_size = 2

3
.env.example Normal file
View File

@ -0,0 +1,3 @@
NUXT_PUBLIC_TURNSTILE_SITE_KEY="changeme"
NUXT_TURNSTILE_SECRET_KEY="changeme"
NUXT_BACKEND=http://localhost:3001

55
.envrc
View File

@ -1 +1,54 @@
use nix #!/usr/bin/env bash
if ! has nix_direnv_version || ! nix_direnv_version 3.1.0; then
source_url "https://raw.githubusercontent.com/nix-community/nix-direnv/3.1.0/direnvrc" "sha256-yMJ2OVMzrFaDPn7q8nCBZFRYpL/f0RcHzhmw/i6btJM="
fi
export DEVENV_IN_DIRENV_SHELL=true
# Load .env file if present
dotenv_if_exists
watch_file flake.nix
watch_file flake.lock
watch_file .envrc.local
watch_file backend/shell.nix
watch_file frontend/shell.nix
# Check if .envrc.local exists and contains a shell preference
if [[ -f .envrc.local ]]; then
source .envrc.local
fi
# If no shell is specified, prompt the user interactively
if [[ -z "$NIX_SHELL_NAME" ]]; then
echo ""
echo "🔧 Available development shells:"
echo " 1) frontend - Nuxt.js/Vue development environment"
echo " 2) backend - Rust backend development environment"
echo ""
echo "💡 Tip: Create a .envrc.local file with 'export NIX_SHELL_NAME=frontend' to skip this prompt"
echo ""
# Read user input
read -p "Select shell (1 or 2): " choice
case $choice in
1|frontend)
NIX_SHELL_NAME=frontend
;;
2|backend)
NIX_SHELL_NAME=backend
;;
*)
echo "❌ Invalid choice. Please select 1 or 2."
return 1
;;
esac
echo "✅ Loading ${NIX_SHELL_NAME} environment..."
fi
if ! use flake ".#${NIX_SHELL_NAME}" --no-pure-eval; then
echo "❌ devenv could not be built. The devenv environment was not loaded. Make the necessary changes to flake.nix and hit enter to try again." >&2
fi

View File

@ -1,50 +0,0 @@
name: deploy
on:
push:
branches:
- main
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v4
with:
node-version: 22.x
- run: npm ci
- uses: purcell/setup-emacs@master
with:
version: 29.1
- name: "Export org to md"
run: emacs -Q --script export.el
- run: npm run build
- name: "Deploy to Cloudflare Pages"
uses: cloudflare/pages-action@v1
with:
apiToken: ${{ secrets.CLOUDFLARE_API_TOKEN }}
accountId: ${{ secrets.ACCOUNT_ID }}
projectName: phundrak-com
directory: content/.vuepress/dist/
githubToken: ${{ secrets.TOKEN }}
# - name: "Deploy on the Web"
# uses: appleboy/scp-action@v0.1.7
# with:
# host: ${{ secrets.HOST }}
# username: ${{ secrets.USERNAME }}
# key: ${{ secrets.KEY }}
# port: ${{ secrets.PORT }}
# source: content/.vuepress/dist/*
# target: ${{ secrets.DESTPATH }}
# strip_components: 3
# - name: "Deploy on Gemini"
# uses: appleboy/scp-action@v0.1.7
# with:
# host: ${{ secrets.HOST }}
# username: ${{ secrets.USERNAME }}
# key: ${{ secrets.KEY }}
# port: ${{ secrets.PORT }}
# source: gemini/*
# target: ${{ secrets.DESTPATH_GMI }}
# strip_components: 1

35
.gitignore vendored
View File

@ -1,6 +1,33 @@
node_modules
.temp .temp
.cache .cache
/content/.vuepress/dist/* .devenv
*.md
/.yarn/ # Logs
logs
*.log
# Misc
.DS_Store
.fleet
.idea
# Local env files
.env
.env.*
!.env.example
# Backend
target/
coverage/
# Frontend
## Nuxt dev/build outputs
.output
.data
.nuxt
.nitro
.cache
dist
## Node dependencies
node_modules

View File

@ -1,3 +0,0 @@
enableMessageNames: false
nodeLinker: node-modules

View File

@ -1,51 +1,76 @@
#+title: phundrak.com #+title: phundrak.com
#+html: <a href="https://www.gnu.org/software/emacs/"><img src="https://img.shields.io/badge/Emacs-29.1-blueviolet.svg?style=flat-square&logo=GNU%20Emacs&logoColor=white" /></a> #+html: <a href="https://www.rust-lang.org/"><img src="https://img.shields.io/badge/Rust-Backend-orange.svg?style=flat-square&logo=Rust&logoColor=white" /></a>
#+html: <a href="https://orgmode.org/"><img src="https://img.shields.io/badge/Written%20with-Org%20mode-success?logo=Org&logoColor=white&style=flat-square"/></a> #+html: <a href="https://nuxt.com/"><img src="https://img.shields.io/badge/Frontend-Nuxt%204-00DC82?logo=Nuxt.js&logoColor=white&style=flat-square"/></a>
#+html: <a href="https://v2.vuepress.vuejs.org/"><img src="https://img.shields.io/badge/Framework-Vuepress-42D392?logo=Vue.js&logoColor=white&style=flat-square"/></a> #+html: <a href="https://vuejs.org/"><img src="https://img.shields.io/badge/Vue-3-42B883?logo=Vue.js&logoColor=white&style=flat-square"/></a>
#+html: <a href="https://phundrak.com"><img src="https://img.shields.io/badge/dynamic/json?label=Website&query=%24%5B%3A1%5D.status&url=https%3A%2F%2Fdrone.phundrak.com%2Fapi%2Frepos%2Fphundrak%2Fphundrak.com%2Fbuilds&style=flat-square&logo=buffer" /></a> #+html: <a href="https://phundrak.com"><img src="https://img.shields.io/badge/Website-phundrak.com-blue?style=flat-square&logo=buffer" /></a>
* Introduction * Introduction
This is the repository for my website [[https://phundrak.com][phundrak.com]] which contains the This is the repository for my website [[https://phundrak.com][phundrak.com]] which contains the
code available on the =main= branch. Code available on the =develop= code available on the =main= branch. Code available on the =develop=
branch is available at [[https://beta.phundrak.com][beta.phundrak.com]]. branch is available at [[https://beta.phundrak.com][beta.phundrak.com]].
* Structure of the project * Architecture
This website is made with [[https://v2.vuepress.vuejs.org/][VuePress]], a Vue-powered static site The website follows a modern full-stack architecture:
generator. You can find its Node.JS configuration in the [[file:package.json][package.json]]
file as well as its content and general configuration in the directory
[[file:content/][content]].
** Installing and running - *Backend*: Rust using the [[https://github.com/poem-web/poem][Poem]] web framework (located in [[file:backend/][backend/]])
In order to run the website, you firts need to export all the orgmode - *Frontend*: Nuxt 4 + Vue 3 + TypeScript (located in [[file:frontend/][frontend/]])
files to Markdown files. I recommend using =ox-gfm= to do so. If you
dont mind =package.el= installing it as well as =f.el=, you can run the ** Backend
following command: The backend is written in Rust and provides a RESTful API using the
Poem framework with OpenAPI support.
*** Running the Backend
To run the backend in development mode:
#+begin_src shell #+begin_src shell
emacs -Q --script export.el cd backend
cargo run
#+end_src #+end_src
To install the NPM dependencies for the project, run one of the To run tests:
following commands:
#+begin_src shell #+begin_src shell
yarn cd backend
# or cargo test
npm install # delete the yarn.lock file before
#+end_src #+end_src
To run the project, run one of the following commands using the same For continuous testing and linting during development, use [[https://dystroy.org/bacon/][bacon]]:
package manager as above:
#+begin_src shell #+begin_src shell
yarn dev cd backend
# or bacon
npm run dev
#+end_src #+end_src
You can compile the website to a static website by running *** Building the Backend
To build the backend for production:
#+begin_src shell #+begin_src shell
yarn build cd backend
# or cargo build --release
npm run build
#+end_src #+end_src
The compiled version of the website can then be found in =content/.vuepress/dist=. The compiled binary will be available at =backend/target/release/backend=.
** Frontend
The frontend is built with Nuxt 4, Vue 3, and TypeScript, providing a
modern single-page application experience.
*** Installing Dependencies
First, install the required dependencies using =pnpm=:
#+begin_src shell
cd frontend
pnpm install
#+end_src
*** Running the Frontend
To run the frontend in development mode:
#+begin_src shell
cd frontend
pnpm dev
#+end_src
*** Building the Frontend
To build the frontend for production:
#+begin_src shell
cd frontend
pnpm build
#+end_src
The compiled version of the website can then be found in =frontend/.output=.

View File

@ -0,0 +1,6 @@
[all]
out = ["Xml"]
target-dir = "coverage"
output-dir = "coverage"
fail-under = 60
exclude-files = ["target/*"]

View File

@ -0,0 +1,7 @@
[all]
out = ["Html", "Lcov"]
skip-clean = true
target-dir = "coverage"
output-dir = "coverage"
fail-under = 60
exclude-files = ["target/*"]

3022
backend/Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

31
backend/Cargo.toml Normal file
View File

@ -0,0 +1,31 @@
[package]
name = "phundrak-dot-com-backend"
version = "0.1.0"
edition = "2024"
publish = false
authors = ["Lucien Cartier-Tilet <lucien@phundrak.com>"]
license = "AGPL-3.0-only"
[lib]
path = "src/lib.rs"
[[bin]]
path = "src/main.rs"
name = "backend"
[dependencies]
chrono = { version = "0.4.42", features = ["serde"] }
config = { version = "0.15.18", features = ["yaml"] }
dotenvy = "0.15.7"
lettre = { version = "0.11.19", default-features = false, features = ["builder", "hostname", "pool", "rustls-tls", "tokio1", "tokio1-rustls-tls", "smtp-transport"] }
poem = { version = "3.1.12", default-features = false, features = ["csrf", "rustls", "test"] }
poem-openapi = { version = "5.1.16", features = ["chrono", "swagger-ui"] }
serde = "1.0.228"
serde_json = "1.0.145"
thiserror = "2.0.17"
tokio = { version = "1.48.0", features = ["macros", "rt-multi-thread"] }
tracing = "0.1.41"
tracing-subscriber = { version = "0.3.20", features = ["fmt", "std", "env-filter", "registry", "json", "tracing-log"] }
[lints.rust]
unexpected_cfgs = { level = "warn", check-cfg = ['cfg(tarpaulin_include)'] }

143
backend/README.md Normal file
View File

@ -0,0 +1,143 @@
# phundrak.com Backend
The backend for [phundrak.com](https://phundrak.com), built with Rust and the [Poem](https://github.com/poem-web/poem) web framework.
## Features
- **RESTful API** with OpenAPI documentation
- **Type-safe routing** using Poem's declarative API
- **Structured logging** with `tracing`
- **Strict linting** for code quality and safety
- **Comprehensive testing** with integration test support
## Development
### Prerequisites
- Rust (latest stable version recommended)
- Cargo (comes with Rust)
### Running the Server
To start the development server:
```bash
cargo run
```
The server will start on the configured port (check your configuration for details).
### Building
For development builds:
```bash
cargo build
```
For optimized production builds:
```bash
cargo build --release
```
The compiled binary will be at `target/release/backend`.
## Testing
Run all tests:
```bash
cargo test
```
Run a specific test:
```bash
cargo test <test_name>
```
Run tests with output:
```bash
cargo test -- --nocapture
```
## Code Quality
### Linting
This project uses strict Clippy linting rules:
- `#![deny(clippy::all)]`
- `#![deny(clippy::pedantic)]`
- `#![deny(clippy::nursery)]`
Run Clippy to check for issues:
```bash
cargo clippy --all-targets
```
### Continuous Checking with Bacon
For continuous testing and linting during development, use [bacon](https://dystroy.org/bacon/):
```bash
bacon
```
This will watch your files and automatically run clippy or tests on changes.
## Code Style
### Error Handling
- Use `thiserror` for custom error types
- Always return `Result` types for fallible operations
- Use descriptive error messages
### Logging
- Use `tracing::event!` for logging
- Always set `target: "backend"`
- Use appropriate log levels (trace, debug, info, warn, error)
Example:
```rust
tracing::event!(target: "backend", tracing::Level::INFO, "Server started");
```
### Imports
Organize imports in three groups:
1. Standard library (`std::*`)
2. External crates
3. Local modules
Use explicit paths (e.g., `poem_openapi::ApiResponse` instead of wildcards).
### Testing
- Use `#[cfg(test)]` module blocks
- Leverage Poem's test utilities for endpoint testing
- Use random TCP listeners for integration tests to avoid port conflicts
## Project Structure
```
backend/
├── src/
│ ├── main.rs # Application entry point
│ ├── api/ # API endpoints
│ ├── models/ # Data models
│ ├── services/ # Business logic
│ └── utils/ # Utility functions
├── tests/ # Integration tests
├── Cargo.toml # Dependencies and metadata
└── README.md # This file
```
## License
See the root repository for license information.

84
backend/bacon.toml Normal file
View File

@ -0,0 +1,84 @@
# This is a configuration file for the bacon tool
#
# Bacon repository: https://github.com/Canop/bacon
# Complete help on configuration: https://dystroy.org/bacon/config/
# You can also check bacon's own bacon.toml file
# as an example: https://github.com/Canop/bacon/blob/main/bacon.toml
default_job = "clippy-all"
[jobs.check]
command = ["cargo", "check", "--color", "always"]
need_stdout = false
[jobs.check-all]
command = ["cargo", "check", "--all-targets", "--color", "always"]
need_stdout = false
# Run clippy on the default target
[jobs.clippy]
command = [
"cargo", "clippy",
"--color", "always",
]
need_stdout = false
[jobs.clippy-all]
command = [
"cargo", "clippy",
"--all-targets",
"--color", "always",
]
need_stdout = false
[jobs.test]
command = [
"cargo", "test", "--color", "always",
"--", "--color", "always", # see https://github.com/Canop/bacon/issues/124
]
need_stdout = true
[jobs.doc]
command = ["cargo", "doc", "--color", "always", "--no-deps"]
need_stdout = false
# If the doc compiles, then it opens in your browser and bacon switches
# to the previous job
[jobs.doc-open]
command = ["cargo", "doc", "--color", "always", "--no-deps", "--open"]
need_stdout = false
on_success = "back" # so that we don't open the browser at each change
# You can run your application and have the result displayed in bacon,
# *if* it makes sense for this crate.
# Don't forget the `--color always` part or the errors won't be
# properly parsed.
# If your program never stops (eg a server), you may set `background`
# to false to have the cargo run output immediately displayed instead
# of waiting for program's end.
[jobs.run]
command = [
"cargo", "run",
"--color", "always",
# put launch parameters for your program behind a `--` separator
]
need_stdout = true
allow_warnings = true
background = true
# This parameterized job runs the example of your choice, as soon
# as the code compiles.
# Call it as
# bacon ex -- my-example
[jobs.ex]
command = ["cargo", "run", "--color", "always", "--example"]
need_stdout = true
allow_warnings = true
# You may define here keybindings that would be specific to
# a project, for example a shortcut to launch a specific job.
# Shortcuts to internal functions (scrolling, toggling, etc.)
# should go in your personal global prefs.toml file instead.
[keybindings]
# alt-m = "job:my-job"
c = "job:clippy-all" # comment this to have 'c' run clippy on only the default target

51
backend/deny.toml Normal file
View File

@ -0,0 +1,51 @@
[output]
feature-depth = 1
[advisories]
ignore = []
[licenses]
# List of explicitly allowed licenses
# See https://spdx.org/licenses/ for list of possible licenses
allow = [
"0BSD",
"AGPL-3.0-only",
"Apache-2.0 WITH LLVM-exception",
"Apache-2.0",
"BSD-3-Clause",
"CDLA-Permissive-2.0",
"ISC",
"MIT",
"MPL-2.0",
"OpenSSL",
"Unicode-3.0",
"Zlib",
]
confidence-threshold = 0.8
exceptions = []
[licenses.private]
ignore = false
registries = []
[bans]
multiple-versions = "allow"
wildcards = "allow"
highlight = "all"
workspace-default-features = "allow"
external-default-features = "allow"
allow = []
deny = []
skip = []
skip-tree = []
[sources]
unknown-registry = "deny"
unknown-git = "deny"
allow-registry = ["https://github.com/rust-lang/crates.io-index"]
allow-git = []
[sources.allow-org]
github = []
gitlab = []
bitbucket = []

48
backend/justfile Normal file
View File

@ -0,0 +1,48 @@
default: run
run:
cargo run
run-release:
cargo run --release
format:
cargo fmt --all
format-check:
cargo fmt --check --all
audit:
cargo deny
build:
cargo build
build-release:
cargo build --release
lint:
cargo clippy --all-targets
release-build:
cargo build --release
release-run:
cargo run --release
test:
cargo test
coverage:
mkdir -p coverage
cargo tarpaulin --config .tarpaulin.local.toml
coverage-ci:
mkdir -p coverage
cargo tarpaulin --config .tarpaulin.ci.toml
check-all: format-check lint coverage audit
## Local Variables:
## mode: makefile
## End:

View File

@ -0,0 +1,9 @@
application:
port: 3100
version: "0.1.0"
email:
host: localhost
user: user
from: Contact Form <noreply@example.com>
password: hunter2

View File

@ -0,0 +1,8 @@
frontend_url: http://localhost:3000
debug: true
application:
protocol: http
host: 127.0.0.1
base_url: http://127.0.0.1:3100
name: "com.phundrak.backend.prod"

View File

@ -0,0 +1,8 @@
debug: false
frontend_url: ""
application:
name: "com.phundrak.backend.prod"
protocol: https
host: 0.0.0.0
base_url: ""

77
backend/shell.nix Normal file
View File

@ -0,0 +1,77 @@
{
inputs,
pkgs,
system,
self,
rust-overlay,
...
}: let
overlays = [(import rust-overlay)];
rustPkgs = import inputs.nixpkgs {inherit system overlays;};
rustVersion = rustPkgs.rust-bin.stable.latest.default;
in
inputs.devenv.lib.mkShell {
inherit inputs pkgs;
modules = [
{
devenv.root = let
devenvRootFileContent = builtins.readFile "${self}/.devenv-root";
in
pkgs.lib.mkIf (devenvRootFileContent != "") devenvRootFileContent;
}
{
packages = with rustPkgs; [
(rustVersion.override {
extensions = [
"clippy"
"rust-src"
"rust-analyzer"
"rustfmt"
];
})
bacon
cargo-deny
cargo-shuttle
cargo-tarpaulin
cargo-watch
flyctl
just
tombi # TOML lsp server
vscode-langservers-extracted
];
services.mailpit = {
enable = true;
# HTTP interface for viewing emails
uiListenAddress = "127.0.0.1:8025";
# SMTP server for receiving emails
smtpListenAddress = "127.0.0.1:1025";
};
processes.run.exec = "cargo watch -x run";
enterShell = ''
echo "🦀 Rust backend development environment loaded!"
echo "📦 Rust version: $(rustc --version)"
echo "📦 Cargo version: $(cargo --version)"
echo ""
echo "Available tools:"
echo " - rust-analyzer (LSP)"
echo " - clippy (linter)"
echo " - rustfmt (formatter)"
echo " - bacon (continuous testing/linting)"
echo " - cargo-deny (dependency checker)"
echo " - cargo-tarpaulin (code coverage)"
echo ""
echo "📧 Mailpit service:"
echo " - SMTP server: 127.0.0.1:1025"
echo " - Web UI: http://127.0.0.1:8025"
echo ""
echo "🚀 Quick start:"
echo " Run 'devenv up' to launch:"
echo " - Mailpit service (email testing)"
echo " - Backend with 'cargo watch -x run' (auto-reload)"
'';
}
];
}

64
backend/src/lib.rs Normal file
View File

@ -0,0 +1,64 @@
#![deny(clippy::all)]
#![deny(clippy::pedantic)]
#![deny(clippy::nursery)]
#![allow(clippy::missing_panics_doc)]
#![allow(clippy::missing_errors_doc)]
#![allow(clippy::unused_async)]
pub mod route;
pub mod settings;
pub mod startup;
pub mod telemetry;
type MaybeListener = Option<poem::listener::TcpListener<String>>;
fn prepare(listener: MaybeListener) -> startup::Application {
dotenvy::dotenv().ok();
let settings = settings::Settings::new().expect("Failed to read settings");
if !cfg!(test) {
let subscriber = telemetry::get_subscriber(settings.debug);
telemetry::init_subscriber(subscriber);
}
tracing::event!(
target: "backend",
tracing::Level::DEBUG,
"Using these settings: {:?}",
settings
);
let application = startup::Application::build(settings, listener);
tracing::event!(
target: "backend",
tracing::Level::INFO,
"Listening on http://{}:{}/",
application.host(),
application.port()
);
tracing::event!(
target: "backend",
tracing::Level::INFO,
"Documentation available at http://{}:{}/docs",
application.host(),
application.port()
);
application
}
#[cfg(not(tarpaulin_include))]
pub async fn run(listener: MaybeListener) -> Result<(), std::io::Error> {
let application = prepare(listener);
application.make_app().run().await
}
#[cfg(test)]
fn make_random_tcp_listener() -> poem::listener::TcpListener<String> {
let tcp_listener =
std::net::TcpListener::bind("127.0.0.1:0").expect("Failed to bind a random TCP listener");
let port = tcp_listener.local_addr().unwrap().port();
poem::listener::TcpListener::bind(format!("127.0.0.1:{port}"))
}
#[cfg(test)]
fn get_test_app() -> startup::App {
let tcp_listener = make_random_tcp_listener();
prepare(Some(tcp_listener)).make_app().into()
}

5
backend/src/main.rs Normal file
View File

@ -0,0 +1,5 @@
#[cfg(not(tarpaulin_include))]
#[tokio::main]
async fn main() -> Result<(), std::io::Error> {
phundrak_dot_com_backend::run(None).await
}

View File

@ -0,0 +1,29 @@
use poem_openapi::{ApiResponse, OpenApi};
use super::ApiCategory;
#[derive(ApiResponse)]
enum HealthResponse {
#[oai(status = 200)]
Ok,
}
pub struct HealthApi;
#[OpenApi(prefix_path = "/v1/health-check", tag = "ApiCategory::Health")]
impl HealthApi {
#[oai(path = "/", method = "get")]
async fn ping(&self) -> HealthResponse {
tracing::event!(target: "backend", tracing::Level::DEBUG, "Accessing health-check endpoint");
HealthResponse::Ok
}
}
#[tokio::test]
async fn health_check_works() {
let app = crate::get_test_app();
let cli = poem::test::TestClient::new(app);
let resp = cli.get("/v1/health-check").send().await;
resp.assert_status_is_ok();
resp.assert_text("").await;
}

86
backend/src/route/meta.rs Normal file
View File

@ -0,0 +1,86 @@
use poem::Result;
use poem_openapi::{ApiResponse, Object, OpenApi, payload::Json};
use super::ApiCategory;
use crate::settings::Settings;
#[derive(Object, Debug, Clone, serde::Serialize, serde::Deserialize)]
struct Meta {
version: String,
name: String,
}
impl From<poem::web::Data<&Settings>> for Meta {
fn from(value: poem::web::Data<&Settings>) -> Self {
let version = value.application.version.clone();
let name = value.application.name.clone();
Self { version, name }
}
}
#[derive(ApiResponse)]
enum MetaResponse {
#[oai(status = 200)]
Meta(Json<Meta>),
}
pub struct MetaApi;
#[OpenApi(prefix_path = "/v1/meta", tag = "ApiCategory::Meta")]
impl MetaApi {
#[oai(path = "/", method = "get")]
async fn meta(&self, settings: poem::web::Data<&Settings>) -> Result<MetaResponse> {
tracing::event!(target: "backend", tracing::Level::DEBUG, "Accessing meta endpoint");
Ok(MetaResponse::Meta(Json(settings.into())))
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::settings::ApplicationSettings;
#[tokio::test]
async fn meta_endpoint_returns_correct_data() {
let app = crate::get_test_app();
let cli = poem::test::TestClient::new(app);
let resp = cli.get("/v1/meta").send().await;
resp.assert_status_is_ok();
// let json = resp.0.into_json().await;
// assert!(json.is_ok(), "Response should be valid JSON");
// let json_value: serde_json::Value = json.unwrap();
// assert!(json_value.get("version").is_some(), "Response should have version field");
// assert!(json_value.get("name").is_some(), "Response should have name field");
}
#[tokio::test]
async fn meta_endpoint_returns_200_status() {
let app = crate::get_test_app();
let cli = poem::test::TestClient::new(app);
let resp = cli.get("/v1/meta").send().await;
resp.assert_status_is_ok();
}
#[test]
fn meta_from_settings_conversion() {
let settings = Settings {
application: ApplicationSettings {
name: "test-app".to_string(),
version: "1.0.0".to_string(),
port: 8080,
host: "127.0.0.1".to_string(),
base_url: "http://localhost:8080".to_string(),
protocol: "http".to_string(),
},
debug: false,
email: crate::settings::EmailSettings::default(),
frontend_url: "http://localhost:3000".to_string(),
};
let meta: Meta = poem::web::Data(&settings).into();
assert_eq!(meta.name, "test-app");
assert_eq!(meta.version, "1.0.0");
}
}

18
backend/src/route/mod.rs Normal file
View File

@ -0,0 +1,18 @@
use poem_openapi::{OpenApi, Tags};
mod health;
pub use health::HealthApi;
mod meta;
pub use meta::MetaApi;
#[derive(Tags)]
enum ApiCategory {
Health,
Meta
}
pub(crate) struct Api;
#[OpenApi]
impl Api {}

157
backend/src/settings.rs Normal file
View File

@ -0,0 +1,157 @@
#[derive(Debug, serde::Deserialize, Clone, Default)]
pub struct Settings {
pub application: ApplicationSettings,
pub debug: bool,
pub email: EmailSettings,
pub frontend_url: String,
}
impl Settings {
pub fn new() -> Result<Self, config::ConfigError> {
let base_path = std::env::current_dir().expect("Failed to determine the current directory");
let settings_directory = base_path.join("settings");
let environment: Environment = std::env::var("APP_ENVIRONMENT")
.unwrap_or_else(|_| "dev".into())
.try_into()
.expect("Failed to parse APP_ENVIRONMENT");
let environment_filename = format!("{environment}.yaml");
// Lower = takes precedence
let settings = config::Config::builder()
.add_source(config::File::from(settings_directory.join("base.yaml")))
.add_source(config::File::from(
settings_directory.join(environment_filename),
))
.add_source(
config::Environment::with_prefix("APP")
.prefix_separator("__")
.separator("__"),
)
.build()?;
settings.try_deserialize()
}
}
#[derive(Debug, serde::Deserialize, Clone, Default)]
pub struct ApplicationSettings {
pub name: String,
pub version: String,
pub port: u16,
pub host: String,
pub base_url: String,
pub protocol: String,
}
#[derive(Debug, PartialEq, Eq, Default)]
pub enum Environment {
#[default]
Development,
Production,
}
impl std::fmt::Display for Environment {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let self_str = match self {
Self::Development => "development",
Self::Production => "production",
};
write!(f, "{self_str}")
}
}
impl TryFrom<String> for Environment {
type Error = String;
fn try_from(value: String) -> Result<Self, Self::Error> {
Self::try_from(value.as_str())
}
}
impl TryFrom<&str> for Environment {
type Error = String;
fn try_from(value: &str) -> Result<Self, Self::Error> {
match value.to_lowercase().as_str() {
"development" | "dev" => Ok(Self::Development),
"production" | "prod" => Ok(Self::Production),
other => Err(format!(
"{other} is not a supported environment. Use either `development` or `production`"
)),
}
}
}
#[derive(Debug, serde::Deserialize, Clone, Default)]
pub struct EmailSettings {
pub host: String,
pub user: String,
pub password: String,
pub from: String,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn environment_display_development() {
let env = Environment::Development;
assert_eq!(env.to_string(), "development");
}
#[test]
fn environment_display_production() {
let env = Environment::Production;
assert_eq!(env.to_string(), "production");
}
#[test]
fn environment_from_str_development() {
assert_eq!(Environment::try_from("development").unwrap(), Environment::Development);
assert_eq!(Environment::try_from("dev").unwrap(), Environment::Development);
assert_eq!(Environment::try_from("Development").unwrap(), Environment::Development);
assert_eq!(Environment::try_from("DEV").unwrap(), Environment::Development);
}
#[test]
fn environment_from_str_production() {
assert_eq!(Environment::try_from("production").unwrap(), Environment::Production);
assert_eq!(Environment::try_from("prod").unwrap(), Environment::Production);
assert_eq!(Environment::try_from("Production").unwrap(), Environment::Production);
assert_eq!(Environment::try_from("PROD").unwrap(), Environment::Production);
}
#[test]
fn environment_from_str_invalid() {
let result = Environment::try_from("invalid");
assert!(result.is_err());
assert!(result.unwrap_err().contains("not a supported environment"));
}
#[test]
fn environment_from_string_development() {
assert_eq!(
Environment::try_from("development".to_string()).unwrap(),
Environment::Development
);
}
#[test]
fn environment_from_string_production() {
assert_eq!(
Environment::try_from("production".to_string()).unwrap(),
Environment::Production
);
}
#[test]
fn environment_from_string_invalid() {
let result = Environment::try_from("invalid".to_string());
assert!(result.is_err());
}
#[test]
fn environment_default_is_development() {
let env = Environment::default();
assert_eq!(env, Environment::Development);
}
}

162
backend/src/startup.rs Normal file
View File

@ -0,0 +1,162 @@
use poem::middleware::{AddDataEndpoint, Cors, CorsEndpoint};
use poem::{EndpointExt, Route};
use poem_openapi::OpenApiService;
use crate::{settings::Settings, route::{Api, HealthApi, MetaApi}};
type Server = poem::Server<poem::listener::TcpListener<String>, std::convert::Infallible>;
pub type App = AddDataEndpoint<CorsEndpoint<Route>, Settings>;
pub struct Application {
server: Server,
app: poem::Route,
host: String,
port: u16,
settings: Settings,
}
pub struct RunnableApplication {
server: Server,
app: App,
}
impl RunnableApplication {
pub async fn run(self) -> Result<(), std::io::Error> {
self.server.run(self.app).await
}
}
impl From<RunnableApplication> for App {
fn from(value: RunnableApplication) -> Self {
value.app
}
}
impl From<Application> for RunnableApplication {
fn from(value: Application) -> Self {
let app = value.app.with(Cors::new()).data(value.settings);
let server = value.server;
Self { server, app }
}
}
impl Application {
fn setup_app(settings: &Settings) -> poem::Route {
let api_service = OpenApiService::new(
(Api, HealthApi, MetaApi),
settings.application.clone().name,
settings.application.clone().version,
);
let ui = api_service.swagger_ui();
poem::Route::new().nest("/", api_service).nest("/docs", ui)
}
fn setup_server(
settings: &Settings,
tcp_listener: Option<poem::listener::TcpListener<String>>,
) -> Server {
let tcp_listener = tcp_listener.unwrap_or_else(|| {
let address = format!(
"{}:{}",
settings.application.host, settings.application.port
);
poem::listener::TcpListener::bind(address)
});
poem::Server::new(tcp_listener)
}
#[must_use]
pub fn build(
settings: Settings,
tcp_listener: Option<poem::listener::TcpListener<String>>,
) -> Self {
let port = settings.application.port;
let host = settings.application.clone().host;
let app = Self::setup_app(&settings);
let server = Self::setup_server(&settings, tcp_listener);
Self {
server,
app,
host,
port,
settings,
}
}
#[must_use]
pub fn make_app(self) -> RunnableApplication {
self.into()
}
#[must_use]
pub fn host(&self) -> String {
self.host.clone()
}
#[must_use]
pub const fn port(&self) -> u16 {
self.port
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_settings() -> Settings {
Settings {
application: crate::settings::ApplicationSettings {
name: "test-app".to_string(),
version: "1.0.0".to_string(),
port: 8080,
host: "127.0.0.1".to_string(),
base_url: "http://localhost:8080".to_string(),
protocol: "http".to_string(),
},
debug: false,
email: crate::settings::EmailSettings::default(),
frontend_url: "http://localhost:3000".to_string(),
}
}
#[test]
fn application_build_and_host() {
let settings = create_test_settings();
let app = Application::build(settings.clone(), None);
assert_eq!(app.host(), settings.application.host);
}
#[test]
fn application_build_and_port() {
let settings = create_test_settings();
let app = Application::build(settings, None);
assert_eq!(app.port(), 8080);
}
#[test]
fn application_host_returns_correct_value() {
let settings = create_test_settings();
let app = Application::build(settings, None);
assert_eq!(app.host(), "127.0.0.1");
}
#[test]
fn application_port_returns_correct_value() {
let settings = create_test_settings();
let app = Application::build(settings, None);
assert_eq!(app.port(), 8080);
}
#[test]
fn application_with_custom_listener() {
let settings = create_test_settings();
let tcp_listener = std::net::TcpListener::bind("127.0.0.1:0")
.expect("Failed to bind random port");
let port = tcp_listener.local_addr().unwrap().port();
let listener = poem::listener::TcpListener::bind(format!("127.0.0.1:{port}"));
let app = Application::build(settings, Some(listener));
assert_eq!(app.host(), "127.0.0.1");
assert_eq!(app.port(), 8080);
}
}

53
backend/src/telemetry.rs Normal file
View File

@ -0,0 +1,53 @@
use tracing_subscriber::layer::SubscriberExt;
#[must_use]
pub fn get_subscriber(debug: bool) -> impl tracing::Subscriber + Send + Sync {
let env_filter = if debug { "debug" } else { "info" }.to_string();
let env_filter = tracing_subscriber::EnvFilter::try_from_default_env()
.unwrap_or_else(|_| tracing_subscriber::EnvFilter::new(env_filter));
let stdout_log = tracing_subscriber::fmt::layer().pretty();
let subscriber = tracing_subscriber::Registry::default()
.with(env_filter)
.with(stdout_log);
let json_log = if debug {
None
} else {
Some(tracing_subscriber::fmt::layer().json())
};
subscriber.with(json_log)
}
pub fn init_subscriber(subscriber: impl tracing::Subscriber + Send + Sync) {
tracing::subscriber::set_global_default(subscriber).expect("Failed to set subscriber");
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn get_subscriber_debug_mode() {
let subscriber = get_subscriber(true);
// If we can create the subscriber without panicking, the test passes
// We can't easily inspect the subscriber's internals, but we can verify it's created
let _ = subscriber;
}
#[test]
fn get_subscriber_production_mode() {
let subscriber = get_subscriber(false);
// If we can create the subscriber without panicking, the test passes
let _ = subscriber;
}
#[test]
fn get_subscriber_creates_valid_subscriber() {
// Test both debug and non-debug modes create valid subscribers
let debug_subscriber = get_subscriber(true);
let prod_subscriber = get_subscriber(false);
// Basic smoke test - if these are created without panicking, they're valid
let _ = debug_subscriber;
let _ = prod_subscriber;
}
}

View File

@ -1,4 +0,0 @@
;;; Directory Local Variables -*- no-byte-compile: t -*-
;;; For more information see (info "(emacs) Directory Variables")
((typescript-mode . ((typescript-indent-level . 2))))

View File

@ -1,25 +0,0 @@
import { defineClientConfig } from '@vuepress/client';
import ResponsiveImage from './components/ResponsiveImage.vue';
import ListRepositories from './components/GitHub/ListRepositories.vue';
import FetchRepositories from './components/GitHub/FetchRepositories.vue';
import GithubRepository from './components/GitHub/GithubRepository.vue';
import ApiLoader from './components/ApiLoader.vue';
import LoaderAnimation from './components/LoaderAnimation.vue';
import FetchError from './components/FetchError.vue';
import Icon from './components/Icon.vue';
export default defineClientConfig({
enhance({ app }) {
app.component('ResponsiveImage', ResponsiveImage);
app.component('ListRepositories', ListRepositories);
app.component('FetchRepositories', FetchRepositories);
app.component('GithubRepository', GithubRepository);
app.component('ApiLoader', ApiLoader);
app.component('LoaderAnimation', LoaderAnimation);
app.component('FetchError', FetchError);
app.component('Icon', Icon);
},
setup() {},
layouts: {},
rootComponents: [],
});

View File

@ -1,36 +0,0 @@
<template>
<slot v-if="loading" name="loader">
<LoaderAnimation />
</slot>
<slot v-else-if="error" name="error">
<FetchError :url="props.url" />
</slot>
<slot v-else> </slot>
</template>
<script setup lang="ts">
import LoaderAnimation from './LoaderAnimation.vue';
import FetchError from './FetchError.vue';
import { useFetchAndCache } from '../composables/fetchAndCache';
const props = defineProps({
url: {
default: '',
required: true,
type: String,
},
cacheName: {
required: true,
type: String,
},
alreadyKnownData: Object,
});
const emits = defineEmits(['loaded', 'error', 'loading']);
const { loading, error } = useFetchAndCache(props.url, {
emits: emits,
cacheName: props.cacheName,
});
</script>

View File

@ -1,26 +0,0 @@
<template>
<div class="error rounded-corners card-width">
<p>API call to {{ props.url }} failed</p>
</div>
</template>
<script setup lang="ts">
const props = defineProps({
url: {
required: true,
type: String,
},
});
</script>
<style lang="less">
@import 'node_modules/nord/src/lesscss/nord.less';
@import '../styles/classes.less';
.error {
display: inline-block;
padding: 2rem;
text-align: center;
background: @nord11;
}
</style>

View File

@ -1,46 +0,0 @@
<template>
<ApiLoader :url="fetchUrl" @loaded="filterRepos" cache-name="repos" />
<slot />
</template>
<script setup lang="ts">
import { PropType, ref } from 'vue';
import { GithubRepo } from '../../types/github';
const props = defineProps({
sortBy: {
default: 'none',
required: false,
type: String as PropType<'stars' | 'forks' | 'pushed_at'>,
},
user: {
default: '',
required: true,
type: String,
},
limit: {
default: 5,
required: false,
type: Number,
},
});
const emits = defineEmits(['loaded']);
const fetchUrl = `https://api.github.com/users/${props.user}/repos?per_page=100`;
const repos = ref<GithubRepo[]>([]);
const filterRepos = (response: GithubRepo[]) => {
repos.value = response
.sort((a, b) => {
if (props.sortBy === 'stars') {
return b.stargazers_count - a.stargazers_count;
}
if (props.sortBy === 'pushed_at') {
const dateA = new Date(a.pushed_at);
const dateB = new Date(b.pushed_at);
return dateB.getTime() - dateA.getTime();
}
return b.forks_count - a.forks_count;
})
.slice(0, +props.limit);
emits('loaded', repos.value);
};
</script>

View File

@ -1,89 +0,0 @@
<template>
<div
class="githubRepo flex-col flex-space-between gap-1rem rounded-corners card-width"
>
<ApiLoader
:cache-name="repoName()"
:url="fetchUrl"
:already-known-data="props.data"
@loaded="(repo: GithubRepo) => (repository = repo)"
>
<h3>{{ repository?.name }}</h3>
<div>
<p>
{{ repository?.description }}
</p>
</div>
<div class="flex-row flex-start gap-1rem stats">
<div class="stars">
<Icon name="star" /> {{ repository?.stargazers_count }}
</div>
<div class="forks">
<Icon name="fork" /> {{ repository?.forks_count }}
</div>
<div class="link">
<a :href="repository?.html_url"><i class="icon phunic-link" /></a>
</div>
</div>
</ApiLoader>
</div>
</template>
<script setup lang="ts">
import ApiLoader from '../ApiLoader.vue';
import { GithubRepo } from '../../types/github';
import { PropType, Ref, ref } from 'vue';
const props = defineProps({
data: Object as PropType<GithubRepo>,
repoName: String,
});
const repoName = (): string => {
return props.data ? props.data.full_name : props.repoName;
};
const fetchUrl = `https://api.github.com/repos/${repoName()}`;
const repository: Ref<GithubRepo | null> = ref(null);
</script>
<style lang="less">
@import 'node_modules/nord/src/lesscss/nord.less';
@import '../../styles/classes.less';
.githubRepo {
padding: 2rem;
background-color: @nord4;
align-self: auto;
h3,
h3:first-child {
margin: 0;
padding: 0;
}
html.dark & {
background-color: @nord3;
}
.info {
max-width: 30rem;
}
.stats {
width: 4rem;
div {
.flex-row();
gap: 0.3rem;
}
}
.link {
a {
display: flex;
align-items: center;
}
}
}
</style>

View File

@ -1,54 +0,0 @@
<template>
<div class="list-repos flex-col gap-1rem">
<FetchRepositories
v-if="props.user !== ''"
:sort-by="props.sortBy"
:user="props.user"
:limit="props.limit"
@loaded="(response: GithubRepo[]) => (repos = response)"
>
<GithubRepository
:data="repo"
type="repositories"
v-for="repo in repos"
/>
</FetchRepositories>
<slot v-else />
</div>
</template>
<script setup lang="ts">
import FetchRepositories from './FetchRepositories.vue';
import GithubRepository from './GithubRepository.vue';
import { PropType, Ref, ref } from 'vue';
import { GithubRepo } from '../../types/github';
const props = defineProps({
sortBy: {
default: 'none',
required: false,
type: String as PropType<'stars' | 'forks' | 'pushed_at'>,
},
user: {
default: '',
required: false,
type: String,
},
limit: {
default: 5,
required: false,
type: Number,
},
});
const repos: Ref<GithubRepo[]> = ref(null);
</script>
<style lang="less">
@import '../../styles/classes.less';
.list-repos {
margin: 2rem auto;
}
</style>

View File

@ -1,17 +0,0 @@
<template>
<i :class="`icon phunic-${props.name}`" />
</template>
<script setup lang="ts">
const props = defineProps({
name: {
default: '',
required: true,
type: String,
},
});
</script>
<style lang="less">
@import '../styles/fonts.less';
</style>

View File

@ -1,47 +0,0 @@
<template>
<svg
class="circle-loader"
width="40"
height="40"
version="1.1"
xmlns="http://www.w3.org/2000/svg"
>
<circle cx="20" cy="20" r="15" />
</svg>
</template>
<style lang="less" scoped>
@import 'node_modules/nord/src/lesscss/nord.less';
.circle-loader {
margin-left: 48%;
fill: transparent;
stroke: @nord7;
stroke-width: 5;
animation: dash 1.5s ease infinite, rotate 2s linear infinite;
}
@keyframes dash {
0% {
stroke-dasharray: 1, 95;
stroke-dashoffset: 0;
}
50% {
stroke-dasharray: 85, 95;
stroke-dashoffset: -25;
}
100% {
stroke-dasharray: 85, 95;
stroke-dashoffset: -90;
}
}
@keyframes rotate {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
</style>

View File

@ -1,25 +0,0 @@
<template>
<img :srcset="srcset" :sizes="sizes" :alt="props.alt" :src="props.src" />
</template>
<script setup lang="ts">
const props = defineProps<{
src: string;
width: number;
preview: string;
previewWidth: number;
previewThreshold?: number;
alt?: string;
}>();
const srcset = [
`${props.preview} ${props.previewWidth}w`,
`${props.src} ${props.width}w`,
].join(', ');
const sizes = [
`(max-width: ${props.previewThreshold || props.previewWidth}px) ${
props.previewWidth
}px`,
`${props.width}px`,
].join(', ');
</script>

View File

@ -1,62 +0,0 @@
import { Ref, computed, ref, watchEffect } from 'vue';
interface CacheOptions {
lifetime?: number;
timestampSuffix?: string;
forceUpdate?: boolean;
}
/**
* Cache data in local storage.
*
* The cache is updated if:
* - cache data does not exist
* - cached data is outdated and `data` is not null
* - or `options.forceUpdate` is true, regardless of the value of `data`
*
* Otherwise, data is retrieved from cache.
*
* @param {string} name Name of the cached value in local storage
* @param {Ref<T>} data Data to cache
* @param {CacheOptions} options Tweaks to the behaviour of the function
*/
export const useCache = <T>(
name: string,
data: Ref<T>,
options: CacheOptions,
) => {
const error = ref<string>(null);
const timestampName = name + (options?.timestampSuffix || '-timestamp');
const lifetime = options?.lifetime || 1000 * 60 * 60; // one hour in milliseconds
const lastUpdated: number = +localStorage.getItem(timestampName);
const cacheAge: number = Date.now() - lastUpdated;
const isDataOutdated = computed(() => {
return cacheAge > lifetime;
});
const shouldUpdate = computed(
() => options?.forceUpdate || (isDataOutdated.value && data.value != null),
);
const setData = () => {
console.log('Setting data in cache with name', name);
localStorage.setItem(name, JSON.stringify(data.value));
localStorage.setItem(timestampName, `${Date.now()}`);
};
const getData = () => {
console.log('Getting data from cache with name', name);
const cached = localStorage.getItem(name);
console.log('Value from storage:', cached);
try {
data.value = JSON.parse(cached);
} catch (err) {
console.error('Failed to parse cached data:', err);
data.value = null;
error.value = err;
}
};
getData();
watchEffect(() => (shouldUpdate.value ? setData() : getData()));
return { error, isDataOutdated };
};

View File

@ -1,72 +0,0 @@
import { ref, Ref } from 'vue';
import { useCache } from './cache';
type FetchAndCacheEmitter = (
event: 'loaded' | 'error' | 'loading',
...args: any[]
) => void;
interface UseFetchAndCacheOptions {
cacheLifetime?: number;
cacheName?: string;
emits?: FetchAndCacheEmitter;
}
const dummyEmits = (
_event: 'loaded' | 'error' | 'loading',
..._args: any[]
) => {};
export const useFetchAndCache = <T, E>(
url: string,
options?: UseFetchAndCacheOptions,
) => {
const data = ref<T | null>(null) as Ref<T>;
const error = ref<E | null>(null) as Ref<E>;
const loading = ref<boolean>(true);
const cacheLifetime: number = options?.cacheLifetime || 1000 * 60 * 60; // one hour
const cacheName: string = options?.cacheName || url;
const { isDataOutdated: isCacheOutdated, error: cacheError } = useCache(
cacheName,
data,
{
lifetime: cacheLifetime,
},
);
const emits: FetchAndCacheEmitter = options?.emits || dummyEmits;
const loaded = () => {
loading.value = false;
emits('loaded', data.value);
};
const fetchData = () => {
loading.value = true;
emits('loading');
console.log('Fetching from URL', url);
fetch(url)
.then((response) => {
if (!response.ok) {
throw new Error(response.statusText);
}
return response.json() as Promise<T>;
})
.then((responseData) => {
data.value = responseData;
loaded();
})
.catch((e) => {
console.warn('Caught error!', e);
error.value = e;
emits('error', e);
})
.finally(() => (loading.value = false));
};
if (isCacheOutdated.value || cacheError.value != null) {
fetchData();
} else {
loaded();
}
return { data, loading, error };
};

View File

@ -1,56 +0,0 @@
import { defaultTheme } from '@vuepress/theme-default';
import { viteBundler } from '@vuepress/bundler-vite';
import { defineUserConfig } from 'vuepress';
import { slimsearchPlugin } from '@vuepress/plugin-slimsearch';
import { umamiAnalyticsPlugin } from '@vuepress/plugin-umami-analytics';
import { head } from './head';
import { locales, searchLocaleLfn } from './locales';
import { themeLocales } from './themeLocales';
const isProd = process.env.NODE_ENV === 'production';
export default defineUserConfig({
lang: 'fr-FR',
title: 'Lucien Cartier-Tilet',
description: 'Site web personnel de Lucien Cartier-Tilet',
head: head,
bundler: isProd
? viteBundler({})
: viteBundler({
viteOptions: {
server: {
allowedHosts: true,
},
},
}),
markdown: {
html: true,
linkify: true,
typographer: true,
},
plugins: [
slimsearchPlugin({
indexContent: true,
indexLocaleOptions: {
'/lfn': searchLocaleLfn,
},
}),
isProd
? umamiAnalyticsPlugin({
id: '67166941-8c83-4a19-bc8c-139e44b7f7aa',
link: 'https://umami.phundrak.com/script.js',
})
: [],
],
locales: locales,
theme: defaultTheme({
contributors: false,
locales: themeLocales,
repo: 'https://labs.phundrak.com/phundrak/phundrak.com',
themePlugins: {
copyCode: false,
prismjs: false,
},
}),
});

View File

@ -1,142 +0,0 @@
import { HeadAttrsConfig } from 'vuepress';
interface SimplifiedHeader {
tag: string;
content: HeadAttrsConfig[];
}
const simplifiedHead: SimplifiedHeader[] = [
{
tag: 'meta',
content: [
{
name: 'author',
content: 'Lucien Cartier-Tilet',
},
{
name: 'fediverse:creator',
content: '@phundrak@mastodon.phundrak.com',
},
{
property: 'og:image',
content: 'https://cdn.phundrak.com/img/rich_preview.png',
},
{
property: 'org:title',
content: 'Lucien Cartier-Tilet',
},
{
property: 'og:description',
content: 'Site web personnel de Lucien Cartier-Tilet',
},
{
name: 'twitter:card',
content: 'summary',
},
{
name: 'twitter:site',
content: '@phundrak',
},
{
name: 'twitter:creator',
content: '@phundrak',
},
{
name: 'build-status',
content: `value: ${process.env.NODE_ENV}`,
},
{ name: 'msapplication-TileColor', content: '#3b4252' },
{ name: 'msapplication-TileImage', content: '/ms-icon-144x144.png' },
{ name: 'theme-color', content: '#3b4252' },
],
},
{
tag: 'link',
content: [
{
rel: 'apple-touch-icon',
sizes: '57x57',
href: '/apple-icon-57x57.png',
},
{
rel: 'apple-touch-icon',
sizes: '60x60',
href: '/apple-icon-60x60.png',
},
{
rel: 'apple-touch-icon',
sizes: '72x72',
href: '/apple-icon-72x72.png',
},
{
rel: 'apple-touch-icon',
sizes: '76x76',
href: '/apple-icon-76x76.png',
},
{
rel: 'apple-touch-icon',
sizes: '114x114',
href: '/apple-icon-114x114.png',
},
{
rel: 'apple-touch-icon',
sizes: '120x120',
href: '/apple-icon-120x120.png',
},
{
rel: 'apple-touch-icon',
sizes: '144x144',
href: '/apple-icon-144x144.png',
},
{
rel: 'apple-touch-icon',
sizes: '152x152',
href: '/apple-icon-152x152.png',
},
{
rel: 'apple-touch-icon',
sizes: '180x180',
href: '/apple-icon-180x180.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '192x192',
href: '/android-icon-192x192.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '32x32',
href: '/favicon-32x32.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '96x96',
href: '/favicon-96x96.png',
},
{
rel: 'icon',
type: 'image/png',
sizes: '16x16',
href: '/favicon-16x16.png',
},
{ rel: 'manifest', href: '/manifest.json' },
],
},
];
const headBuilder = [];
simplifiedHead.forEach((tag) => {
tag.content.forEach((element) => {
headBuilder.push([tag.tag, element]);
});
});
headBuilder.push([
'a',
{ rel: 'me', href: 'https://mastodon.phundrak.com/@phundrak' },
'Mastodon',
]);
export const head = headBuilder;

View File

@ -1,36 +0,0 @@
import SlimSarchLocaleData from '@vuepress/plugin-slimsearch';
export const locales = {
'/': {
lang: 'fr-FR',
title: 'Lucien Cartier-Tilet',
description: 'Site web personnel de Lucien Cartier-Tilet',
},
'/en/': {
lang: 'en-US',
title: 'Lucien Cartier-Tilet',
description: 'Personal website of Lucien Cartier-Tilet',
},
'/lfn/': {
lang: 'lfn',
title: 'Lucien Cartier-Tilet',
description: 'loca ueb de Lucien Cartier-Tilet',
},
};
export const searchLocaleLfn: SlimSarchLocaleData = {
cancel: 'Cansela',
placeholder: 'Xerca',
search: 'Xerca',
searching: 'Xercante',
defaultTitle: 'Documentos',
select: 'eleje',
navigate: 'naviga',
autocomplete: 'auto-completi',
exit: 'sorti',
queryHistory: 'Historia de xerca',
resultHistory: 'Historia de resultas',
emptyHistory: 'Historia vacua',
emptyResult: 'Resultas vacua',
loading: 'Cargante la indise de xerca...',
};

View File

@ -1,24 +0,0 @@
{
"subject": "acct:phundrak@emacs.ch",
"aliases": ["https://emacs.ch/@phundrak", "https://emacs.ch/users/phundrak"],
"links": [
{
"rel": "http://webfinger.net/rel/profile-page",
"type": "text/html",
"href": "https://emacs.ch/@phundrak"
},
{
"rel": "self",
"type": "application/activity+json",
"href": "https://emacs.ch/users/phundrak"
},
{
"rel": "http://ostatus.org/schema/1.0/subscribe",
"template": "https://emacs.ch/authorize_interaction?uri={uri}"
},
{
"rel": "http://openid.net/specs/connect/1.0/issuer",
"href": "https://auth.phundrak.com"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

View File

@ -1,2 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<browserconfig><msapplication><tile><square70x70logo src="/ms-icon-70x70.png"/><square150x150logo src="/ms-icon-150x150.png"/><square310x310logo src="/ms-icon-310x310.png"/><TileColor>#eceff4</TileColor></tile></msapplication></browserconfig>

View File

@ -1,18 +0,0 @@
body {
margin: 3em;
}
body, code,p {
background: #e5e9f0 !important;
line-height: 1.4 !important;
color: #2E3440;
font-size: 16px !important;
}
blockquote, blockquote p {
border-left-color: #3b4252;
}
pre {
padding: 10px;
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.1 KiB

View File

@ -1,36 +0,0 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="phunic" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#xe900;" glyph-name="star" horiz-adv-x="1152" d="M575.8 960c18.4 0 35.2-10.4 43.2-27l137.2-282.6 306.4-45.2c18-2.6 33-15.2 38.6-32.6s1-36.2-11.8-49l-222.2-220.4 52.4-311.2c3-18-4.4-36.2-19.2-47s-34.6-12-50.6-3.4l-274 146.4-273.8-146.2c-16.2-8.6-35.8-7.4-50.6 3.4s-22.4 29-19.4 47l52.4 311.2-222.2 220.2c-13 12.8-17.4 31.8-11.8 49s20.6 29.8 38.6 32.6l306.4 45.2 137.2 282.6c8.2 16.6 24.8 27 43.2 27zM575.8 802l-105-216.4c-7-14.2-20.4-24.2-36.2-26.6l-236.6-34.8 171.8-170.2c11-11 16.2-26.6 13.6-42l-40.6-239.4 210.4 112.4c14.2 7.6 31.2 7.6 45.2 0l210.4-112.4-40.4 239.2c-2.6 15.4 2.4 31 13.6 42l171.8 170.2-236.6 35c-15.6 2.4-29.2 12.2-36.2 26.6l-105.2 216.4z" />
<glyph unicode="&#xe901;" glyph-name="envelope" d="M96 832c-53 0-96-43-96-96 0-30.2 14.2-58.6 38.4-76.8l435.2-326.4c22.8-17 54-17 76.8 0l435.2 326.4c24.2 18.2 38.4 46.6 38.4 76.8 0 53-43 96-96 96h-832zM0 608v-416c0-70.6 57.4-128 128-128h768c70.6 0 128 57.4 128 128v416l-435.2-326.4c-45.6-34.2-108-34.2-153.6 0l-435.2 326.4z" />
<glyph unicode="&#xe902;" glyph-name="emacs" horiz-adv-x="953" d="M783.986 963.865h-275.721c83.997-19.531 275.721-63.47 275.721-98.442 0-74.39-354.471 19.638-433.184 19.638-39.366 0-118.026-6.581-118.115-59.058-0.338-137.717 185.123-255.869 314.998-334.671-90.454 26.238-173.296 39.348-252.045 39.348-102.407 0-299.236-39.348-299.236-177.28 0-121.709 246.779-233.919 314.998-255.834 118.507-38.156 314.998-58.844 314.998-78.749 0-19.798-157.464-39.348-354.471-39.348h-118.098c78.821-39.561 196.918-39.561 315.104-39.561l118.098 0.089c78.732 0.089 275.561 2.935 275.561 70.905 0 59.253-236.194 109.524-354.381 133.912-164.739 33.975-284.367 100.096-275.561 149.475 20.955 118.667 314.998 118.187 551.299 118.187-103.047 82.876-354.471 236.285-354.471 303.31 0 28.551 39.455 35.238 78.732 35.346 157.553 0.907 275.721-33.318 315.158-33.318 78.66 0 118.045 49.095 118.045 108.172 0.035 98.53-117.99 137.877-157.427 137.877z" />
<glyph unicode="&#xe903;" glyph-name="gitea" d="M179.534 736.179c-10.527 0-22.386-0.823-35.824-3.754-14.172-2.932-54.552-12.077-87.618-43.842-73.302-65.32-54.584-169.208-52.304-184.844 2.769-19.058 11.207-71.999 51.604-118.097 74.605-91.383 235.239-89.273 235.239-89.273s19.688-47.089 49.823-90.418c40.723-53.917 82.592-95.932 123.315-100.981 102.622 0 307.714 0.127 307.714 0.127s19.581-0.143 46.131 16.798c22.805 13.846 43.141 38.115 43.141 38.114s21.011 22.5 50.332 73.811c8.959 15.801 16.455 31.062 22.97 45.559 0 0 89.91 190.801 89.91 376.498-1.792 56.198-15.641 66.099-18.899 69.357-6.679 6.679-15.653 6.554-15.653 6.554s-190.895-10.763-289.771-13.044c-21.601-0.488-43.054-0.981-64.33-1.145l0.127-190.572c0 0 93.541-39.449 135.404-65.347 6.027-3.747 16.6-11.1 20.998-23.479 3.421-9.936 3.232-21.334-1.654-31.433l-99.326-206.67c-10.099-20.687-34.869-29.534-55.231-19.598l-206.733 99.39c-20.362 9.774-29.153 34.516-19.216 55.039l99.39 206.733c9.774 20.362 34.516 29.153 55.040 19.216 27.903-13.465 43.837-21.063 43.969-21.125 0 59.356-0.127 177.655-0.127 177.655-47.239-0.652-145.331 3.627-145.331 3.627s-230.325 11.527-255.41 13.807c-7.982 0.489-17.153 1.336-27.679 1.336zM199.387 657.979c0 0 11.57-96.789 25.579-153.475 11.728-47.565 40.406-126.559 40.406-126.559s-42.528 5.053-70.056 14.825c-42.189 13.846-60.067 30.479-60.067 30.479s-31.131 21.836-46.768 64.839c-26.877 71.999-2.291 115.934-2.291 115.934s13.708 36.651 62.739 48.868c22.479 6.027 50.459 5.090 50.459 5.090zM523.077 350.102c-13.357-0.163-25.085-9.448-28.18-22.479s3.258-26.551 14.823-32.579c12.543-6.516 28.506-2.932 36.977 8.796 8.308 11.565 7.004 27.529-2.932 37.628l39.094 79.98c2.443-0.163 6.027-0.326 10.099 0.814 6.679 1.466 11.565 5.864 11.565 5.864 6.841-2.932 14.009-6.19 21.502-9.936 7.819-3.909 15.149-7.982 21.828-11.891 1.466-0.814 2.932-1.792 4.561-3.095 2.606-2.118 5.538-5.050 7.656-8.959 3.095-8.959-3.095-24.271-3.095-24.271-3.747-12.38-29.972-66.134-29.972-66.134-13.194 0.326-24.923-8.145-28.832-20.362-4.235-13.194 1.792-28.18 14.497-34.696s28.343-2.769 36.651 8.633c8.145 11.077 7.493 26.551-1.792 36.814 3.095 6.027 6.027 12.054 9.122 18.407 8.145 16.941 21.99 49.519 21.99 49.519 1.466 2.769 9.285 16.778 4.398 34.696-4.072 18.57-20.524 27.203-20.524 27.203-19.873 12.869-47.565 24.76-47.565 24.76s0 6.679-1.792 11.565c-1.792 5.050-4.561 8.308-6.353 10.262 7.656 15.801 15.312 31.438 22.968 47.239-6.679 3.258-13.194 6.516-19.873 9.936-7.819-15.963-15.801-32.090-23.619-48.053-10.914 0.163-21.013-5.701-26.226-15.312-5.538-10.262-4.398-22.968 3.095-32.253l-40.072-82.098z" />
<glyph unicode="&#xe904;" glyph-name="share" horiz-adv-x="896" d="M570.8 565.8l-188.2-94c1-7.8-0.4-14-0.4-23.8 0-8 1.4-14.2 0.4-23.8l188.2-94c34.4 33.4 81.4 53.8 133.2 53.8 106 0 192-84.2 192-192 0-106-86-192-192-192-107.8 0-192 86-192 192 0 9.8 0.4 16 1.4 23.8l-188.2 94c-34.4-33.4-81.4-53.8-133.2-53.8-106.040 0-192 86-192 192 0 107.8 85.96 192 192 192 51.8 0 98.8-20.4 133.2-53.8l188.2 94c-1 9.6-1.4 15.8-1.4 23.8 0 106.040 84.2 192 192 192 106 0 192-85.96 192-192 0-106-86-192-192-192-51.8 0-98.8 20.4-133.2 53.8v0z" />
<glyph unicode="&#xe905;" glyph-name="terminal" horiz-adv-x="1152" d="M18.744 786.74c-24.992 25-24.992 65.52 0 90.52 24.996 24.98 65.516 24.98 90.516 0l383.94-384.060c25-25 25-65.4 0-90.4l-383.94-384c-25-25-65.52-25-90.516 0-24.992 25-24.992 65.4 0 90.4l338.656 338.8-338.656 338.74zM1088 128c35.4 0 64-28.6 64-64s-28.6-64-64-64h-576c-35.4 0-64 28.6-64 64s28.6 64 64 64h576z" />
<glyph unicode="&#xe906;" glyph-name="at" d="M415.6 918.54c-186.9-36.64-337.4-187.32-374-374.2-55.28-281.8 137.3-532.4 398.2-570.2 38.020-5.776 72.34 24.52 72.34 62.98v1.326c0 31.48-22.88 57.76-53.68 62.48-168.7 25.96-298.4 172.26-298.4 348.4 0 205.8 177.22 371 386.8 350.8 183.080-17.738 317.2-182.5 317.2-366.4v-32.32c0-44.18-35.88-80.1-80-80.1s-80.020 35.92-80.020 80.1v240.2c0 17.694-14.322 32.040-32.020 32.040l-63.96-0.007c-14.598 0-26.4-9.984-30.24-23.36-49.7 24.3-108.48 32.76-172.12 10.212-77.5-27.46-136.24-97.82-147.44-179.28-18.966-138.020 87.62-256 221.8-256 52.88 0 100.86 19.088 139.18 49.76 48-62.6 130.46-97.38 218.8-74.98 92.36 21.409 153.96 111.809 152.16 205.609v41.8c0 298.4-267.8 531.264-574.6 471.14zM478.2 351.4c-52.94 0-96 43.12-96 96.1s43.060 96.1 96 96.1 96-43.12 96-96.1-41.2-96.1-96-96.1z" />
<glyph unicode="&#xe907;" glyph-name="mastodon" horiz-adv-x="896" d="M866 601.78c0 194.4-127.42 251.4-127.42 251.4-125.040 57.4-457.12 56.8-580.96 0 0 0-127.44-57-127.44-251.4 0-231.4-13.2-518.8 211.26-578.2 81.020-21.4 150.64-26 206.66-22.8 101.62 5.6 158.64 36.2 158.64 36.2l-3.4 73.8s-72.62-22.8-154.24-20.2c-80.82 2.8-166 8.8-179.26 108-1.147 8.146-1.801 17.557-1.801 27.12 0 0.239 0 0.478 0.001 0.717v-0.037c171.26-41.8 317.3-18.2 357.5-13.4 112.24 13.4 210 82.6 222.46 145.8 19.6 99.6 18 243 18 243zM715.76 351.38h-93.26v228.4c0 99.4-128 103.2-128-13.8v-125h-92.66v125.020c0 117-128 113.2-128 13.8v-228.4h-93.46c0 244.2-10.4 295.8 36.82 350 51.8 57.8 159.64 61.6 207.66-12.2l23.2-39 23.2 39c48.22 74.2 156.24 69.6 207.66 12.2 47.42-54.6 36.8-106 36.8-350z" />
<glyph unicode="&#xe908;" glyph-name="conlang" d="M512 800c-385.364 0-621.988-380.55-460.664-693.875l106.664 131.875 654 66 84-130-842.73-71.551c2.159-4.106 4.349-8.208 6.645-12.289l892.086 73.84 20.246-53.512c157.457 279.979-52.304 689.512-460.246 689.512zM410 490h204l54-66-328-30 70 96zM694 412l74-98-560-56 90 118 396 36z" />
<glyph unicode="&#xe909;" glyph-name="link" horiz-adv-x="1280" d="M1159.6 424.6c113 113 113 296 0 409-100 100-257.6 113-372.6 30.8l-3.2-2.2c-28.8-20.6-35.4-60.6-14.8-89.2s60.6-35.4 89.2-14.8l3.2 2.2c64.2 45.8 152 38.6 207.6-17.2 63-63 63-165 0-228l-224.4-224.8c-63-63-165-63-228 0-55.8 55.8-63 143.6-17.2 207.6l2.2 3.2c20.6 28.8 13.8 68.8-14.8 89.2s-68.8 13.8-89.2-14.8l-2.2-3.2c-82.4-114.8-69.4-272.4 30.6-372.4 113-113 296-113 409 0l224.6 224.6zM120.4 471.4c-113-113-113-296 0-409 100-100 257.6-113 372.6-30.8l3.2 2.2c28.8 20.6 35.4 60.6 14.8 89.2s-60.6 35.4-89.2 14.8l-3.2-2.2c-64.2-45.8-152-38.6-207.6 17.2-63 63.2-63 165.2 0 228.2l224.4 224.6c63 63 165 63 228 0 55.8-55.8 63-143.6 17.2-207.8l-2.2-3.2c-20.6-28.8-13.8-68.8 14.8-89.2s68.8-13.8 89.2 14.8l2.2 3.2c82.4 115 69.4 272.6-30.6 372.6-113 113-296 113-409 0l-224.6-224.6z" />
<glyph unicode="&#xe90a;" glyph-name="code" horiz-adv-x="1280" d="M829.6 878.42l-256-896.020c-9.8-34-45.2-53.6-79.2-44-34 9.8-53.6 45.2-44 79.2l256 895.98c9.8 33.988 45.2 53.668 79.2 43.956 34-9.71 53.6-45.136 44-79.116v0zM1037.2 717.2l224-224c25-25 25-65.4 0-90.4l-224-224c-25-25-65.4-25-90.4 0s-25 65.4 0 90.4l178.6 178.8-178.6 178.8c-25 25-25 65.4 0 90.4s65.4 25 90.4 0v0zM333.2 626.8l-178.7-178.8 178.7-178.8c25-25 25-65.4 0-90.4s-65.4-25-90.4 0l-224.056 224c-24.992 25-24.992 65.4 0 90.4l224.056 224c25 25 65.4 25 90.4 0s25-65.4 0-90.4v0z" />
<glyph unicode="&#xe90b;" glyph-name="fork" horiz-adv-x="896" d="M320 800c0-65.6-39.4-120.2-96-146.6v-175.6c37.6 21.8 81.4 34.2 128 34.2h192c70.6 0 128 57.4 128 128v13.4c-56.6 26.4-96 81-96 146.6 0 88.36 71.6 160 160 160s160-71.64 160-160c0-65.6-39.4-120.2-96-146.6v-13.4c0-141.4-114.6-256-256-256h-192c-70.6 0-128-57.4-128-128v-13.4c56.6-24.6 96-81 96-146.6 0-88.4-71.6-160-160-160-88.36 0-160 71.6-160 160 0 65.6 39.5 122 96 146.6v410.8c-56.5 26.4-96 81-96 146.6 0 88.36 71.64 160 160 160 88.4 0 160-71.64 160-160v0zM160 752c26.5 0 48 21.5 48 48s-21.5 48-48 48c-26.5 0-48-21.5-48-48s21.5-48 48-48zM736 848c-26.6 0-48-21.5-48-48s21.4-48 48-48c26.6 0 48 21.5 48 48s-21.4 48-48 48zM160 48c26.5 0 48 21.4 48 48s-21.5 48-48 48c-26.5 0-48-21.4-48-48s21.5-48 48-48z" />
<glyph unicode="&#xe90c;" glyph-name="house" horiz-adv-x="1152" d="M1151.6 449c0-36-30-64.2-64-64.2h-64l1.4-320.2c0-5.6-0.4-10.8-1-16.2v-32.4c0-44.2-35.8-80-80-80h-32c-2.2 0-4.4 1.8-6.6 0.2-2.8 1.6-5.6-0.2-8.4-0.2h-113c-44.2 0-80 35.8-80 80v176c0 35.4-28.6 64-64 64h-128c-35.4 0-64-28.6-64-64v-176c0-44.2-35.8-80-80-80h-111.8c-3 0-6 0.2-9 0.4-2.4-0.2-4.8-0.4-7.2-0.4h-32c-44.18 0-80 35.8-80 80v224c0 1.8 0.060 3.8 0.18 5.6v139.2h-64.080c-36.060 0-64.1 28.2-64.1 64.2 0 18 6.008 34 20.020 48l512.78 446.968c14 14.028 30 16.032 44 16.032s30-4.008 42.2-14.028l510.6-448.972c16-14 24.2-30 22-48v0z" />
<glyph unicode="&#xe90d;" glyph-name="language" horiz-adv-x="1280" d="M896 632c22 0 40-16.2 40-40v-8h120c22 0 40-16.2 40-40 0-22-18-40-40-40h-4l-3.2-9c-17.8-47.2-45-93.2-79.4-130.8 1.8-1 3.6-0.4 5.4-3.2l37.8-22.6c19-11.4 25-36 13.6-55-11.2-19-35.8-25-54.8-13.6l-37.8 22.6c-8.8 5.4-19.4 11-26.2 17-21-15-43.8-28-67.8-38.8l-7.4-3.2c-20.2-9-43.8 0.2-52.8 20.4s0.2 43.8 20.4 52.8l7.2 3.2c12.8 5.8 25.2 14 37 19.6l-24.2 24.4c-15.8 15.6-15.8 40.8 0 56.4 15.6 15.8 40.8 15.8 56.4 0l29.2-29 1.2 0.6c24.8 24.4 45 54.8 59.6 90h-214.2c-23.8 0-40 16.2-40 40 0 22 16.2 40 40 40h104v8c0 22 16.2 40 40 40v-1.8zM320 493.6l38-85.6h-77.8l39.8 85.6zM0 704c0 70.7 57.3 128 128 128h1024c70.6 0 128-57.3 128-128v-512c0-70.6-57.4-128-128-128h-1024c-70.7 0-128 57.4-128 128v512zM640 192h512v512h-512v-512zM356.6 608.2c-6.4 14.4-20.8 23.8-36.6 23.8s-30.2-9.4-36.6-23.8l-127.96-288c-8.96-18.4 0.12-43.8 20.32-52.8 20.18-9 43.84 0.2 52.84 20.4l17.8 42h147.2l17.8-42c9-20.2 32.6-29.4 52.8-20.4s29.4 34.4 20.4 52.8l-128 288z" />
<glyph unicode="&#xe90e;" glyph-name="mic-lines" horiz-adv-x="768" d="M384 256c106.060 0 192 85.94 192 192h-160c-17.6 0-32 14.4-32 32s14.4 32 32 32h160v64h-160c-17.6 0-32 14.4-32 32s14.4 32 32 32h160v65.8h-160c-17.672 0-32 14.328-32 32s14.328 32 32 32l160-1.8c0 106.060-85.94 192-192 192s-192-85.94-192-192v-320c0-106 84.2-192 192-192zM688 576c-26.6 0-48-21.4-48-46.2v-81.8c0-146.66-123.94-264.8-272.6-255.4-132.16 8.338-239.4 133.18-239.4 265.6v71.6c0 24.8-21.5 46.2-48 46.2s-48-21.4-48-46.2v-64.3c0-179.32 127.94-339.2 304-363.4v-70.1h-80c-36.38 0-65.68-30.36-63.92-67.14 0.78-16.46 15.52-28.86 31.92-28.86h320c16.444 0 31.14 12.432 31.92 28.86 1.68 36.74-27.52 67.14-63.92 67.14h-80v67.54c171.4 23.46 304 170.66 304 348.46v81.8c0 24.8-21.4 46.2-48 46.2z" />
<glyph unicode="&#xe90f;" glyph-name="question" horiz-adv-x="640" d="M408.6 895.98h-216.6c-105.88 0-192-86.12-192-192 0-35.34 28.62-62.2 64-62.2s64 28.64 64 62.2c0 35.28 28.68 64 64 64h216.6c57 0 103.4-46.38 103.4-103.58 0-39.44-21.94-74.94-61-94.66l-195.4-114.54c-21.4-11.6-31.6-32.6-31.6-55.2v-80c0-35.34 28.62-63.98 64-63.98s64 28.64 64 63.98v43.4l160 94c78.94 39.5 128 118.84 128 207 0 127.7-103.8 231.58-231.4 231.58zM288 160c-44.18 0-80-35.82-80-80s35.82-78.2 80-78.2 80 35.8 80 78.2-35.8 80-80 80z" />
<glyph unicode="&#xe910;" glyph-name="discord" horiz-adv-x="1280" d="M1049.062 820.328c-0.331 0.632-0.862 1.122-1.508 1.393l-0.020 0.007c-69.126 32.613-149.446 58.394-233.51 73.348l-5.862 0.864c-0.2 0.039-0.429 0.061-0.664 0.061-1.364 0-2.552-0.751-3.173-1.863l-0.009-0.018c-9.162-16.095-19.138-36.2-28.112-56.841l-1.688-4.359c-40.401 6.456-86.983 10.145-134.426 10.145s-94.023-3.689-139.472-10.794l5.046 0.65c-10.583 24.679-20.712 44.78-31.866 64.218l1.596-3.018c-0.669 1.124-1.878 1.866-3.26 1.866-0.208 0-0.412-0.017-0.61-0.049l0.022 0.003c-89.917-15.782-170.24-41.566-245.309-76.709l5.933 2.495c-0.662-0.286-1.201-0.752-1.568-1.338l-0.008-0.014c-152.458-227.676-194.222-449.754-173.734-669.082 0.124-1.122 0.692-2.092 1.521-2.743l0.009-0.007c83.919-62.742 181.476-113.306 286.742-146.499l6.908-1.879c0.327-0.102 0.702-0.16 1.092-0.16 1.236 0 2.333 0.59 3.027 1.503l0.007 0.009c20.992 28.236 40.943 60.215 58.246 93.782l1.828 3.902c0.254 0.49 0.402 1.069 0.402 1.683 0 1.595-1.004 2.956-2.415 3.485l-0.026 0.008c-35.78 13.792-65.987 28.482-94.765 45.347l3.029-1.641c-1.12 0.667-1.859 1.872-1.859 3.25 0 1.221 0.58 2.306 1.48 2.995l0.009 0.007c6.164 4.618 12.332 9.422 18.218 14.274 0.623 0.516 1.432 0.83 2.313 0.83 0.539 0 1.050-0.117 1.51-0.327l-0.023 0.009c192.458-87.834 400.82-87.834 591 0 0.455 0.221 0.99 0.351 1.556 0.351 0.873 0 1.673-0.308 2.298-0.822l-0.006 0.005c5.888-4.852 12.054-9.702 18.264-14.32 0.922-0.695 1.511-1.787 1.511-3.017 0-1.367-0.728-2.565-1.818-3.225l-0.017-0.009c-25.909-15.466-56.144-30.151-87.654-42.266l-4.126-1.394c-1.425-0.553-2.416-1.913-2.416-3.505 0-0.627 0.154-1.218 0.426-1.738l-0.010 0.021c19.628-37.639 39.545-69.579 61.585-99.876l-1.557 2.246c0.684-0.951 1.788-1.563 3.035-1.563 0.389 0 0.765 0.060 1.117 0.17l-0.026-0.007c112.357 34.95 210.088 85.528 296.679 150.197l-2.555-1.825c0.853 0.627 1.427 1.59 1.529 2.689l0.001 0.015c24.528 253.566-41.064 473.824-173.868 669.082zM444.982 284.84c-57.944 0-105.688 53.174-105.688 118.478s46.818 118.482 105.688 118.482c59.33 0 106.612-53.64 105.686-118.478 0-65.308-46.82-118.482-105.686-118.482zM835.742 284.84c-57.942 0-105.686 53.174-105.686 118.478s46.818 118.482 105.686 118.482c59.334 0 106.614-53.64 105.688-118.478 0-65.308-46.354-118.482-105.688-118.482z" />
<glyph unicode="&#xe911;" glyph-name="writefreely" horiz-adv-x="1494" d="M326.398 822.626c-67.928-27.714-164.043-198.071-111.689-197.965 11.689 0.026 27.462 19.506 44.156 54.547 30.149 63.267 71.106 92.993 97.712 70.916 21.255-17.641 21.513-89.775 0.91-254.979-39.539-316.996-6.135-421.177 135.046-421.177 82.517 0 169.528 63.124 226.677 164.445l25.359 44.964 4.578-63.285c20.127-278.251 408.917-148.508 518.99 173.19 86.012 251.376-51.838 536.687-202.038 418.17-64.428-50.843-4.336-141.484 66.875-100.878 111.357 63.494 127.429-242.113 21.46-408.032-84.677-132.585-214.644-193.552-277.853-130.344-32.561 32.562-33.85 102.731-5.484 298.609 18.047 124.625 25.9 232.713 22.694 312.261-1.122 27.769-2.859 28.573-61.669 28.573h-60.521l4.592-111.689c12.99-316.177-121.112-587.149-271.854-549.316-49.7 12.471-53.49 49.311-28.803 279.964 27.078 253.024 27.557 292.562 4.095 339.336-27.547 54.927-92.775 77.354-153.232 52.689z" />
<glyph unicode="&#xea96;" glyph-name="twitter" d="M1024 733.6c-37.6-16.8-78.2-28-120.6-33 43.4 26 76.6 67.2 92.4 116.2-40.6-24-85.6-41.6-133.4-51-38.4 40.8-93 66.2-153.4 66.2-116 0-210-94-210-210 0-16.4 1.8-32.4 5.4-47.8-174.6 8.8-329.4 92.4-433 219.6-18-31-28.4-67.2-28.4-105.6 0-72.8 37-137.2 93.4-174.8-34.4 1-66.8 10.6-95.2 26.2 0-0.8 0-1.8 0-2.6 0-101.8 72.4-186.8 168.6-206-17.6-4.8-36.2-7.4-55.4-7.4-13.6 0-26.6 1.4-39.6 3.8 26.8-83.4 104.4-144.2 196.2-146-72-56.4-162.4-90-261-90-17 0-33.6 1-50.2 3 93.2-59.8 203.6-94.4 322.2-94.4 386.4 0 597.8 320.2 597.8 597.8 0 9.2-0.2 18.2-0.6 27.2 41 29.4 76.6 66.4 104.8 108.6z" />
<glyph unicode="&#xea9b;" glyph-name="rss" d="M136.294 209.070c-75.196 0-136.292-61.334-136.292-136.076 0-75.154 61.1-135.802 136.292-135.802 75.466 0 136.494 60.648 136.494 135.802-0.002 74.742-61.024 136.076-136.494 136.076zM0.156 612.070v-196.258c127.784 0 247.958-49.972 338.458-140.512 90.384-90.318 140.282-211.036 140.282-339.3h197.122c-0.002 372.82-303.282 676.070-675.862 676.070zM0.388 960v-196.356c455.782 0 826.756-371.334 826.756-827.644h196.856c0 564.47-459.254 1024-1023.612 1024z" />
<glyph unicode="&#xea9d;" glyph-name="youtube" d="M1013.8 652.8c0 0-10 70.6-40.8 101.6-39 40.8-82.6 41-102.6 43.4-143.2 10.4-358.2 10.4-358.2 10.4h-0.4c0 0-215 0-358.2-10.4-20-2.4-63.6-2.6-102.6-43.4-30.8-31-40.6-101.6-40.6-101.6s-10.2-82.8-10.2-165.8v-77.6c0-82.8 10.2-165.8 10.2-165.8s10-70.6 40.6-101.6c39-40.8 90.2-39.4 113-43.8 82-7.8 348.2-10.2 348.2-10.2s215.2 0.4 358.4 10.6c20 2.4 63.6 2.6 102.6 43.4 30.8 31 40.8 101.6 40.8 101.6s10.2 82.8 10.2 165.8v77.6c-0.2 82.8-10.4 165.8-10.4 165.8zM406.2 315.2v287.8l276.6-144.4-276.6-143.4z" />
<glyph unicode="&#xea9f;" glyph-name="twitch" d="M96 960l-96-160v-736h256v-128h128l128 128h160l288 288v608h-864zM832 416l-160-160h-160l-128-128v128h-192v576h640v-416zM608 704h96v-256h-96v256zM416 704h96v-256h-96v256z" />
<glyph unicode="&#xeab0;" glyph-name="github" d="M512.008 947.358c-282.738 0-512.008-229.218-512.008-511.998 0-226.214 146.704-418.132 350.136-485.836 25.586-4.738 34.992 11.11 34.992 24.632 0 12.204-0.48 52.542-0.696 95.324-142.448-30.976-172.504 60.41-172.504 60.41-23.282 59.176-56.848 74.916-56.848 74.916-46.452 31.778 3.51 31.124 3.51 31.124 51.4-3.61 78.476-52.766 78.476-52.766 45.672-78.27 119.776-55.64 149.004-42.558 4.588 33.086 17.852 55.68 32.506 68.464-113.73 12.942-233.276 56.85-233.276 253.032 0 55.898 20.004 101.574 52.76 137.428-5.316 12.9-22.854 64.972 4.952 135.5 0 0 43.006 13.752 140.84-52.49 40.836 11.348 84.636 17.036 128.154 17.234 43.502-0.198 87.336-5.886 128.256-17.234 97.734 66.244 140.656 52.49 140.656 52.49 27.872-70.528 10.35-122.6 5.036-135.5 32.82-35.856 52.694-81.532 52.694-137.428 0-196.654-119.778-239.95-233.79-252.624 18.364-15.89 34.724-47.046 34.724-94.812 0-68.508-0.596-123.644-0.596-140.508 0-13.628 9.222-29.594 35.172-24.566 203.322 67.776 349.842 259.626 349.842 485.768 0 282.78-229.234 511.998-511.992 511.998z" />
<glyph unicode="&#xeac6;" glyph-name="reddit" d="M256 320c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM640 320c0 35.346 28.654 64 64 64s64-28.654 64-64c0-35.346-28.654-64-64-64s-64 28.654-64 64zM643.112 183.222c16.482 12.986 40.376 10.154 53.364-6.332s10.152-40.378-6.334-53.366c-45.896-36.158-115.822-59.524-178.142-59.524-62.322 0-132.248 23.366-178.144 59.522-16.486 12.99-19.32 36.882-6.332 53.368 12.99 16.482 36.882 19.318 53.366 6.332 26.422-20.818 78.722-43.222 131.11-43.222s104.688 22.404 131.112 43.222zM1024 448c0 70.692-57.308 128-128 128-48.116 0-89.992-26.57-111.852-65.82-65.792 35.994-145.952 59.246-233.28 64.608l76.382 171.526 146.194-42.2c13.152-37.342 48.718-64.114 90.556-64.114 53.020 0 96 42.98 96 96s-42.98 96-96 96c-36.56 0-68.342-20.442-84.554-50.514l-162.906 47.024c-18.224 5.258-37.538-3.722-45.252-21.052l-103.77-233.026c-85.138-5.996-163.262-29.022-227.636-64.236-21.864 39.25-63.766 65.804-111.882 65.804-70.692 0-128-57.308-128-128 0-52.312 31.402-97.254 76.372-117.102-8.070-24.028-12.372-49.104-12.372-74.898 0-176.73 200.576-320 448-320 247.422 0 448 143.27 448 320 0 25.792-4.3 50.862-12.368 74.886 44.97 19.85 76.368 64.802 76.368 117.114zM864 772c19.882 0 36-16.118 36-36s-16.118-36-36-36-36 16.118-36 36 16.118 36 36 36zM64 448c0 35.29 28.71 64 64 64 25.508 0 47.572-15.004 57.846-36.646-33.448-25.366-61.166-54.626-81.666-86.738-23.524 9.47-40.18 32.512-40.18 59.384zM512 12c-205.45 0-372 109.242-372 244s166.55 244 372 244c205.45 0 372-109.242 372-244s-166.55-244-372-244zM919.82 388.616c-20.5 32.112-48.218 61.372-81.666 86.738 10.276 21.642 32.338 36.646 57.846 36.646 35.29 0 64-28.71 64-64 0-26.872-16.656-49.914-40.18-59.384z" />
<glyph unicode="&#xeac9;" glyph-name="linkedin" d="M928 960h-832c-52.8 0-96-43.2-96-96v-832c0-52.8 43.2-96 96-96h832c52.8 0 96 43.2 96 96v832c0 52.8-43.2 96-96 96zM384 128h-128v448h128v-448zM320 640c-35.4 0-64 28.6-64 64s28.6 64 64 64c35.4 0 64-28.6 64-64s-28.6-64-64-64zM832 128h-128v256c0 35.4-28.6 64-64 64s-64-28.6-64-64v-256h-128v448h128v-79.4c26.4 36.2 66.8 79.4 112 79.4 79.6 0 144-71.6 144-160v-288z" />
<glyph unicode="&#xeae7;" glyph-name="git" d="M1004.692 493.606l-447.096 447.080c-25.738 25.754-67.496 25.754-93.268 0l-103.882-103.876 78.17-78.17c12.532 5.996 26.564 9.36 41.384 9.36 53.020 0 96-42.98 96-96 0-14.82-3.364-28.854-9.362-41.386l127.976-127.974c12.532 5.996 26.566 9.36 41.386 9.36 53.020 0 96-42.98 96-96s-42.98-96-96-96-96 42.98-96 96c0 14.82 3.364 28.854 9.362 41.386l-127.976 127.974c-3.042-1.456-6.176-2.742-9.384-3.876v-266.968c37.282-13.182 64-48.718 64-90.516 0-53.020-42.98-96-96-96s-96 42.98-96 96c0 41.796 26.718 77.334 64 90.516v266.968c-37.282 13.18-64 48.72-64 90.516 0 14.82 3.364 28.852 9.36 41.384l-78.17 78.17-295.892-295.876c-25.75-25.776-25.75-67.534 0-93.288l447.12-447.080c25.738-25.75 67.484-25.75 93.268 0l445.006 445.006c25.758 25.762 25.758 67.54-0.002 93.29z" />
</font></defs></svg>

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.4 KiB

View File

@ -1,56 +0,0 @@
each(range(5), {
.gap-@{value}rem {
gap: @value * 1rem;
}
});
.flex {
display: flex;
}
.flex-inline {
display: inline-flex;
}
.flex-inline-col {
.flex-inline();
flex-direction: column;
}
.flex-col {
.flex();
flex-direction: column;
}
.flex-row {
.flex();
flex-direction: row;
}
@flex-justifications-prefixed: flex-start, flex-end;
each(@flex-justifications-prefixed, {
.@{value} {
.flex();
justify-content: @value;
}
});
@flex-justifications: center, space-between, space-around, space-evenly;
each(@flex-justifications, {
.flex-@{value} {
.flex();
justify-content: @value;
}
});
.rounded-corners {
border-radius: 0.3rem;
}
.center {
margin: 0 auto;
}
.card-width {
max-width: 35rem;
}

View File

@ -1,112 +0,0 @@
@font-face {
font-family: "phunic";
src: url("/fonts/phunic.eot");
src: url("/fonts/phunic.eot#iefix") format("embedded-opentype"),
url("/fonts/phunic.ttf") format("truetype"),
url("/fonts/phunic.woff") format("woff"),
url("/fonts/phunic.svg#phunic") format("svg");
font-weight: normal;
font-style: normal;
font-display: block;
}
i.icon {
/* use !important to prevent issues with browser extensions that change fonts */
font-family: "phunic" !important;
speak: never;
font-style: normal;
font-weight: normal;
font-variant: normal;
text-transform: none;
line-height: 1;
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
&::before {
width: 1.4rem;
display: inline-block;
align-content: center;
text-align: center;
}
}
.phunic-envelope:before {
content: "\e901";
}
.phunic-discord:before {
content: "\e910";
}
.phunic-writefreely:before {
content: "\e911";
}
.phunic-mastodon:before {
content: "\e907";
}
.phunic-link:before {
content: "\e909";
}
.phunic-star:before {
content: "\e900";
}
.phunic-share:before {
content: "\e904";
}
.phunic-terminal:before {
content: "\e905";
}
.phunic-at:before {
content: "\e906";
}
.phunic-conlang:before {
content: "\e908";
}
.phunic-code:before {
content: "\e90a";
}
.phunic-fork:before {
content: "\e90b";
}
.phunic-house:before {
content: "\e90c";
}
.phunic-language:before {
content: "\e90d";
}
.phunic-mic-lines:before {
content: "\e90e";
}
.phunic-question:before {
content: "\e90f";
}
.phunic-emacs:before {
content: "\e902";
}
.phunic-gitea:before {
content: "\e903";
}
.phunic-twitter:before {
content: "\ea96";
}
.phunic-rss:before {
content: "\ea9b";
}
.phunic-youtube:before {
content: "\ea9d";
}
.phunic-twitch:before {
content: "\ea9f";
}
.phunic-github:before {
content: "\eab0";
}
.phunic-reddit:before {
content: "\eac6";
}
.phunic-linkedin:before {
content: "\eac9";
}
.phunic-git:before {
content: "\eae7";
}

View File

@ -1,174 +0,0 @@
/*
* Nord Theme:
* - Copyright (c) 2016-present Arctic Ice Studio <development@arcticicestudio.com>
* - Copyright (c) 2016-present Sven Greb <development@svengreb.de>
*/
:root {
--nord0: #2e3440;
--nord1: #3b4252;
--nord2: #434c5e;
--nord3: #4c566a;
--nord4: #d8dee9;
--nord5: #e5e9f0;
--nord6: #eceff4;
--nord7: #8fbcbb;
--nord8: #88c0d0;
--nord9: #81a1c1;
--nord10: #5e81ac;
--nord11: #bf616a;
--nord12: #d08770;
--nord13: #ebcb8b;
--nord14: #a3be8c;
--nord15: #b48ead;
scroll-behavior: smooth;
// brand colors
--c-brand: var(--nord10);
--c-brand-light: var(--nord9);
// background colors
--c-bg: var(--nord6);
--c-bg-light: var(--nord6);
--c-bg-lighter: var(--nord5);
--c-bg-dark: var(--nord5);
--c-bg-darker: var(--nord4);
--c-bg-navbar: var(--c-bg);
--c-bg-sidebar: var(--c-bg);
--c-bg-arrow: var(--nord4);
// text colors
--c-text: var(--nord1);
--c-text-accent: var(--c-brand);
--c-text-light: var(--nord2);
--c-text-lighter: var(--nord3);
--c-text-lightest: var(--nord4);
--c-text-quote: var(--nord2);
// border colors
--c-border: var(--nord4);
--c-border-dark: var(--nord4);
// custom container colors
--c-tip: var(--nord14);
--c-tip-bg: var(--c-bg);
--c-tip-title: var(--c-text);
--c-tip-text: var(--c-text);
--c-tip-text-accent: var(--c-text-accent);
--c-warning: var(--nord13);
--c-warning-bg: var(--c-bg);
--c-warning-bg-light: var(--c-bg-light);
--c-warning-bg-lighter: var(--c-bg-lighter);
--c-warning-border-dark: var(--nord3);
--c-warning-details-bg: var(--c-bg);
--c-warning-title: var(--nord12);
--c-warning-text: var(--nord12);
--c-warning-text-accent: var(--nord12);
--c-warning-text-light: var(--nord12);
--c-warning-text-quote: var(--nord12);
--c-danger: var(--nord11);
--c-danger-bg: var(--c-bg);
--c-danger-bg-light: var(--c-bg-light);
--c-danger-bg-lighter: var(--c-bg-light);
--c-danger-border-dark: var(--nord11);
--c-danger-details-bg: var(--nord2);
--c-danger-title: var(--nord11);
--c-danger-text: var(--nord11);
--c-danger-text-accent: var(--nord11);
--c-danger-text-light: var(--nord11);
--c-danger-text-quote: var(--nord11);
--c-details-bg: var(--c-bg-lighter);
// badge component colors
--c-badge-tip: var(--c-tip);
--c-badge-warning: var(--c-warning);
--c-badge-warning-text: var(--c-bg);
--c-badge-danger: var(--c-danger);
--c-badge-danger-text: var(--c-bg);
// transition vars
--t-color: 0.3s ease;
--t-transform: 0.3s ease;
// code blocks vars
--code-bg-color: var(--nord0);
--code-hl-bg-color: var(--nord1);
--code-ln-color: #9e9e9e;
--code-ln-wrapper-width: 3.5rem;
// font vars
--font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen,
Ubuntu, Cantarell, "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
--font-family-code: Consolas, Monaco, "Andale Mono", "Ubuntu Mono", monospace;
// layout vars
--navbar-height: 3.6rem;
--navbar-padding-v: 0.7rem;
--navbar-padding-h: 1.5rem;
--sidebar-width: 20rem;
--sidebar-width-mobile: calc(var(--sidebar-width) * 0.82);
--content-width: 740px;
--homepage-width: 960px;
}
html.dark {
// brand colors
--c-brand: var(--nord14);
--c-brand-light: var(--nord14);
// background colors
--c-bg: var(--nord1);
--c-bg-light: var(--nord2);
--c-bg-lighter: var(--nord2);
--c-bg-dark: var(--nord3);
--c-bg-darker: var(--nord3);
// text colors
--c-text: var(--nord4);
--c-text-light: var(--nord5);
--c-text-lighter: var(--nord5);
--c-text-lightest: var(--nord6);
--c-text-quote: var(--c-text);
// border colors
--c-border: var(--nord3);
--c-border-dark: var(--nord3);
// custom container colors
--c-tip: var(--nord14);
--c-warning: var(--nord13);
--c-warning-bg: var(--c-bg);
--c-warning-bg-light: var(--c-bg-light);
--c-warning-bg-lighter: var(--c-bg-lighter);
--c-warning-border-dark: var(--nord3);
--c-warning-details-bg: var(--c-bg);
--c-warning-title: var(--nord13);
--c-warning-text: var(--nord13);
--c-warning-text-accent: var(--nord13);
--c-warning-text-light: var(--nord13);
--c-warning-text-quote: var(--nord13);
--c-danger: var(--nord11);
--c-danger-bg: var(--c-bg);
--c-danger-bg-light: var(--c-bg-light);
--c-danger-bg-lighter: var(--c-bg-light);
--c-danger-border-dark: var(--nord11);
--c-danger-details-bg: var(--nord2);
--c-danger-title: var(--nord11);
--c-danger-text: var(--nord11);
--c-danger-text-accent: var(--nord11);
--c-danger-text-light: var(--nord11);
--c-danger-text-quote: var(--nord11);
--c-details-bg: var(--c-bg-light);
// badge component colors
--c-badge-warning-text: var(--nord0);
--c-badge-danger-text: var(--nord0);
// code blocks vars
--code-hl-bg-color: var(--nord2);
}

View File

@ -1,56 +0,0 @@
const pages: string[] = [
'/index.md',
'/find-me.md',
'/resume.md',
'/projects.md',
'/conlanging.md',
'/vocal-synthesis.md',
'/about.md',
'/privacy.md',
];
const localePages = (languagePrefix: string) => {
return pages.map((page: string) => `/${languagePrefix}${page}`);
};
export const themeLocales = {
'/': {
selectLanguageName: 'Français',
tip: 'nota bene',
warning: 'attention',
sidebar: pages,
notFound: [
'Cest bien vide ici',
'Pourquoi sommes-nous ici?',
'Erreur 404',
'Le lien ne semble pas être correct',
],
backToHome: 'Retour accueil',
openInNewWindow: 'Ouvrir dans une nouvelle fenêtre',
toggleColorMode: 'Changer de thème',
toggleSidebar: 'Barre latérale',
lastUpdatedText: 'Dernière mise à jour',
},
'/lfn/': {
selectLanguageName: 'Elefen',
tip: 'avisa',
warning: 'averti',
danger: 'peril',
sidebar: localePages('lfn'),
notFound: [
'Ce? Se no ave no cosa asi',
'A do vade tu?',
'Era 404',
'La lia no es coreta',
],
backToHome: 'reversa a la paja prima',
openInNewWindow: 'abri en un nova fenetra',
toggleColorMode: 'cambia la colores',
toggleSidebar: 'bara ladal',
lastUpdatedText: 'Ultima refresci',
},
'/en/': {
selectLanguageName: 'English',
sidebar: localePages('en'),
},
};

View File

@ -1,106 +0,0 @@
export interface GithubRepo {
id: number;
node_id: string;
name: string;
full_name: string;
private: boolean;
owner: Owner;
html_url: string;
description: string;
fork: boolean;
url: string;
forks_url: string;
keys_url: string;
collaborators_url: string;
teams_url: string;
hooks_url: string;
issue_events_url: string;
events_url: string;
assignees_url: string;
branches_url: string;
tags_url: string;
blobs_url: string;
git_tags_url: string;
git_refs_url: string;
trees_url: string;
statuses_url: string;
languages_url: string;
stargazers_url: string;
contributors_url: string;
subscribers_url: string;
subscription_url: string;
commits_url: string;
git_commits_url: string;
comments_url: string;
issue_comment_url: string;
contents_url: string;
compare_url: string;
merges_url: string;
archive_url: string;
downloads_url: string;
issues_url: string;
pulls_url: string;
milestones_url: string;
notifications_url: string;
labels_url: string;
releases_url: string;
deployments_url: string;
created_at: string;
updated_at: string;
pushed_at: string;
git_url: string;
ssh_url: string;
clone_url: string;
svn_url: string;
homepage: string;
size: number;
stargazers_count: number;
watchers_count: number;
language: string;
has_issues: boolean;
has_projects: boolean;
has_downloads: boolean;
has_wiki: boolean;
has_pages: boolean;
forks_count: number;
mirror_url: null;
archived: boolean;
disabled: boolean;
open_issues_count: number;
license: null;
allow_forking: boolean;
is_template: boolean;
web_commit_signoff_required: boolean;
topics: any[];
visibility: string;
forks: number;
open_issues: number;
watchers: number;
default_branch: string;
}
export interface Owner {
login: string;
id: number;
node_id: string;
avatar_url: string;
gravatar_id: string;
url: string;
html_url: string;
followers_url: string;
following_url: string;
gists_url: string;
starred_url: string;
subscriptions_url: string;
organizations_url: string;
repos_url: string;
events_url: string;
received_events_url: string;
type: string;
site_admin: boolean;
}
export interface GithubError {
message: string;
documentation_url: string;
}

View File

@ -1,24 +0,0 @@
#+setupfile: ./headers
#+language: fr
* À Propos
** Introduction
Ceci est le site web personnel de Lucien Cartier-Tilet, aussi connu
sous le nom de « Pundrak » ou « Phundrak ».
Il est écrit avec [[https://v2.vuepress.vuejs.org/][Vuepress]] et est entièrement open-source. Vous pouvez
trouver son code source sur [[https://labs.phundrak.com/phundrak/phundrak.com][mon instance personnelle Gitea]]. Les icônes
utilisées sur ce site proviennent de plusieurs sources différentes :
- [[https://icomoon.io][IcoMoon]], que jutilise pour consolider toutes les icônes dans une
même fonte, y compris des icônes de leur pack par défaut,
- [[https://fontawesome.com/][FontAwesome]] doù viennent la majorité des icônes (leur
implémentation de leur paquet pour Vue laisse à mon avis plus quà
désirer),
- La {{{icon(conlang)}}} [[https://conlang.org/][Société de Création de Langues]] dont jai modifié
leur logo afin de créer licône pour mes langues construites,
- {{{icon(emacs)}}} [[https://www.gnu.org/software/emacs/][Emacs]] et {{{icon(writefreely)}}} [[https://writefreely.org/][WriteFreely]] dont jai recréé
une partie de leur logo respectif en SVG afin den créer une icône,
- {{{icon(gitea)}}} [[https://gitea.io/][Gitea]] dont jai modifié le logo en SVG pour lavoir en
monochrome.
#+include: other-links

View File

@ -1,39 +0,0 @@
#+setupfile: ./headers
#+language: fr
* Création de langues
Les /idéolangues/, ou /langues construites/ (en anglais /conlang/), sont des
langues construites et artificielles, nées de lesprit dune ou
parfois quelques personnes. Elles se distinguent ainsi des /langues
naturelles/ qui sont des langues ayant naturellement évolué depuis
dautres langues plus anciennes, comme le Français, lAnglais, le
Mandarin, le Japonais, le bahasa ou le !xhosa (oui, le point
dexclamation fait partie de lorthographe du nom de la langue).
Les idéolangues peuvent avoir différents buts lors de leur création,
par exemple :
- être parlées comme des langues naturelles par des individus afin de
servir de /lingua franca/ entre plusieurs communautés linguistiques,
comme le célèbre [[https://en.wikipedia.org/wiki/Esperanto][espéranto]] ou bien la [[https://elefen.org][lingua franca nova]]
- être une langue secrète que seules quelques personnes connaissent
afin de communiquer entre eux sans que dautres personnes puissent
comprendre, un peu comme un argot, mais plus poussé encore
- être une expérience concrète de linguistique, comme le [[https://en.wikipedia.org/wiki/Lojban][lojban]] qui
essaie dêtre la langue la plus logique qui soit
- complémenter un univers littéraire, comme les langues elfiques de
Tolkien ou le klingon de Star Trek
- juste être une forme dart, comme la peinture ou la poésie
Dans mon cas, les deux dernières justifications sont celles qui me
poussent à créer de nouvelles langues. Mes deux projets principaux
actuellement sont le [[https://conlang.phundrak.com/proto-nyqy][proto-ñyqy]] et l[[https://conlang.phundrak.com/eittlandic][éittlandais]]. La première est une
langue racine qui me permettra de développer toute une famille de
langues dans mon univers littéraire, tandis que la seconde sinscrit
dans un exercice créatif de création dun pays fictif présent dans
notre monde.
Plus dinformations peuvent être trouvées sur [[https://conlang.phundrak.com/][mon site
didéolinguistique]] (en anglais)
#+include: other-links

View File

@ -1,22 +0,0 @@
#+setupfile: ../headers
#+language: en
* About
** Introduction
This is the personal website of Lucien “Phundrak” Cartier-Tilet.
It is written with [[https://v2.vuepress.vuejs.org/][Vuepress]] and is completely open-source. You can
find the source code on my [[https://labs.phundrak.com/phundrak/phundrak.com][personal Gitea instance]]. Icons used on this
website come from different sources:
- [[https://icomoon.io/][IcoMoon]] which I use to consolidate all the icons used in one font,
including some icons from their default pack
- [[https://fontawesome.com/][FontAwesome]] from which most icons come from --- their Vue package
is, in my opinion, really not usable
- The {{{icon(conlang)}}} [[https://conlang.org/][Language Creation Society]] whose logo I modified in
order to create the icon used here when referring to my constructed
languages
- {{{icon(emacs)}}} [[https://www.gnu.org/software/emacs/][Emacs]] and {{{icon(writefreely)}}} [[https://writefreely.org/][WriteFreely]] whose respective
logo I partially remade as an SVG file in order to create an icon.
- {{{icon(gitea)}}} [[https://gitea.io][Gitea]] whose logo I modified to be monochromatic
#+include: other-links

View File

@ -1,28 +0,0 @@
#+setupfile: ../headers
#+language: en
* Conlanging
/Conlangs/, short for /constructed languages/, are artificial
languages born out of the mind of a single individual (sometimes a
couple of them), unlike natural languages born through countless
iterations by their native speakers, slowly evolving over time like
English, French, Mandarin, Japanese, Bahasa, or !Xhosa did.
They can serve various goals from their creators:
- be spoken by as many people as possible as a neutral language, like
[[https://en.wikipedia.org/wiki/Esperanto][Esperanto]] and [[https://elefen.org][Lingua Franca Nova]]
- be a secret language between a couple of people
- as a thought experiment, like [[https://en.wikipedia.org/wiki/Lojban][Lojban]]
- fill a litterary universe, like Tolkiens elvish languages or Star
Treks Klingon
- for the sake of art itself
In my case, the last two reasons are the main ones driving me to
create languages. My two main projects at the time of writing this
page are [[https://conlang.phundrak.com/proto-nyqy][Proto-Ñyqy]] and [[https://conlang.phundrak.com/eittlandic][Eittlandic]]. Both are accompanied by their own
worldbuilding project, although Proto-Ñyqys worldbuilding is still
largely secret while Eittlands worldbuilding is mostly public.
More information can be found on my [[https://conlang.phundrak.com/][conlanging website]].
#+include: other-links

View File

@ -1,31 +0,0 @@
#+setupfile: ../headers
#+language: en
* Where to find me
I am on various websites and some social networks where you can follow
me.
** Social Networks
- {{{icon(mastodon)}}} *Mastodon* :: [[https://mastodon.phundrak.com/@phundrak][@phundrak@mastodon.phundrak.com]]
- {{{icon(twitter)}}} *Twitter* :: [[https://twitter.com/phundrak][@phundrak]], though I harldy use it anymore
and mostly reshare my Mastodon messages when I think to, and
sometimes they get truncated
- {{{icon(writefreely)}}} *Writefreely* ::
- [[https://write.phundrak.com/phundrak][@phundrak@write.phundrak.com]] : blog alternative
- [[https://write.phundrak.com/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: short stories,
mainly in French for now
- {{{icon(discord)}}} *Discord* :: =@phundrak= (tell me you come from here,
otherwise theres a chance Ill consider your message as spam)
** Other Websites
- {{{icon(envelope)}}} *Email* :: [[mailto:lucien@phundrak.com][lucien@phundrak.com]]
- {{{icon(rss)}}} *Blog* :: [[https://blog.phundrak.com][blog.phundrak.com]]
- {{{icon(gitea)}}} *Gitea* :: [[https://labs.phundrak.com/phundrak][@phundrak@labs.phundrak.com]]
- {{{icon(github)}}} *GitHub* :: [[https://github.com/Phundrak][Phundrak]]
- {{{icon(youtube)}}} *YouTube* :: [[https://www.youtube.com/@phundrak][@phundrak]]
- {{{icon(reddit)}}} *Reddit* :: [[https://www.reddit.com/user/phundrak][/u/phundrak]]
- {{{icon(linkedin)}}} *LinkedIn* :: [[https://www.linkedin.com/in/lucien-cartier-tilet/][Lucien Cartier-Tilet]]
- {{{icon(twitch)}}} *Twitch* :: [[https://www.twitch.tv/phundrak][phundrak]]
#+include: other-links

View File

@ -1,29 +0,0 @@
#+setupfile: ../headers
#+language: en
* Home
Hi, Im Lucien Cartier-Tilet, a consultant working at [[https://aubay.com][Aubay]].
I studied for my Masters 2 degree in THYP (in French: /Technologies de
lHypermédia/, in English: /Hypermedia Technologies/) at the Université
Vincennes Saint-Denis (Paris 8).
I worked at VoxWave from 2012 to 2018 as its co-founder and CTO.
During that time, I developed French singing vocal libraries for vocal
synthesizers, known as ALYS and LEORA.
Im a free software enthusiast, using GNU/Linux since 2008 and Emacs
since 2016.
I spend my personnal programming projects as well as on my constructed
worlds and languages. I also like to go climbing, and hiking whenever
I have the opportunity to.
I speak natively French, and English at a native level. I also speak
some Japanese, [[https://elefen.org][Lingua Franca Nova]], and Norwegian Bokmål.
#+begin_export html
This website is also available on Gemini as [gmi.phundrak.com/en](gemini://gmi.phundrak.com/en)!
#+end_export
#+include: other-links

View File

@ -1,190 +0,0 @@
#+setupfile: ../headers
#+language: en
* BSUP01 Keine Tashi
** Introduction
KEINE Tashi is a character and set of vocal libraries developed for
the shareware [[http://utau2008.web.fc2.com/][UTAU]], a singing voice synthesizer. I developed KEINE
Tashi over the course of several years, from 2012 to 2015. Three vocal
libraries have been released to the public, the most used one being
his *JPN Power Extend* one. On March 10th, 2017, I announced I would
cease any kind of activity related to UTAU.
#+begin_export html
<blockquote class="twitter-tweet" data-dnt="true" data-theme="dark"><p
lang="en" dir="ltr">Id like to also announce that from now on I am
dropping my previous UTAU projects other than covers and wont develop
any new UTAU library</p>— Pundrak (@Phundrak) <a
href="https://twitter.com/Phundrak/status/840174634377105408?ref_src=twsrc%5Etfw">March
10, 2017</a></blockquote> <component is="script" async
src="https://platform.twitter.com/widgets.js"
charset="utf-8"></component>
#+end_export
** Character and vocal libraries
Heres a copy and paste of some old pages describing KEINE Tashi:
*** Presentation
#+begin_export html
<ResponsiveImage
src="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_1024.webp"
:width="1024"
preview="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_512.webp"
:previewWidth="512">
Illustration de KEINE Tashi par Umi
</ResponsiveImage>
#+end_export
- Codename :: BSUP01 恵音བཀྲ་ཤིས་ KEINE Tashi
- First name :: Tashi (བཀྲ་ཤིས་), Tibetan name meaning “auspicious”
- Last name :: Keine (恵音), Japanese name meaning “Blessing sound”.
It reads as “keine”, although its regular reading should be
“megumine”.
- Model :: BSUP (Bödkay Shetang UTAU Project)
- Number :: 01
- Gender :: male
- Birthday (lore) :: June 28th, 1991
- Birthday (first release) :: October 14th, 2012
- Weight :: 154 lb / 70 kg
- Heigh :: 60″ / 182 cm (very tall for a Tibetan)
- Hair color :: black
- Eyes color :: brown~black
- Appearance :: Tashi wears a modernized Tibetan suit from the Amdo
Region (Chinese: 安多 Ānduō), colored in blue. He also wears some
turquoise jeweleries.
- Favorite food :: meat momo (Tibetan raviolies)
- Character item :: a Tibetan manuscript
- Voice and creator :: [[https://phundrak.com][Phundrak]] (me)
- Likes :: to meditate, calligraphy, old books, manuscripts (is that
a self-insert?)
- Dislikes :: selfishness, lies, arrogance
- Personality :: Tashi is somebody very calm, sweet. He really enjoys
old books and manuscripts, and he LOVES meditate! He's never hungry,
so, he can stay meditating for 2~3 days meditating, just like that,
until he realizes that he should eat something. And he always keeps
quiet, it's really hard to make him angry.
But when he is, his anger becomes wrath. Anyone who experienced it
can attest how complex and difficult it is to calm him down.
Strangely enough, shortly after being confronted by Tashi, the
victims of this wrath see their quality of life greatly improve.
Maybe these people needed to hear some truths they refused to face
before?
*** Vocal libraries
**** JPN VCV
- Download link ::
| Extension | Size | Link |
|-----------+----------+------|
| 7z | 25.7 MiB | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_VCV.7z][DL]] |
| tar.xz | 32.5 MiB | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_VCV.tar.xz][DL]] |
| zip | 38.0 MiB | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_VCV.zip][DL]] |
- File size :: 60.7 MB
- Total uncompressed size :: 94.4 MB
- Number of voice phonemes :: 1264 (253 audio files)
- Average frequency :: G#2
- Vocal range :: C2~D3
- FRQ file presence :: partial
- Release date :: October, 14th 2012
- Phoneme encoding :: Romaji with hiragana and CV romaji aliases
- Supported languages :: Japanese
- oto.ini :: Tuned myself
- Recommended engines :: TIPS, VS4U
**** JPN Extend Power
- Download link ::
| Extension | Size | Link |
|-----------+--------+------|
| 7z | 1.1Gio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Power.7z][DL]] |
| tar.xz | 1.1Gio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Power.tar.xz][DL]] |
| zip | 1.2Gio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Power.zip][DL]] |
- File size :: 114 MB
- Total uncompressed size :: 155 MB
- Number of voice phonemes :: 3020 (546 audio files)
- Average frequency :: C3
- Vocal range :: B1~D4
- FRQ file presence :: partial
- Release date :: June 28th, 2013
- Phoneme encoding :: Romaji (hiragana aliases)
- Supported languages :: Japanese
- oto.ini :: Tuned myself
- Recommended engines :: VS4U, world4utau
**** JPN Extend Youth
- Download link ::
| Extension | Size | Link |
|-----------+----------+------|
| 7z | 237.7Mio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Youth.7z][DL]] |
| tar.xz | 243.5Mio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Youth.tar.xz][DL]] |
| zip | 268.7Mio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Youth.zip][DL]] |
- File size :: 36.9 MB
- Total uncompressed size :: 42.0 MB
- Number of voice phonemes :: 1954 (182 audio files)
- Average frequency :: C4
- Vocal range :: F#3~A#4
- FRQ file presence :: partial
- Release date :: June 28th, 2013
- Phoneme encoding :: Romaji (hiragana aliases, romaji added with the
oto.ini update)
- Supported languages :: Japanese
- oto.ini :: Tuned myself
- Recommended engines :: fresamp, VS4U, world4utau
**** JPN Extend Native
- Status :: abandonned
**** TIB CVVC
- Status :: abandonned
**** ENG
#+begin_export html
<ResponsiveImage
src="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_EN_673.webp"
:width="673"
preview="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_EN_246.webp"
:previewWidth="300">
Illustration de KEINE Tashi EN
</ResponsiveImage>
#+end_export
- Status :: abandonned
** Usage clause and license
KEINE Tashi is released under the [[https://creativecommons.org/licenses/by-nc-sa/4.0/][CC BY-SA-NC 4.0 license]], meaning you
are free to:
- use :: make use of the vocal libraries in UTAU or any other singing
vocal synthesizer software.
- adapt :: remix, transform, and build upon the material
- share :: copy and redistribute the material in any medium or format
my work, on the condition of:
- Attribution :: You must give appropriate credit, provide a link to
the license, and indicate if changes were made. You may do so in any
reasonable manner, but not in any way that suggests the licensor
endorses you or your use.
- NonCommercial :: You may not use the material for commercial
purposes.
- ShareAlike :: If you remix, transform, or build upon the material,
you must distribute your contributions under the same license as the
original.
Although I cannot add anything to this legal notice, I would also like
if you followed the following rules of thumb regarding this character:
any religious use of this character and its vocal libraries is
forbidden, except for folk music, and Buddhist and Bön songs. However,
due to the current controversy, any song linked to His Holiness the
Gyalwa Karmapa is strictly forbidden until said controversy has been
officially resolved. This is also applicable to His Holiness the Dalai
Lama, the Venerable Shamar Rinpoche, and Tai Situ Rinpoche. If you
have any question or if you are unsure, please email me.
#+include: other-links

View File

@ -1,14 +0,0 @@
# -*- mode: org -*-
#+begin_export gmi
# Other Web Pages
=> ./index.gmi Home
=> ./find-me.gmi Where to find me
=> ./resume.gmi Resume
=> ./projets.gmi Programming Projets
=> ./conlanging.gmi Conlanging
=> ./vocal-synthesis.gmi Vocal Synthesis
=> ./about.gmi About
=> ./privacy.gmi Privacy
#+end_export

View File

@ -1,76 +0,0 @@
#+setupfile: ../headers
#+language: en
* Privacy
** Where is the website hosted?
This website is hosted on my private physical server, located in the
town of Bron in France, near Lyon. All of my websites are also hosted
on this server, except for [[https://labs.phundrak.com][=labs.phundrak.com=]] and =mail.phundrak.com=
which are hosted on servers rented to Scaleway and OVH France
respectively. These servers are also located in France.
** Cookies
*** What are cookies?
Cookies are small files a website saves on your computer or mobile
phone when you visit a website. Although not all sites make use of
them, they are nevertheless extremely common in order to allow
websites to function properly or function properly or more
efficiently.
This website uses some functional cookies in order to remember your
preferences, such as your preferred language or its colour theme.
These cookies are not and cannot be used to track you.
However, as this site is protected by Cloudflare, they may also host
some cookies to remember, for example, that your browser is safe or to
record traffic to the site.
*** How can I control cookies on my computer?
If you don't want Cloudflare to record your browsing activity on my
website, a good ad blocker should do the trick. I personally recommend
[[https://ublockorigin.com/][uBlock Origin]], one of the most effective ad blockers I know of if not
the most effective one.
You can also manually delete cookies from your browser, but given the
number of browsers out there, it might be quicker for you to look up
DuckDuckGo, Qwant or Startpage to do this for your current browser (if
you're worried about cookie usage, I guess you'll want to avoid
Google).
*** What about other methods of tracking users?
There are other more subtle methods of tracking someone on the
internet, or even via emails or any web content rendered on the
screen, such as web beacons (minuscule, invisible images). It is also
possible to store Flash cookies or local shared objects.
*** But is there any tracking at all on this website?
Well, there is, but it absolutely respects your privacy. I use my own
instance of [[https://umami.is][Umami]] which is an analytics service that is fully GDPR and
CCPA compliant. In short, when you visit a web page, some data get
sent to my service, but nothing that can identify you. If you come
back an hour later, I wont have any indication that you are the same
person.
If you still worry about your privacy, you have two options:
- Activate the Do Not Track setting of your browser (which Umami will
honour)
- Block the domain =umami.phundrak.com= in uBlock Origin (the only
ad blocker I will ever trust)
** Is there targeted advertisement on this website?
Theres no advertisement to begin with, and never will be. If you see
any, check your computer and browser for virus, that is not normal. If
it indeed comes from my website, it means it has been hacked. If you
can see in this websites repository that I myself added ads, it means
that I either lost my morals, or that I have been kidnapped and this
is a cry for help.
** How often is this page updated?
It is updated from time to time to reflect any changes in how my
website behaves, or if I notice errors on this page (such as typos).
** I have other questions
And I have the answers! Ill be more than happy to chat with you by
email, feel free to send me one at [[mailto:lucien@phundrak.com][lucien@phundrak.com]].
#+include: other-links

View File

@ -1,43 +0,0 @@
#+setupfile: ../headers
#+language: en
* Programming Projects
** Pinned GitHub Projects
#+begin_export gemini
Unfortunately, this content is not available on Gemini. Im working on it.
#+end_export
#+begin_export html
<ClientOnly>
<ListRepositories>
<GithubRepository repoName="rejeep/f.el" />
<GithubRepository repoName="Phundrak/eshell-info-banner.el" />
<GithubRepository repoName="Phundrak/dotfiles" />
<GithubRepository repoName="Phundrak/conlang.phundrak.com" />
</ListRepositories>
</ClientOnly>
#+end_export
** Most Starred Projects on GitHub
#+begin_export gemini
Unfortunately, this content is not available on Gemini. Im working on it.
#+end_export
#+begin_export html
<ClientOnly>
<ListRepositories sortBy='stars' user='phundrak' :limit='5' />
</ClientOnly>
#+end_export
** Latest Active Repositories on GitHub
#+begin_export gemini
Unfortunately, this content is not available on Gemini. Im working on it.
#+end_export
#+begin_export html
<ClientOnly>
<ListRepositories sortBy='pushed_at' user='phundrak' :limit='5' />
</ClientOnly>
#+end_export
#+include: other-links

View File

@ -1,84 +0,0 @@
#+setupfile: ../headers
#+language: en
* Resume
** Profesionnal Experiences
*** Aubay (2023 - )
- Consultant since September 2023
- Internship from early February to early August 2023
- Web app development
- Usage of Angular, Java Spring Boot, Spring Batch, and PostgreSQL
*** VoxWave (2014 - 2018)
Startup specialized in the creation of French virtual singers using
vocal synthesis. Its best known product is ALYS. [[./vocal-synthesis.md][More here]].
- Co-founder, CTO
- Development of singing synthesis vocal libraries
- Linguistic research
- User support
- Recruit training for vocal libraries development
** Education
*** 2nd Year Masters Degree (University of Paris 8)
Year repeated due to health issues with no long-lasting consequences.
*** 1st Year Masters Degree (University of Paris 8)
*** Computer Science Bachelor Degree (University of Paris 8)
*** English Literature (University of Lyon 2)
Studied for a year and a half until the creation of [[./resume.md#voxwave-2014-2018][VoxWave]].
** Web Programming
*** Front-end
- Professional use of Angular and TypeScript
- Personal use of Vue (including Nuxt)
*** Back-end
- Professional use of Java SpringBoot and SpringBatch
- Professional and personal use of PostgreSQL
- Personal use of Rust ([[https://github.com/poem-web/poem/][poem]], [[https://actix.rs/][actix-web]] and [[https://rocket.rs/][Rocket]])
- Some experience in back-end development with Django (Python)
- Personal use of MySQL and SQLite
** System Programming
- Frequent usage of Rust, C, EmacsLisp, and UNIX shells (bash, fish, Eshell)
- Occasional use of C++, Python, and CommonLisp
** Development Tools
*** IDEs and Text Editors
- Professional use of VS Code, Eclipse, and Git
- Advanced user of Emacs, including its LSP and Git integrations
- Basic knowledge of Vim, CLion, Pycharm, and WebStorm
*** CI/CD and Deploying to the Web
- Experienced with web servers such as Nginx and Caddyserver
- Good knowledge of virtualization and deployment with Docker and
Docker Compose for virtualization, Drone.io, and GitHub Actions for
deployment.
** Operating Systems
- Usage and administration of Linux (Arch Linux, Void Linux, Debian,
Ubuntu, Alpine Linux, NixOS)
- Administration of web servers and storage servers (Arch Linux,
Debian, Raspbian, Alpine Linux, NixOS)
- Basic knowledge with Guix System and Windows XP through 10 (except
Vista)
** Office Applications
- Good knowledge with [[https://orgmode.org/][org-mode]] (main tool), LaTeX
- I know my way around LibreOffice, Microsoft Office, OnlyOffice, and
WPS Office
** Audio
*** Singing Vocal Synthesis
- Development and creation of vocal libraries for VOCALOID3,
Alter/Ego, Chipspeech, and UTAU
- Usage of VOCALOID 2 through 4, Alter/Ego, Chipspeech, UTAU, CeVIO
Creative Studio
*** Audio Engineering
- Music writing and mix software: FL Studio
- Audio repair and cleaning: iZotope RX
- Mastering: T-RackS CS
#+include: other-links

View File

@ -1,58 +0,0 @@
#+setupfile: ../headers
#+language: en
#+begin_export html
---
title: Vocal Synthesis
---
#+end_export
* My works in vocal synthesis
From 2011 to 2018, I worked as an amateur and professional in singing
vocal synthesis. More precisely, I was creating vocal libraries used
by various libraries, mainly UTAU and Alter/Ego.
** UTAU
I began working with UTAU first by the end of 2011 on an unnamed and
deleted Japanese vocal library. While I didnt maintain it for long,
mainly due to its bad recording quality (I recorded it with a low-end
desktop microphone) and configuration, it did teach me the basics of
creating vocal libraries and working with audio files.
In October 14th, 2012, I released my second vocal library, named
/BSUP01 KEINE Tashi JPN VCV/ which was of higher quality both due to the
recording equipment, manner of recording, and configuration, though
still relatively average for the time. My best work with this series
of vocal libraries was /BSUP01 KEINE Tashi JPN Extend Power/, a
high-energy voice made in similar circumstances but with yet again
better know-how.
This series of vocal libraries also featured /BSUP01 KEINE Tashi TIB
CVVC/ and /BSUP02 Drolma TIB/, the two first Tibetan vocal libraries for
singing vocal synthesis worldwide.
I later created in UTAU /ALYS 001 JPN/, /ALYS 001 FRA/, and /ALYS 002 FRA/
as prototypes, known as /ALYS4UTAU/, for our upcoming product while
working at VoxWave.
While all these vocal libraries have been discontinued, vocal
libraries for /BSUP01 KEINE Tashi/ and /ALYS/ are available for download.
Please refer to the following pages:
- BSUP01 KEINE Tashi :: [[file:./keine-tashi.org][BSUP01 KEINE Tashi]]
- ALYS :: [[https://labs.phundrak.com/ALYS/ALYS][ALYS for Alter/Ego download]]
** Alter/Ego
[[https://www.plogue.com/products/alter-ego.html][Alter/Ego]] is a singing vocal synthesis engine made by [[https://www.plogue.com/][Plogue Inc.]].
ALYS was its first commercial vocal library as well as the first
professional singing vocal library available in French.
Due to the architecture and behaviour of Alter/Ego, important changes
had to be done to the recording script for ALYS (later re-used for
LEORA). Including the development of the new recording scripts, the
initial development period for ALYS spanned well over a year, with
some additional eight to nine months for its first major update.
ALYS for Alter/Ego, also known as /ALYS4AE/, is available free of charge
as a module for Alter/Ego
#+include: other-links

View File

@ -1,31 +0,0 @@
#+setupfile: ./headers
#+language: fr
* Où me trouver
Je suis présent sur différentes plateformes et quelques réseaux
sociaux où vous pouvez me suivre.
** Réseaux sociaux
- {{{icon(mastodon)}}} *Mastodon* :: [[https://mastodon.phundrak.com/@phundrak][@phundrak@mastodon.phundrak.com]]
- {{{icon(twitter)}}} *Twitter* :: [[https://twitter.com/phundrak][@phundrak]], cependant je ny suis plus très
actif et jy repartage principalement mes messages Mastodon qui
parfois se font tronquer
- {{{icon(writefreely)}}} *Writefreely* ::
- [[https://write.phundrak.com/phundrak][*@phundrak@write.phundrak.com*]] : billets personnels
- [[https://write.phundrak.com/phundraks-short-stories][*@phundraks-short-stories@write.phundrak.com*]] : histoires courtes
- {{{icon(discord)}}} *Discord* :: =@phundrak= (dites-moi que vous venez
dici, autrement il est possible que je considère le message comme
du pourriel)
** Autres plateformes
- {{{icon(envelope)}}} *Courriel* :: [[mailto:lucien@phundrak.com][lucien@phundrak.com]]
- {{{icon(rss)}}} *Blog* :: [[https://blog.phundrak.com][blog.phundrak.com]]
- {{{icon(gitea)}}} *Gitea* :: [[https://labs.phundrak.com/phundrak][@phundrak@labs.phundrak.com]]
- {{{icon(github)}}} *GitHub* :: [[https://github.com/Phundrak][Phundrak]]
- {{{icon(youtube)}}} *YouTube* :: [[https://www.youtube.com/@phundrak][@phundrak]]
- {{{icon(reddit)}}} *Reddit* :: [[https://www.reddit.com/user/phundrak][/u/phundrak]]
- {{{icon(linkedin)}}} *LinkedIn* :: [[https://www.linkedin.com/in/lucien-cartier-tilet/][Lucien Cartier-Tilet]]
- {{{icon(twitch)}}} *Twitch* :: [[https://www.twitch.tv/phundrak][phundrak]]
#+include: other-links

View File

@ -1,7 +0,0 @@
# -*- mode: org -*-
#+author: Lucien Cartier-Tilet
#+email: lucien@phundrak.com
#+creator: Lucien Cartier-Tilet
#+options: H:4 broken_links:mark email:t ^:{} tex:dvisvgm toc:nil
#+macro: icon @@html:<Icon name="$1" />@@

View File

@ -1,31 +0,0 @@
#+setupfile: ./headers
#+language: fr
* Accueil
Bonjour, je suis Lucien Cartier-Tilet, un consultant chez [[https://aubay.com][Aubay]].
Jai étudié pour mon Master 2 THYP (/Technologies de lHypermédia/) à
lUniversité Vincennes Saint-Denis (Paris 8).
Jai travaillé chez VoxWave de 2012 à 2018 en tant que co-fondateur et
directeur technique de lentreprise. Jy ai notamment développé les
chanteuses virtuelles francophones nommées ALYS et LEORA.
Je suis un enthousiaste du logiciel libre, utilisant Linux depuis 2008
et Emacs depuis 2016.
Mes passe-temps principaux sont la programmation, aussi bien de la
programmation système que de la programmation web, et la construction
de langues et univers fictifs. Jaime aussi faire de lescalade et
quand lopportunité se présente, de la randonnée.
Ma langue maternelle est le Français, mais je parle également
couramment en anglais. Jai également des bases en japonais, [[https://elefen.org][lingua
franca nova]], et en norvégien bokmål.
#+begin_export html
Ce site web est également disponible sur Gemini à ladresse [gmi.phundrak.com](gemini://gmi.phundrak.com)!
#+end_export
#+include: other-links

View File

@ -1,193 +0,0 @@
#+setupfile: ./headers
#+language: fr
#+begin_export html
---
title: BSUP01 Keine Tashi
---
#+end_export
* BSUP01 Keine Tashi
** Présentation
Keine Tashi est un personnage et le nom dune collection de banques
vocales développées pour le logiciel [[http://utau2008.web.fc2.com/][UTAU]], un logiciel de synthèse de
voix pour le chant. Jai développé Keine Tashi de 2012 à 2015 et
publiai trois de ses banques vocales. Celle ayant rencontre le plus de
succès fut sa banque vocale /JPN Extend Power/. Le 10 mars 2017,
jannonçai arrêter toutes activités liées à UTAU.
#+begin_export html
<blockquote class="twitter-tweet" data-dnt="true" data-theme="dark">
<p lang="en" dir="ltr">
I&#39;d like to also announce that from now on I
am dropping my previous UTAU projects other than covers and won&#39;t
develop any new UTAU library
</p>
&mdash; P&#39;undrak (@Phundrak) <a href="https://twitter.com/Phundrak/status/840174634377105408?ref_src=twsrc%5Etfw">March 10, 2017</a>
</blockquote>
<component is="script" async src="https://platform.twitter.com/widgets.js" charset="utf-8"></component>
#+end_export
** Personnage et banques vocales
Voici une traduction en français des informations ayant trait à Keine
Tashi sur danciennes pages le présentant.
*** Présentation
#+begin_export html
<ResponsiveImage
src="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_1024.webp"
:width="1024"
preview="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_512.webp"
:previewWidth="512">
Illustration de KEINE Tashi par Umi
</ResponsiveImage>
#+end_export
- Nom de code :: BSUP01 恵音བཀྲ་ཤིས་ Keine Tashi
- Prénom :: Tashi (བཀྲ་ཤིས་), prénom tibétain signifiant « auspicieux »
- Nom :: Keine (恵音), nom japonais signifiant « son bénissant ». Le
nom se lit « keine » bien que sa lecture normale devrait être
« megumine ».
- Modèle :: BSUP (Bödkay Shetang UTAU Project, /Projet UTAU de Chant
Tibétain/)
- Numéro :: 01
- Sexe :: homme
- Anniversaire (personnage) :: 28 juin 1998
- Première publication :: 14 octobre 2012
- Poids :: 154lb / 70kg
- Taille :: 182cm
- Couleur de cheveux :: noir
- Couleur des yeux :: entre le marron et le noir
- Apparance :: Tashi porte une version modernisée dun habit tibétain
traditionnel de la région de lAmdo (Chinois : 安多 Ānduō) coloré en
bleu. Il porte également quelques bijoux de turquoise.
- Nourriture préférée :: momo à la viande (raviolis tibétains)
- Objet signature :: un manuscrit tibétain
- Voix et créateur :: [[https://phundrak.com][Phundrak]] (moi)
- Aime :: méditer, la calligraphie, les vieux livres et manuscripts
(en gros, moi à lépoque ou je créai ce personnage)
- Naime pas :: légoïsme, les mensonges, larrogance
- Personnalité :: Tashi est quelquun de très calme et dagréable. Il
adore les vieux livres et manuscrits, mais ce quil aime par-dessus
tout est la méditation. Il na jamais faim, ce qui fait quil peut
rester pendant plusieurs jours à méditer si lenvie le prend,
jusquau moment où il réalise quil a /besoin/ de manger. Il est très
difficile de le mettre en colère.
Mais quand il le devient, sa colère devient explosive. Le calmer
devient alors une tâche extrêmement complexe. Étrangement, les
victimes de son courroux voient peu de temps après leur qualité de
vie grandement saméliorer. Peut-être ces personnes avaient besoin
dentendre des réalités auxquelles elles refusaient de faire face ?
*** Banques vocales
**** JPN VCV
- Lien de téléchargement ::
| Extension | Taille | Lien |
|-----------+----------+------|
| 7z | 25.7 MiB | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_VCV.7z][DL]] |
| tar.xz | 32.5 MiB | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_VCV.tar.xz][DL]] |
| zip | 38.0 MiB | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_VCV.zip][DL]] |
- Taille décompressée :: 47.1Mio
- Nombre de phonèmes :: 1264 (253 fichiers audio)
- Note moyenne :: G#2
- Plage vocale :: C2~D3
- Présence de fichiers FRQ :: partiel
- Date de publication :: 14 octobre 2012
- Encodage des phonèmes :: Romaji avec des alias hiragana et un
support CV en romaji
- Langues supportées :: Japonais
- Moteurs de synthèse recommandés :: TIPS, VS4U
**** JPN Extend Power
- Lien de téléchargement ::
| Extension | Taille | Lien |
|-----------+--------+------|
| 7z | 1.1Gio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Power.7z][DL]] |
| tar.xz | 1.1Gio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Power.tar.xz][DL]] |
| zip | 1.2Gio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Power.zip][DL]] |
- Taille décompressée :: 1.3Gio
- Nombre de phonèmes :: 3020 (546 fichiers audio)
- Note moyenne :: C3
- Plage vocale :: B1~D4
- Présence de fichiers FRQ :: partiel
- Date de publication :: 28 juin 2013
- Encodage des phonèmes :: Romaji (alias hiragana)
- Langues supportées :: Japonais
- Moteurs de synthèse recommandés :: VS4U, world4utau
**** JPN Extend Youth
- Lien de téléchargement ::
| Extension | Taille | Lien |
|-----------+----------+------|
| 7z | 237.7Mio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Youth.7z][DL]] |
| tar.xz | 243.5Mio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Youth.tar.xz][DL]] |
| zip | 268.7Mio | [[https://cdn.phundrak.com/files/KeineTashi/BSUP01_KEINE_Tashi_JPN_Extend_Youth.zip][DL]] |
- Taille décompressée :: 301.1Mio
- Nombre de phonèmes :: 1954 (182 fichiers audio)
- Note moyenne :: C4
- Plage vocale :: F#3~A#4
- Présence de fichiers FRQ :: partiel
- Date de publication :: 28 juin 2013
- Encodage des phonèmes :: Romaji (alias hiragana)
- Langues supportées :: Japonais
- Moteurs de synthèse recommandés :: fresamp, VS4U, world4utau
**** JPN Extend Native
- Status :: abandonné
**** TIB CVVC
- Status :: abandonné
**** ENG
#+begin_export html
<ResponsiveImage
src="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_EN_673.webp"
:width="673"
preview="https://cdn.phundrak.com/img/UTAU/KEINE_Tashi_EN_246.webp"
:previewWidth="300">
Illustration de Keine Tashi EN
</ResponsiveImage>
#+end_export
- Status :: abandonné
** Licence dutilisation
Keine Tashi est publié sous la licence [[https://creativecommons.org/licenses/by-nc-sa/4.0/][CC BY-SA-NC 4.0]]. Cela signifie
que vous êtes libres :
- dutiliser :: utiliser les banques vocales dans UTAU ou tout autre
logiciel ;
- de partager :: copier, distribuer et communiquer le matériel par
tous moyens et sous tous formats ;
- dadapter :: remixer, transformer et créer à partir du matériel ;
Selon les conditions suivantes :
- Attribution :: Vous devez me créditer lors de lutilisation de
Tashi, intégrer un lien vers la licence et indiquer si des
modifications ont été effectuées. Vous devez indiquer ces
informations par tous les moyens raisonnables, sans toutefois
suggérer que je vous soutienne ou que je soutienne la façon dont
vous utilisez Tashi ;
- Pas dUtilisation Commerciale :: Vous nêtes pas autorisé à faire un
usage commercial de Tashi, tout ou partie du matériel le composant ;
- Partage dans les Mêmes Conditions :: Dans le cas où vous effectuez
un remix, que vous transformez ou créez à partir du matériel
composant Tashi, vous devez le diffuser modifié dans les mêmes
conditions, c'est-à-dire avec la même licence avec laquelle Tashi
est diffusé ici.
Bien que je ne puisse pas ajouter déléments à cette licence légale,
je souhaiterais ajouter une requête personnelle : merci de ne pas
créer de chansons à caractère religieux, à lexception des chansons
tibétaines bouddhistes ou bön. Cependant, du fait de la controverse
actuelle concernant lidentité de Sa Sainteté le Gyalwa Karmapa, toute
chanson lié à sa personne est également interdite jusquà résolution
officielle de la situation. Cette interdiction est également
applicable à Sa Sainteté le Dalaï Lama, au Vénérable Shamar Rinpoché
et Tai Situ Rinpoche. Si vous avez la moindre question, nhésitez pas
à m[[mailto:lucien@phundrak.com][envoyer un email]].
#+include: other-links

View File

@ -1,23 +0,0 @@
#+setupfile: ../headers
#+language: lfn
* A tema
** Introdui
Asi es la loca ueb personal de Lucien Cartier-Tilet, ance conoseda
commo “Pundrak” o “Phundrak”.
Lo es scriveda con [[https://v2.vuepress.vuejs.org/][Vuepress]] e es completa de fonte abrida. On pote
trova la totalia de la testo de fonte sur [[https://labs.phundrak.com/phundrak/phundrak.com][mea Gitea personal]]. La
icones ce es usada sur esa loca ueb veni de varia orijinas diversa:
- [[https://icomoon.io][IcoMoon]] ce me usa afin faxi icones en un tipo de letra unica,
incluinte alga de lor propre icones,
- [[https://fontawesome.com/][FontAwesome]] de do la majoria de mea icones veni (lor util per Vue no
es vera bon en mea opinia)
- La {{{icon(conlang)}}} [[https://conlang.org/][Asosia de Crea de Linguas]] {{{icon(conlang)}}} de cual me
ia altera lor logo per mea linguas construida,
- {{{icon(emacs)}}}[[https://www.gnu.org/software/emacs/][Emacs]] e {{{icon(writefreely)}}} [[https://writefreely.org/][WriteFreely]] de cual me ia
recrea parte de lor logo propre en SVG afin de crea un icon,
- {{{icon(gitea)}}} [[https://gitea.io][Gitea]] de cual me ia altera la logo afin fa lo
monocromatica.
#+include: other-links

View File

@ -1,31 +0,0 @@
#+setupfile: ../headers
#+language: lfn
* Crea de linguas
La /linguas construida/ (en engles /conlang/) es linguas artifis naseda de
la spirito de un persone, o a veses alga persones. Les es diferente de
linguas natural ce apare con la evolui de un lingua presedente parlada
par un popla completa dura multiple sentenios, como franses, engles,
putong, nion, o bahasa o cosa (/!Xhosa/).
Linguas construida pote ave diversa intende tra sua crea, pe:
- es parlada como otra linguas natural afin de es un lingua franca
entre multiple comunias, como elefen o esperanto
- es un lingua secreta ce sola algun persones conose afin de comunica
sin ce on comprende los
- esperia lo ce es posible linguisticamente, como [[https://en.wikipedia.org/wiki/Lojban][Lojban]] ce atenta a
es la lingua la plu lojica
- completa un universo fantasial, como la linguas elfin de Tolkien o
klingon de Star Trek
- sola es arte, como la pinta o la poesia.
En mea caso, la du ultima espicas es lo ce me impulsa a crea nova
linguas. Mea du projetas xef es [[https://conlang.phundrak.com/proto-nyqy][proto-ñyqy]] e [[https://conlang.phundrak.com/eittlandic][eittlansce]]. La prima es
un lingua prima ce se fundi sur no otra lingua, ma ce me va usa a crea
un familia linguistica completa, en ce eittlansce es desende de la
lingua de la vicinges, vea nordica, e ce se parla en la pais fantasial
de Eittland.
On pote trove plu de informa sur [[https://conlang.phundrak.com/][mea loca ueb de linguas construida]].
#+include: other-links

View File

@ -1,28 +0,0 @@
#+setupfile: ../headers
#+language: lfn
* Do trova me
On pote trova me sur multe loca ueb e redes sosial do on pote segue me.
** Redes sosial
- {{{icon(mastodon)}}} *Mastodon* :: [[https://mastodon.phundrak.com/@phundrak][@phundrak@mastodon.phundrak.com]]
- {{{icon(twitter)}}} *Twitter* :: [[https://twitter.com/phundrak][@phundrak]], ma me usa lo a poca veses, la
plu de mea tuitas es mea mesajes mastodon ce es a vesas truncada
- {{{icon(writefreely)}}} *Writefreely* ::
- [[https://write.phundrak.com/phundrak][@phundrak@write.phundrak.com]] :: revistas personal
- [[https://write.phundrak.com/phundraks-short-stories][@phundraks-short-stories@write.phundrak.com]] :: istorias corta (a
multe veses en Frans)
- {{{icon(discord)}}} *Discord* :: =@phundrak= (dise me ce tu veni de asi,
si no, me pote pensa ce tua mesaje es spam)
** Other Websites
- {{{icon(envelope)}}} *Eposta* :: [[mailto:lucien@phundrak.com][lucien@phundrak.com]]
- {{{icon(rss)}}} *Blog* :: [[https://blog.phundrak.com][blog.phundrak.com]]
- {{{icon(gitea)}}} *Gitea* :: [[https://labs.phundrak.com/phundrak][@phundrak@labs.phundrak.com]]
- {{{icon(github)}}} *GitHub* :: [[https://github.com/Phundrak][Phundrak]]
- {{{icon(youtube)}}} *YouTube* :: [[https://www.youtube.com/@phundrak][@phundrak]]
- {{{icon(reddit)}}} *Reddit* :: [[https://www.reddit.com/user/phundrak][/u/phundrak]]
- {{{icon(linkedin)}}} *LinkedIn* :: [[https://www.linkedin.com/in/lucien-cartier-tilet/][Lucien Cartier-Tilet]]
- {{{icon(twitch)}}} *Twitch* :: [[https://www.twitch.tv/phundrak][phundrak]]
#+include: other-links

View File

@ -1,30 +0,0 @@
#+setupfile: ../headers
#+language: lfn
* Paje prima
Saluta, me es Lucien Cartier-Tilet, un spesialiste laborante a [[https://aubay.com][Aubay]].
Me ia studia la informatica en la Master 2 THYP (franses: /Technologies
de lHypermédia/, elefen: /Tecnolojia de la Ipermedia/) en la Universia
Vincennes Saint-Denis (Paris 8).
Me ia labora a VoxWave de 2012 a 2018 como un de sua co-fundores e sua
dirijor tecnical. Dura esta tempo, me ia crea la cantores virtual
franses ALYS e LEORA.
Me es un zelo de la programes libre, usante GNU/Linux de 2008 e Emacs
de 2016.
Me amatos es la crea de programes e construi mundos e linguas
fantasial. Me ama ance asende e pasea en la montania cuando me pote
lo.
Mea lingua prima es franses, ma me pote parla fasil engles. Me ance
pote parla alga nion, norsce (bokmål) e elefen.
#+begin_export html
Eso loca ueb es ance dispone per Gemini a la adirije ueb [gmi.phundrak.com/lfn](gemini://gmi.phundrak.com/lfn)!
#+end_export
#+include: other-links

Some files were not shown because too many files have changed in this diff Show More