Compare commits
10 commits
e3970d3480
...
592d19fea6
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
592d19fea6 | ||
|
|
2ecac616d5 | ||
|
|
cc30d0d44a | ||
|
|
cd591d31fe | ||
|
|
25ae07d364 | ||
|
|
f8ac123283 | ||
|
|
57636e230a | ||
|
|
a817460fa7 | ||
|
|
ccdd2d6e40 | ||
|
|
98beb0cbf4 |
|
|
@ -6,5 +6,10 @@ dist
|
||||||
node_modules
|
node_modules
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env*
|
||||||
DEPLOY.md
|
!.env.example
|
||||||
|
.github
|
||||||
|
.openai
|
||||||
|
.agents
|
||||||
|
.codex
|
||||||
|
*.md
|
||||||
|
|
|
||||||
5
.gitignore
vendored
|
|
@ -1,7 +1,10 @@
|
||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
.cache
|
.cache
|
||||||
|
.npm
|
||||||
.idea/
|
.idea/
|
||||||
*.iml
|
*.iml
|
||||||
|
*.log
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.env
|
.env*
|
||||||
|
!.env.example
|
||||||
|
|
|
||||||
3
404.html
|
|
@ -6,9 +6,6 @@
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>404 - Fi3w0</title>
|
<title>404 - Fi3w0</title>
|
||||||
<meta name="robots" content="noindex">
|
<meta name="robots" content="noindex">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=IBM+Plex+Mono:wght@400;500;600&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="/css/style.css?v=4">
|
<link rel="stylesheet" href="/css/style.css?v=4">
|
||||||
<link rel="stylesheet" href="/css/editorial.css?v=4">
|
<link rel="stylesheet" href="/css/editorial.css?v=4">
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||||
|
|
|
||||||
50
DEPLOY.md
|
|
@ -1,50 +0,0 @@
|
||||||
# Server deployment
|
|
||||||
|
|
||||||
The production image builds the Webpack site with Node and serves the generated `dist/` directory with Nginx. The container does not publish a host port; Traefik reaches it over an external Docker network.
|
|
||||||
|
|
||||||
## Defaults
|
|
||||||
|
|
||||||
| Setting | Default |
|
|
||||||
| --- | --- |
|
|
||||||
| Domain | `fiwlabs.dev` |
|
|
||||||
| Traefik network | `proxy` |
|
|
||||||
| HTTPS entrypoint | `websecure` |
|
|
||||||
| Certificate resolver | `porkbun` |
|
|
||||||
|
|
||||||
Compose reads overrides from `.env`. The available keys and defaults are documented in `.env.example`.
|
|
||||||
|
|
||||||
## Prerequisites
|
|
||||||
|
|
||||||
1. Point the `A` and optional `AAAA` records for `fiwlabs.dev` to the server.
|
|
||||||
2. Ensure Traefik has its Docker provider enabled.
|
|
||||||
3. Attach Traefik to the same external network configured by `TRAEFIK_NETWORK`.
|
|
||||||
4. Ensure the configured certificate resolver exists in Traefik.
|
|
||||||
|
|
||||||
Create the default shared network once if the Traefik stack has not already created it:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker network create proxy
|
|
||||||
```
|
|
||||||
|
|
||||||
## Deploy
|
|
||||||
|
|
||||||
Validate the resolved Compose configuration:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker compose -f docker-compose.yml config
|
|
||||||
```
|
|
||||||
|
|
||||||
Build and start the site:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker compose -f docker-compose.yml up --build -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Check container health and logs:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
docker compose -f docker-compose.yml ps
|
|
||||||
docker compose -f docker-compose.yml logs -f fiws-page
|
|
||||||
```
|
|
||||||
|
|
||||||
The container exposes `/healthz` internally for Docker health checks. Traefik serves the site at `https://fiwlabs.dev`; the server's `web` entrypoint redirects HTTP requests to HTTPS globally.
|
|
||||||
24
Dockerfile
|
|
@ -1,21 +1,29 @@
|
||||||
# syntax=docker/dockerfile:1.7
|
# syntax=docker/dockerfile:1.7
|
||||||
|
|
||||||
FROM node:22-alpine AS build
|
FROM node:24-alpine AS build
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY package.json package-lock.json ./
|
COPY package.json package-lock.json ./
|
||||||
RUN npm ci --ignore-scripts
|
RUN npm ci --ignore-scripts --no-audit --no-fund
|
||||||
|
|
||||||
COPY . .
|
COPY webpack.common.js webpack.config.prod.js ./
|
||||||
|
COPY index.html 404.html favicon.ico icon.png icon.svg robots.txt site.webmanifest ./
|
||||||
|
COPY css ./css
|
||||||
|
COPY img ./img
|
||||||
|
COPY js ./js
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
FROM nginx:1.27-alpine AS runtime
|
FROM nginxinc/nginx-unprivileged:1.29.4-alpine AS runtime
|
||||||
|
|
||||||
COPY deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
COPY --chown=101:101 deploy/nginx.conf /etc/nginx/conf.d/default.conf
|
||||||
COPY --from=build /app/dist /usr/share/nginx/html
|
COPY --chown=101:101 --from=build /app/dist /usr/share/nginx/html
|
||||||
|
|
||||||
EXPOSE 80
|
USER 101:101
|
||||||
|
|
||||||
|
EXPOSE 8080
|
||||||
|
|
||||||
|
STOPSIGNAL SIGQUIT
|
||||||
|
|
||||||
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
|
||||||
CMD wget -q -O /dev/null http://127.0.0.1/healthz || exit 1
|
CMD wget -q -O /dev/null http://127.0.0.1:8080/healthz || exit 1
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
Copyright (c) HTML5 Boilerplate
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2026 Alex (Fi3w0)
|
||||||
|
Portions copyright (c) HTML5 Boilerplate
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
||||||
this software and associated documentation files (the "Software"), to deal in
|
this software and associated documentation files (the "Software"), to deal in
|
||||||
|
|
|
||||||
93
README.md
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
# FIW Labs Homepage
|
||||||
|
|
||||||
|
The source for [fiwlabs.dev](https://fiwlabs.dev), the personal portfolio of Alex (`Fi3w0`). It presents systems and desktop projects including TideWM, Scorium, Flux, Moonlit Shell, and the FIW Minecraft tooling collection.
|
||||||
|
|
||||||
|
The site is intentionally framework-light: semantic HTML, hand-written CSS, and vanilla JavaScript are compiled into a static production bundle. There is no analytics, telemetry, backend, database, or runtime dependency on third-party fonts, icons, or scripts.
|
||||||
|
|
||||||
|
## Highlights
|
||||||
|
|
||||||
|
- Responsive editorial interface with accessible navigation and reduced-motion support.
|
||||||
|
- Interactive liquid ASCII artwork with graceful text fallbacks.
|
||||||
|
- Self-contained assets and strict browser security headers.
|
||||||
|
- Reproducible Webpack production build.
|
||||||
|
- Unprivileged, read-only Nginx container designed to sit behind Traefik.
|
||||||
|
|
||||||
|
## Technology
|
||||||
|
|
||||||
|
| Area | Choice |
|
||||||
|
| --- | --- |
|
||||||
|
| Interface | HTML, CSS, vanilla JavaScript |
|
||||||
|
| Build | Node.js 24, Webpack 5 |
|
||||||
|
| Runtime | Unprivileged Nginx on port `8080` |
|
||||||
|
| Deployment | Docker Compose and Traefik |
|
||||||
|
| Privacy | No analytics or third-party runtime requests |
|
||||||
|
|
||||||
|
## Local development
|
||||||
|
|
||||||
|
Requirements: Node.js 24 and npm.
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm ci
|
||||||
|
npm start
|
||||||
|
```
|
||||||
|
|
||||||
|
Create the production bundle with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
npm run build
|
||||||
|
```
|
||||||
|
|
||||||
|
Webpack writes the static site to `dist/`.
|
||||||
|
|
||||||
|
## Production deployment
|
||||||
|
|
||||||
|
The Compose stack expects an existing external Docker network shared with Traefik. Copy the example environment file and adjust it for the target host:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
cp .env.example .env
|
||||||
|
docker network create proxy
|
||||||
|
docker compose config
|
||||||
|
docker compose up --build -d
|
||||||
|
```
|
||||||
|
|
||||||
|
The network only needs to be created once. If Traefik already owns it, skip that command.
|
||||||
|
|
||||||
|
| Variable | Default | Purpose |
|
||||||
|
| --- | --- | --- |
|
||||||
|
| `DOMAIN` | `fiwlabs.dev` | Traefik host rule |
|
||||||
|
| `TRAEFIK_NETWORK` | `proxy` | Shared external network |
|
||||||
|
| `TRAEFIK_HTTPS_ENTRYPOINT` | `websecure` | HTTPS entrypoint |
|
||||||
|
| `TRAEFIK_CERT_RESOLVER` | `porkbun` | Configured certificate resolver |
|
||||||
|
| `IMAGE_NAME` | `fiws-page` | Local/container registry image name |
|
||||||
|
| `IMAGE_TAG` | `latest` | Image tag to deploy |
|
||||||
|
|
||||||
|
Check the deployment with:
|
||||||
|
|
||||||
|
```sh
|
||||||
|
docker compose ps
|
||||||
|
docker compose logs --tail=100 fiws-page
|
||||||
|
```
|
||||||
|
|
||||||
|
Docker monitors `/healthz` inside the container. The service is exposed only to the shared Docker network; it does not publish a host port.
|
||||||
|
|
||||||
|
## Security posture
|
||||||
|
|
||||||
|
The production container runs as an unprivileged user with a read-only filesystem, all Linux capabilities dropped, privilege escalation disabled, and bounded CPU, memory, process, temporary-storage, and log usage. Nginx serves only static files and applies CSP, HSTS, referrer, permissions, framing, and content-type protections.
|
||||||
|
|
||||||
|
Keep secrets out of the frontend and out of Docker build arguments. Everything delivered to a browser is public by definition.
|
||||||
|
|
||||||
|
## Repository layout
|
||||||
|
|
||||||
|
```text
|
||||||
|
css/ Site styles
|
||||||
|
deploy/nginx.conf Static server and security headers
|
||||||
|
img/ Optimized project artwork
|
||||||
|
js/app.js Interaction and visual effects
|
||||||
|
Dockerfile Multi-stage production image
|
||||||
|
docker-compose.yml Traefik-connected runtime
|
||||||
|
webpack.*.js Development and production builds
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
The website source is available under the [MIT License](LICENSE.txt). Product names, screenshots, logos, and third-party marks retain their respective rights.
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
--cyan: #8fd4d0;
|
--cyan: #8fd4d0;
|
||||||
--lime: #c8ee80;
|
--lime: #c8ee80;
|
||||||
--yellow: #f3d277;
|
--yellow: #f3d277;
|
||||||
--display: "Archivo Black", "Arial Black", sans-serif;
|
--display: "Arial Black", "Helvetica Neue", system-ui, sans-serif;
|
||||||
--mono: "IBM Plex Mono", "Courier New", monospace;
|
--mono: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||||
--page-pad: clamp(1.25rem, 4vw, 4rem);
|
--page-pad: clamp(1.25rem, 4vw, 4rem);
|
||||||
--max-width: 92rem;
|
--max-width: 92rem;
|
||||||
--radius-sm: 0;
|
--radius-sm: 0;
|
||||||
|
|
@ -315,7 +315,7 @@ code {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
border: 2px solid rgba(143, 212, 208, 0.35);
|
border: 2px solid rgba(143, 212, 208, 0.35);
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
background: var(--bg-soft);
|
background: rgba(23, 37, 46, 0.8);
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
transform: rotate(1deg);
|
transform: rotate(1deg);
|
||||||
transition: transform 220ms ease, border-color 220ms ease;
|
transition: transform 220ms ease, border-color 220ms ease;
|
||||||
|
|
@ -391,7 +391,7 @@ code {
|
||||||
.profile-chip img {
|
.profile-chip img {
|
||||||
width: 4rem;
|
width: 4rem;
|
||||||
height: 4rem;
|
height: 4rem;
|
||||||
border: 1px solid var(--line);
|
border: 0;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -732,101 +732,6 @@ code {
|
||||||
transform: translateX(1.2rem);
|
transform: translateX(1.2rem);
|
||||||
}
|
}
|
||||||
|
|
||||||
.current-project.has-tide-ascii .current-watermark {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* ASCIIText, ported from React Bits to vanilla JS/three.js. Lazily mounted
|
|
||||||
(see app.js) to replace the flat "TIDE" watermark with a rippling,
|
|
||||||
wave-distorted ASCII render — a nod to the compositor's water theme.
|
|
||||||
Falls back to the plain watermark above if WebGL/three.js is unavailable. */
|
|
||||||
.tide-ascii {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
bottom: -9%;
|
|
||||||
left: -6%;
|
|
||||||
width: min(82rem, 126%);
|
|
||||||
height: 55%;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 900ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tide-ascii.is-ready {
|
|
||||||
opacity: 0.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tide-ascii-inner {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tide-ascii-canvas {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
width: 100%;
|
|
||||||
height: 100%;
|
|
||||||
opacity: 0.7;
|
|
||||||
filter: saturate(1.4);
|
|
||||||
image-rendering: pixelated;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tide-ascii-pre {
|
|
||||||
position: absolute;
|
|
||||||
top: 0;
|
|
||||||
left: 0;
|
|
||||||
z-index: 2;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
overflow: hidden;
|
|
||||||
color: rgba(17, 23, 26, 0.86);
|
|
||||||
line-height: 1em;
|
|
||||||
mix-blend-mode: multiply;
|
|
||||||
white-space: pre;
|
|
||||||
user-select: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 68rem) {
|
|
||||||
.tide-ascii {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.current-project.has-tide-ascii .current-watermark {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-visual-flux.has-flux-ascii .flux-watermark {
|
|
||||||
opacity: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flux-ascii {
|
|
||||||
position: absolute;
|
|
||||||
z-index: 1;
|
|
||||||
bottom: 34%;
|
|
||||||
left: -14%;
|
|
||||||
width: min(58rem, 118%);
|
|
||||||
height: 88%;
|
|
||||||
opacity: 0;
|
|
||||||
pointer-events: none;
|
|
||||||
transition: opacity 900ms ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flux-ascii.is-ready {
|
|
||||||
opacity: 0.85;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 64rem) {
|
|
||||||
.flux-ascii {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-visual-flux.has-flux-ascii .flux-watermark {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.project-feature {
|
.project-feature {
|
||||||
min-height: 38rem;
|
min-height: 38rem;
|
||||||
margin-bottom: 4rem;
|
margin-bottom: 4rem;
|
||||||
|
|
@ -885,6 +790,210 @@ code {
|
||||||
font-size: 0.86rem;
|
font-size: 0.86rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Scorium is code-native, so its project visual is a small editorial
|
||||||
|
language specimen rather than a fake product screenshot. */
|
||||||
|
.project-visual-scorium {
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
background:
|
||||||
|
linear-gradient(rgba(17, 23, 26, 0.08) 1px, transparent 1px),
|
||||||
|
linear-gradient(90deg, rgba(17, 23, 26, 0.08) 1px, transparent 1px),
|
||||||
|
var(--paper-deep);
|
||||||
|
background-size: 2.25rem 2.25rem;
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-scorium::before {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
top: -16%;
|
||||||
|
right: -5%;
|
||||||
|
width: 55%;
|
||||||
|
height: 72%;
|
||||||
|
transform: rotate(12deg);
|
||||||
|
background: var(--coral);
|
||||||
|
content: "";
|
||||||
|
clip-path: polygon(26% 0, 100% 0, 100% 78%, 0 100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-window {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
top: 8%;
|
||||||
|
right: 8%;
|
||||||
|
bottom: 21%;
|
||||||
|
left: 7%;
|
||||||
|
display: grid;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 3px solid var(--ink);
|
||||||
|
background: #111b20;
|
||||||
|
box-shadow: 0.9rem 0.9rem 0 rgba(17, 23, 26, 0.2);
|
||||||
|
grid-template-rows: auto 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-titlebar {
|
||||||
|
display: grid;
|
||||||
|
min-height: 3.5rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-bottom: 2px solid rgba(237, 241, 233, 0.22);
|
||||||
|
color: var(--muted);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.62rem;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-titlebar b {
|
||||||
|
color: var(--coral-bright);
|
||||||
|
letter-spacing: 0.13em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-titlebar span {
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-titlebar i {
|
||||||
|
color: var(--lime);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-code {
|
||||||
|
align-self: center;
|
||||||
|
min-width: 0;
|
||||||
|
margin: 0;
|
||||||
|
padding: clamp(1.2rem, 3vw, 2.2rem);
|
||||||
|
overflow: hidden;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
color: var(--text);
|
||||||
|
font: 500 clamp(0.67rem, 1.05vw, 0.93rem)/1.65 var(--mono);
|
||||||
|
white-space: pre;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-code .scorium-dim {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2.25em;
|
||||||
|
color: #60737b;
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-line {
|
||||||
|
display: block;
|
||||||
|
min-height: 1.65em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-indent {
|
||||||
|
display: inline-block;
|
||||||
|
width: 2.2em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-indent-double {
|
||||||
|
width: 4.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-code b {
|
||||||
|
color: var(--cyan);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-code em {
|
||||||
|
color: var(--coral-bright);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-code strong {
|
||||||
|
color: var(--lime);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-code i {
|
||||||
|
color: var(--yellow);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-diagnostic {
|
||||||
|
display: flex;
|
||||||
|
min-height: 2.9rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-top: 2px solid rgba(237, 241, 233, 0.16);
|
||||||
|
color: var(--muted);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.61rem;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.65rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-diagnostic i {
|
||||||
|
width: 0.55rem;
|
||||||
|
height: 0.55rem;
|
||||||
|
flex: 0 0 auto;
|
||||||
|
background: var(--lime);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-pipeline {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 2;
|
||||||
|
right: 2rem;
|
||||||
|
bottom: 2rem;
|
||||||
|
left: 2rem;
|
||||||
|
display: flex;
|
||||||
|
padding-top: 0.8rem;
|
||||||
|
border-top: 3px solid var(--ink);
|
||||||
|
color: var(--ink);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: clamp(0.54rem, 0.8vw, 0.68rem);
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-pipeline b {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-pipeline i {
|
||||||
|
color: #765b4d;
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-watermark {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 1;
|
||||||
|
right: -0.04em;
|
||||||
|
bottom: -0.2em;
|
||||||
|
color: rgba(17, 23, 26, 0.1);
|
||||||
|
font-family: var(--display);
|
||||||
|
font-size: clamp(7rem, 15vw, 14rem);
|
||||||
|
letter-spacing: -0.08em;
|
||||||
|
line-height: 0.8;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-scorium .image-label {
|
||||||
|
z-index: 3;
|
||||||
|
right: auto;
|
||||||
|
bottom: auto;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
transform: translate(-2px, -100%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-feature-scorium .project-feature-copy {
|
||||||
|
border-top-color: var(--coral);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-tags {
|
||||||
|
margin: -0.2rem 0 1.4rem;
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
.project-facts {
|
.project-facts {
|
||||||
border-top-width: 3px;
|
border-top-width: 3px;
|
||||||
border-bottom-width: 2px;
|
border-bottom-width: 2px;
|
||||||
|
|
@ -1764,6 +1873,45 @@ code {
|
||||||
min-height: 20rem;
|
min-height: 20rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.project-visual-scorium {
|
||||||
|
min-height: 28rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-window {
|
||||||
|
top: 6%;
|
||||||
|
right: 1.1rem;
|
||||||
|
bottom: 19%;
|
||||||
|
left: 1.1rem;
|
||||||
|
box-shadow: 0.55rem 0.55rem 0 rgba(17, 23, 26, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-titlebar {
|
||||||
|
min-height: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-titlebar span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-titlebar {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-code {
|
||||||
|
padding: 1rem;
|
||||||
|
font-size: clamp(0.56rem, 2.35vw, 0.72rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-pipeline {
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1.35rem;
|
||||||
|
left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scorium-pipeline i {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
.project-card-cool {
|
.project-card-cool {
|
||||||
width: 94%;
|
width: 94%;
|
||||||
}
|
}
|
||||||
|
|
@ -1899,6 +2047,617 @@ code {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Product-led project cards: quieter surfaces, tighter hierarchy, and real
|
||||||
|
product evidence in place of the earlier oversized ASCII treatments. */
|
||||||
|
.current-project-tide {
|
||||||
|
min-height: 36rem;
|
||||||
|
border: 1px solid rgba(143, 212, 208, 0.55);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 8% 100%, rgba(92, 196, 192, 0.16), transparent 28rem),
|
||||||
|
linear-gradient(135deg, #102832 0%, #101d26 58%, #0c171e 100%);
|
||||||
|
color: var(--text);
|
||||||
|
grid-template-columns: minmax(0, 1.4fr) minmax(21rem, 0.72fr);
|
||||||
|
box-shadow: 0 1.8rem 5rem rgba(3, 9, 13, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide:hover {
|
||||||
|
transform: translateY(-0.2rem);
|
||||||
|
border-color: var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide::after {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 0;
|
||||||
|
right: 24%;
|
||||||
|
bottom: -14rem;
|
||||||
|
width: 44rem;
|
||||||
|
height: 25rem;
|
||||||
|
border: 1px solid rgba(143, 212, 208, 0.12);
|
||||||
|
border-radius: 50%;
|
||||||
|
box-shadow:
|
||||||
|
0 -2.5rem 0 rgba(143, 212, 208, 0.025),
|
||||||
|
0 -5rem 0 rgba(143, 212, 208, 0.025),
|
||||||
|
0 -7.5rem 0 rgba(143, 212, 208, 0.025);
|
||||||
|
content: "";
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-project-main {
|
||||||
|
padding: clamp(2rem, 4vw, 3.8rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-meta {
|
||||||
|
border-bottom-color: rgba(143, 212, 208, 0.3);
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-meta > span:last-child {
|
||||||
|
color: var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-meta i {
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--lime);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-kicker {
|
||||||
|
margin: clamp(3.2rem, 7vw, 6rem) 0 0.9rem;
|
||||||
|
color: var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .tide-heading {
|
||||||
|
gap: clamp(0.8rem, 1.8vw, 1.4rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .tide-heading h3 {
|
||||||
|
color: var(--paper);
|
||||||
|
font-size: clamp(4.2rem, 7.4vw, 7.4rem);
|
||||||
|
line-height: 0.88;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .tide-mark {
|
||||||
|
width: clamp(3rem, 4.5vw, 4.2rem);
|
||||||
|
height: clamp(3rem, 4.5vw, 4.2rem);
|
||||||
|
filter: drop-shadow(0 0.7rem 1rem rgba(60, 166, 182, 0.22));
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-project-main > p:not(.current-kicker) {
|
||||||
|
max-width: 50rem;
|
||||||
|
margin: 1.8rem 0 0;
|
||||||
|
color: #c7d4d2;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
line-height: 1.72;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof {
|
||||||
|
position: relative;
|
||||||
|
z-index: 2;
|
||||||
|
display: grid;
|
||||||
|
max-width: 48rem;
|
||||||
|
margin: 2rem 0 1.7rem;
|
||||||
|
border-top: 1px solid rgba(143, 212, 208, 0.32);
|
||||||
|
border-bottom: 1px solid rgba(143, 212, 208, 0.2);
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof div {
|
||||||
|
padding: 1rem 0.9rem 1rem 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof div + div {
|
||||||
|
padding-left: 1rem;
|
||||||
|
border-left: 1px solid rgba(143, 212, 208, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof strong,
|
||||||
|
.tide-proof span {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof strong {
|
||||||
|
color: var(--lime);
|
||||||
|
font-family: var(--display);
|
||||||
|
font-size: clamp(1.2rem, 2vw, 1.8rem);
|
||||||
|
font-weight: 400;
|
||||||
|
letter-spacing: -0.04em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof span {
|
||||||
|
margin-top: 0.2rem;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.57rem;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-tags {
|
||||||
|
color: var(--muted);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-repo-link {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap {
|
||||||
|
z-index: 2;
|
||||||
|
padding: clamp(2rem, 3.2vw, 3rem);
|
||||||
|
border-left: 1px solid rgba(143, 212, 208, 0.26);
|
||||||
|
background: rgba(7, 18, 24, 0.42);
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap > p {
|
||||||
|
color: var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap ol {
|
||||||
|
border-top-color: rgba(143, 212, 208, 0.55);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap li {
|
||||||
|
min-height: 6.2rem;
|
||||||
|
border-bottom-color: rgba(143, 212, 208, 0.2);
|
||||||
|
grid-template-columns: 1.7rem minmax(0, 1fr) auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap li > span {
|
||||||
|
color: #6ca3a7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap strong {
|
||||||
|
color: var(--text);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap small {
|
||||||
|
max-width: 28rem;
|
||||||
|
color: #9eb1b2;
|
||||||
|
line-height: 1.45;
|
||||||
|
overflow-wrap: anywhere;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap li > b {
|
||||||
|
background: rgba(200, 238, 128, 0.12);
|
||||||
|
color: var(--lime);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap li:last-child > b {
|
||||||
|
background: rgba(240, 153, 110, 0.12);
|
||||||
|
color: var(--coral-bright);
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-watermark {
|
||||||
|
right: auto;
|
||||||
|
bottom: -0.24em;
|
||||||
|
left: -0.04em;
|
||||||
|
color: rgba(143, 212, 208, 0.045);
|
||||||
|
font-size: clamp(10rem, 18vw, 17rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux {
|
||||||
|
min-height: 36rem;
|
||||||
|
padding: 0;
|
||||||
|
border-color: rgba(186, 177, 238, 0.55);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 88% 12%, rgba(184, 146, 255, 0.2), transparent 24rem),
|
||||||
|
linear-gradient(145deg, #1a1d2a, #111821 72%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-frame {
|
||||||
|
position: absolute;
|
||||||
|
top: 2.2rem;
|
||||||
|
right: 2.2rem;
|
||||||
|
bottom: 6.5rem;
|
||||||
|
left: 2.2rem;
|
||||||
|
display: grid;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(237, 241, 233, 0.28);
|
||||||
|
background: #0d1015;
|
||||||
|
box-shadow: 1rem 1rem 0 rgba(111, 92, 170, 0.14);
|
||||||
|
grid-template-rows: auto minmax(0, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar {
|
||||||
|
display: grid;
|
||||||
|
min-height: 2.9rem;
|
||||||
|
padding: 0 1rem;
|
||||||
|
border-bottom: 1px solid rgba(237, 241, 233, 0.16);
|
||||||
|
color: var(--muted);
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.58rem;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
grid-template-columns: auto 1fr auto;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.9rem;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar > span {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar i {
|
||||||
|
width: 0.55rem;
|
||||||
|
height: 0.55rem;
|
||||||
|
border-radius: 50%;
|
||||||
|
background: var(--coral);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar i:nth-child(2) {
|
||||||
|
background: var(--yellow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar i:nth-child(3) {
|
||||||
|
background: var(--lime);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar b {
|
||||||
|
color: var(--text);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar em {
|
||||||
|
color: var(--cyan);
|
||||||
|
font-style: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux .flux-product-shot {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
border: 0;
|
||||||
|
filter: saturate(0.84) contrast(1.04);
|
||||||
|
object-fit: cover;
|
||||||
|
object-position: left center;
|
||||||
|
transform: none;
|
||||||
|
transition: filter 250ms ease, transform 500ms ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux:hover .flux-product-shot {
|
||||||
|
filter: saturate(1) contrast(1.03);
|
||||||
|
transform: scale(1.012);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-caption {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
right: 2.2rem;
|
||||||
|
bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
min-width: 18rem;
|
||||||
|
padding: 0.65rem 0.8rem;
|
||||||
|
border: 1px solid rgba(237, 241, 233, 0.25);
|
||||||
|
background: #171d27;
|
||||||
|
color: var(--text);
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux .flux-product-caption img {
|
||||||
|
width: 2.8rem;
|
||||||
|
height: 2.8rem;
|
||||||
|
border-radius: 22%;
|
||||||
|
filter: none;
|
||||||
|
object-fit: contain;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-caption span,
|
||||||
|
.flux-product-caption strong,
|
||||||
|
.flux-product-caption small {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-caption strong {
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-caption small {
|
||||||
|
margin-top: 0.15rem;
|
||||||
|
color: var(--muted);
|
||||||
|
font-size: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux .image-label {
|
||||||
|
right: auto;
|
||||||
|
bottom: 1.8rem;
|
||||||
|
left: 2.2rem;
|
||||||
|
border-color: rgba(237, 241, 233, 0.22);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-moonlit {
|
||||||
|
min-height: 36rem;
|
||||||
|
overflow: hidden;
|
||||||
|
border-color: rgba(113, 177, 179, 0.58);
|
||||||
|
background:
|
||||||
|
radial-gradient(circle at 12% 90%, rgba(65, 147, 157, 0.18), transparent 24rem),
|
||||||
|
linear-gradient(145deg, #0c2028, #0a141b 72%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-moonlit::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-screen {
|
||||||
|
position: absolute;
|
||||||
|
top: 2.2rem;
|
||||||
|
right: 2.2rem;
|
||||||
|
bottom: 6.6rem;
|
||||||
|
left: 2.2rem;
|
||||||
|
display: block;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1px solid rgba(143, 212, 208, 0.42);
|
||||||
|
background: #071219;
|
||||||
|
box-shadow: 1rem 1rem 0 rgba(31, 103, 112, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-camera {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
top: 0.45rem;
|
||||||
|
left: 50%;
|
||||||
|
width: 0.35rem;
|
||||||
|
height: 0.35rem;
|
||||||
|
transform: translateX(-50%);
|
||||||
|
border: 1px solid rgba(237, 241, 233, 0.22);
|
||||||
|
border-radius: 50%;
|
||||||
|
background: #03090d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-moonlit .moonlit-screen > img {
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
filter: saturate(0.92) contrast(1.04) brightness(0.98);
|
||||||
|
object-fit: contain;
|
||||||
|
object-position: center;
|
||||||
|
transform: none;
|
||||||
|
transition: filter 300ms ease, transform 650ms cubic-bezier(0.2, 0.7, 0.2, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-moonlit:hover .moonlit-screen > img {
|
||||||
|
filter: saturate(1) contrast(1.03) brightness(1.03);
|
||||||
|
transform: scale(1.008);
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
display: grid;
|
||||||
|
min-height: 3.4rem;
|
||||||
|
padding: 0.7rem 0.85rem;
|
||||||
|
border: 1px solid rgba(143, 212, 208, 0.28);
|
||||||
|
background: rgba(5, 17, 24, 0.84);
|
||||||
|
color: var(--text);
|
||||||
|
font-family: var(--mono);
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
align-content: center;
|
||||||
|
backdrop-filter: blur(0.5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session b,
|
||||||
|
.moonlit-session span,
|
||||||
|
.moonlit-session i {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session b {
|
||||||
|
font-size: 0.68rem;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session span {
|
||||||
|
margin-top: 0.12rem;
|
||||||
|
color: #9eb7b8;
|
||||||
|
font-size: 0.54rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session i {
|
||||||
|
color: var(--lime);
|
||||||
|
font-size: 0.56rem;
|
||||||
|
font-style: normal;
|
||||||
|
letter-spacing: 0.08em;
|
||||||
|
grid-column: 2;
|
||||||
|
grid-row: 1 / 3;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-specs {
|
||||||
|
position: absolute;
|
||||||
|
z-index: 3;
|
||||||
|
right: 2.2rem;
|
||||||
|
bottom: 1.5rem;
|
||||||
|
display: flex;
|
||||||
|
color: #aebfc0;
|
||||||
|
font-family: var(--mono);
|
||||||
|
font-size: 0.54rem;
|
||||||
|
letter-spacing: 0.03em;
|
||||||
|
gap: 1.1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-specs span + span::before {
|
||||||
|
margin-right: 1.1rem;
|
||||||
|
color: #4c6e72;
|
||||||
|
content: "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-specs b {
|
||||||
|
color: var(--cyan);
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-moonlit .image-label {
|
||||||
|
right: auto;
|
||||||
|
bottom: 1.35rem;
|
||||||
|
left: 2.2rem;
|
||||||
|
border-color: rgba(143, 212, 208, 0.24);
|
||||||
|
}
|
||||||
|
|
||||||
|
article[data-repo="Moonlit-shell"] .project-feature-copy {
|
||||||
|
border-top-color: var(--cyan);
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-links {
|
||||||
|
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 68rem) {
|
||||||
|
.current-project-tide {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .current-roadmap {
|
||||||
|
border-top: 1px solid rgba(143, 212, 208, 0.26);
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-links {
|
||||||
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-links > *:nth-child(odd) {
|
||||||
|
padding-left: 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-links > *:nth-child(n + 3) {
|
||||||
|
border-top: 2px solid var(--ink);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 52rem) {
|
||||||
|
.current-project-tide .current-kicker {
|
||||||
|
margin-top: 3rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.current-project-tide .tide-heading h3 {
|
||||||
|
font-size: clamp(3.2rem, 15vw, 5rem);
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof div,
|
||||||
|
.tide-proof div + div {
|
||||||
|
padding: 0.75rem 0;
|
||||||
|
border-top: 1px solid rgba(143, 212, 208, 0.18);
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.tide-proof div:first-child {
|
||||||
|
border-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux {
|
||||||
|
min-height: 27rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-moonlit {
|
||||||
|
min-height: 27rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-frame {
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 5.8rem;
|
||||||
|
left: 1rem;
|
||||||
|
box-shadow: 0.55rem 0.55rem 0 rgba(111, 92, 170, 0.14);
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-bar {
|
||||||
|
padding: 0 0.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-caption {
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 0.9rem;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux .image-label {
|
||||||
|
bottom: 1.2rem;
|
||||||
|
left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-screen {
|
||||||
|
top: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 5.8rem;
|
||||||
|
left: 1rem;
|
||||||
|
box-shadow: 0.55rem 0.55rem 0 rgba(31, 103, 112, 0.15);
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session {
|
||||||
|
right: 0.6rem;
|
||||||
|
bottom: 0.6rem;
|
||||||
|
left: 0.6rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-specs {
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1.25rem;
|
||||||
|
gap: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-specs span + span::before {
|
||||||
|
margin-right: 0.55rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-moonlit .image-label {
|
||||||
|
bottom: 1.1rem;
|
||||||
|
left: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-links {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
|
.contact-links > * + *,
|
||||||
|
.contact-links > *:nth-child(n + 3) {
|
||||||
|
padding-left: 0;
|
||||||
|
border-top: 0;
|
||||||
|
border-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 36rem) {
|
||||||
|
.current-project-tide .current-project-main,
|
||||||
|
.current-project-tide .current-roadmap {
|
||||||
|
padding: 1.35rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.flux-product-caption {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session span,
|
||||||
|
.moonlit-specs {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.moonlit-session {
|
||||||
|
grid-template-columns: 1fr auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.project-visual-flux .image-label {
|
||||||
|
right: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@media print {
|
@media print {
|
||||||
.site-header,
|
.site-header,
|
||||||
.signal-strip {
|
.signal-strip {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
--cyan: #8fd4d0;
|
--cyan: #8fd4d0;
|
||||||
--lime: #c8ee80;
|
--lime: #c8ee80;
|
||||||
--yellow: #f3d277;
|
--yellow: #f3d277;
|
||||||
--display: "Syne", "Avenir Next", sans-serif;
|
--display: "Arial Black", "Helvetica Neue", system-ui, sans-serif;
|
||||||
--mono: "IBM Plex Mono", "Courier New", monospace;
|
--mono: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace;
|
||||||
--page-pad: clamp(1.25rem, 4vw, 4.5rem);
|
--page-pad: clamp(1.25rem, 4vw, 4.5rem);
|
||||||
--max-width: 96rem;
|
--max-width: 96rem;
|
||||||
--radius-sm: 0.5rem;
|
--radius-sm: 0.5rem;
|
||||||
|
|
@ -681,7 +681,7 @@ code {
|
||||||
width: 4.1rem;
|
width: 4.1rem;
|
||||||
height: 4.1rem;
|
height: 4.1rem;
|
||||||
border-radius: 0.65rem;
|
border-radius: 0.65rem;
|
||||||
background: var(--paper);
|
background: transparent;
|
||||||
object-fit: contain;
|
object-fit: contain;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,18 +1,28 @@
|
||||||
server {
|
server {
|
||||||
listen 80 default_server;
|
listen 8080 default_server;
|
||||||
listen [::]:80 default_server;
|
listen [::]:8080 default_server;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
root /usr/share/nginx/html;
|
root /usr/share/nginx/html;
|
||||||
index index.html;
|
index index.html;
|
||||||
charset utf-8;
|
charset utf-8;
|
||||||
server_tokens off;
|
server_tokens off;
|
||||||
|
autoindex off;
|
||||||
|
access_log off;
|
||||||
|
|
||||||
|
add_header Strict-Transport-Security "max-age=31536000" always;
|
||||||
add_header X-Content-Type-Options "nosniff" always;
|
add_header X-Content-Type-Options "nosniff" always;
|
||||||
add_header X-Frame-Options "DENY" always;
|
add_header X-Frame-Options "DENY" always;
|
||||||
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
|
add_header X-Permitted-Cross-Domain-Policies "none" always;
|
||||||
add_header Permissions-Policy "camera=(), microphone=(), geolocation=()" always;
|
add_header Referrer-Policy "no-referrer" always;
|
||||||
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; form-action 'none'; frame-ancestors 'none'; script-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src 'self' https://fonts.gstatic.com; img-src 'self' data:; connect-src 'self'" always;
|
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=(), browsing-topics=()" always;
|
||||||
|
add_header Cross-Origin-Opener-Policy "same-origin" always;
|
||||||
|
add_header Cross-Origin-Resource-Policy "same-origin" always;
|
||||||
|
add_header Content-Security-Policy "default-src 'self'; base-uri 'none'; form-action 'none'; frame-ancestors 'none'; object-src 'none'; script-src 'self'; style-src 'self' 'unsafe-inline'; font-src 'self'; img-src 'self' data:; connect-src 'self'; media-src 'self'; manifest-src 'self'; worker-src 'none'; upgrade-insecure-requests" always;
|
||||||
|
|
||||||
|
if ($request_method !~ ^(GET|HEAD)$) {
|
||||||
|
return 405;
|
||||||
|
}
|
||||||
|
|
||||||
gzip on;
|
gzip on;
|
||||||
gzip_vary on;
|
gzip_vary on;
|
||||||
|
|
|
||||||
|
|
@ -2,30 +2,39 @@ name: fiws-page
|
||||||
|
|
||||||
services:
|
services:
|
||||||
fiws-page:
|
fiws-page:
|
||||||
image: fiws-page:latest
|
image: "${IMAGE_NAME:-fiws-page}:${IMAGE_TAG:-latest}"
|
||||||
container_name: fiws-page
|
|
||||||
build:
|
build:
|
||||||
context: .
|
context: .
|
||||||
dockerfile: Dockerfile
|
dockerfile: Dockerfile
|
||||||
target: runtime
|
target: runtime
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
init: true
|
init: true
|
||||||
|
user: "101:101"
|
||||||
read_only: true
|
read_only: true
|
||||||
|
cap_drop:
|
||||||
|
- ALL
|
||||||
security_opt:
|
security_opt:
|
||||||
- no-new-privileges:true
|
- no-new-privileges:true
|
||||||
tmpfs:
|
tmpfs:
|
||||||
- /var/cache/nginx
|
- /tmp:rw,noexec,nosuid,nodev,size=16m,uid=101,gid=101,mode=1770
|
||||||
- /var/run
|
pids_limit: 64
|
||||||
- /tmp
|
mem_limit: 128m
|
||||||
|
cpus: 0.50
|
||||||
|
stop_grace_period: 10s
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: "3"
|
||||||
expose:
|
expose:
|
||||||
- "80"
|
- "8080"
|
||||||
networks:
|
networks:
|
||||||
- proxy
|
- proxy
|
||||||
labels:
|
labels:
|
||||||
traefik.enable: "true"
|
traefik.enable: "true"
|
||||||
traefik.docker.network: "${TRAEFIK_NETWORK:-proxy}"
|
traefik.docker.network: "${TRAEFIK_NETWORK:-proxy}"
|
||||||
|
|
||||||
traefik.http.services.fiws-page.loadbalancer.server.port: "80"
|
traefik.http.services.fiws-page.loadbalancer.server.port: "8080"
|
||||||
|
|
||||||
traefik.http.routers.fiws-page.rule: "Host(`${DOMAIN:-fiwlabs.dev}`)"
|
traefik.http.routers.fiws-page.rule: "Host(`${DOMAIN:-fiwlabs.dev}`)"
|
||||||
traefik.http.routers.fiws-page.entrypoints: "${TRAEFIK_HTTPS_ENTRYPOINT:-websecure}"
|
traefik.http.routers.fiws-page.entrypoints: "${TRAEFIK_HTTPS_ENTRYPOINT:-websecure}"
|
||||||
|
|
|
||||||
BIN
icon.png
|
Before Width: | Height: | Size: 6.5 KiB After Width: | Height: | Size: 4.8 KiB |
BIN
img/avatar.png
|
Before Width: | Height: | Size: 218 KiB After Width: | Height: | Size: 197 KiB |
|
Before Width: | Height: | Size: 51 KiB After Width: | Height: | Size: 48 KiB |
|
Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 131 KiB |
1
img/icons/archlinux.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Arch Linux</title><path d="M11.39.605C10.376 3.092 9.764 4.72 8.635 7.132c.693.734 1.543 1.589 2.923 2.554-1.484-.61-2.496-1.224-3.252-1.86C6.86 10.842 4.596 15.138 0 23.395c3.612-2.085 6.412-3.37 9.021-3.862a6.61 6.61 0 01-.171-1.547l.003-.115c.058-2.315 1.261-4.095 2.687-3.973 1.426.12 2.534 2.096 2.478 4.409a6.52 6.52 0 01-.146 1.243c2.58.505 5.352 1.787 8.914 3.844-.702-1.293-1.33-2.459-1.929-3.57-.943-.73-1.926-1.682-3.933-2.713 1.38.359 2.367.772 3.137 1.234-6.09-11.334-6.582-12.84-8.67-17.74zM22.898 21.36v-.623h-.234v-.084h.562v.084h-.234v.623h.331v-.707h.142l.167.5.034.107a2.26 2.26 0 01.038-.114l.17-.493H24v.707h-.091v-.593l-.206.593h-.084l-.205-.602v.602h-.091"/></svg>
|
||||||
|
After Width: | Height: | Size: 780 B |
1
img/icons/docker.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Docker</title><path d="M13.983 11.078h2.119a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.119a.185.185 0 00-.185.185v1.888c0 .102.083.185.185.185m-2.954-5.43h2.118a.186.186 0 00.186-.186V3.574a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m0 2.716h2.118a.187.187 0 00.186-.186V6.29a.186.186 0 00-.186-.185h-2.118a.185.185 0 00-.185.185v1.887c0 .102.082.185.185.186m-2.93 0h2.12a.186.186 0 00.184-.186V6.29a.185.185 0 00-.185-.185H8.1a.185.185 0 00-.185.185v1.887c0 .102.083.185.185.186m-2.964 0h2.119a.186.186 0 00.185-.186V6.29a.185.185 0 00-.185-.185H5.136a.186.186 0 00-.186.185v1.887c0 .102.084.185.186.186m5.893 2.715h2.118a.186.186 0 00.186-.185V9.006a.186.186 0 00-.186-.186h-2.118a.185.185 0 00-.185.185v1.888c0 .102.082.185.185.185m-2.93 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.083.185.185.185m-2.964 0h2.119a.185.185 0 00.185-.185V9.006a.185.185 0 00-.184-.186h-2.12a.186.186 0 00-.186.186v1.887c0 .102.084.185.186.185m-2.92 0h2.12a.185.185 0 00.184-.185V9.006a.185.185 0 00-.184-.186h-2.12a.185.185 0 00-.184.185v1.888c0 .102.082.185.185.185M23.763 9.89c-.065-.051-.672-.51-1.954-.51-.338.001-.676.03-1.01.087-.248-1.7-1.653-2.53-1.716-2.566l-.344-.199-.226.327c-.284.438-.49.922-.612 1.43-.23.97-.09 1.882.403 2.661-.595.332-1.55.413-1.744.42H.751a.751.751 0 00-.75.748 11.376 11.376 0 00.692 4.062c.545 1.428 1.355 2.48 2.41 3.124 1.18.723 3.1 1.137 5.275 1.137.983.003 1.963-.086 2.93-.266a12.248 12.248 0 003.823-1.389c.98-.567 1.86-1.288 2.61-2.136 1.252-1.418 1.998-2.997 2.553-4.4h.221c1.372 0 2.215-.549 2.68-1.009.309-.293.55-.65.707-1.046l.098-.288Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
1
img/icons/ffmpeg.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>FFmpeg</title><path d="M21.72 17.91V6.5l-.53-.49L9.05 18.52l-1.29-.06L24 1.53l-.33-.95-11.93 1-5.75 6.6v-.23l4.7-5.39-1.38-.77-9.11.77v2.85l1.91.46v.01l.19-.01-.56.66v10.6c.609-.126 1.22-.241 1.83-.36L14.12 5.22l.83-.04L0 21.44l9.67.82 1.35-.77 6.82-6.74v2.15l-5.72 5.57 11.26.95.35-.94v-3.16l-3.29-.18c.434-.403.858-.816 1.28-1.23z"/></svg>
|
||||||
|
After Width: | Height: | Size: 434 B |
1
img/icons/githubactions.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GitHub Actions</title><path d="M10.984 13.836a.5.5 0 0 1-.353-.146l-.745-.743a.5.5 0 1 1 .706-.708l.392.391 1.181-1.18a.5.5 0 0 1 .708.707l-1.535 1.533a.504.504 0 0 1-.354.146zm9.353-.147l1.534-1.532a.5.5 0 0 0-.707-.707l-1.181 1.18-.392-.391a.5.5 0 1 0-.706.708l.746.743a.497.497 0 0 0 .706-.001zM4.527 7.452l2.557-1.585A1 1 0 0 0 7.09 4.17L4.533 2.56A1 1 0 0 0 3 3.406v3.196a1.001 1.001 0 0 0 1.527.85zm2.03-2.436L4 6.602V3.406l2.557 1.61zM24 12.5c0 1.93-1.57 3.5-3.5 3.5a3.503 3.503 0 0 1-3.46-3h-2.08a3.503 3.503 0 0 1-3.46 3 3.502 3.502 0 0 1-3.46-3h-.558c-.972 0-1.85-.399-2.482-1.042V17c0 1.654 1.346 3 3 3h.04c.244-1.693 1.7-3 3.46-3 1.93 0 3.5 1.57 3.5 3.5S13.43 24 11.5 24a3.502 3.502 0 0 1-3.46-3H8c-2.206 0-4-1.794-4-4V9.899A5.008 5.008 0 0 1 0 5c0-2.757 2.243-5 5-5s5 2.243 5 5a5.005 5.005 0 0 1-4.952 4.998A2.482 2.482 0 0 0 7.482 12h.558c.244-1.693 1.7-3 3.46-3a3.502 3.502 0 0 1 3.46 3h2.08a3.503 3.503 0 0 1 3.46-3c1.93 0 3.5 1.57 3.5 3.5zm-15 8c0 1.378 1.122 2.5 2.5 2.5s2.5-1.122 2.5-2.5-1.122-2.5-2.5-2.5S9 19.122 9 20.5zM5 9c2.206 0 4-1.794 4-4S7.206 1 5 1 1 2.794 1 5s1.794 4 4 4zm9 3.5c0-1.378-1.122-2.5-2.5-2.5S9 11.122 9 12.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5zm9 0c0-1.378-1.122-2.5-2.5-2.5S18 11.122 18 12.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5zm-13 8a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0zm2 0a.5.5 0 1 0 1 0 .5.5 0 0 0-1 0zm12 0c0 1.93-1.57 3.5-3.5 3.5a3.503 3.503 0 0 1-3.46-3.002c-.007.001-.013.005-.021.005l-.506.017h-.017a.5.5 0 0 1-.016-.999l.506-.017c.018-.002.035.006.052.007A3.503 3.503 0 0 1 20.5 17c1.93 0 3.5 1.57 3.5 3.5zm-1 0c0-1.378-1.122-2.5-2.5-2.5S18 19.122 18 20.5s1.122 2.5 2.5 2.5 2.5-1.122 2.5-2.5z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.7 KiB |
1
img/icons/gnubash.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>GNU Bash</title><path d="M21.038,4.9l-7.577-4.498C13.009,0.134,12.505,0,12,0c-0.505,0-1.009,0.134-1.462,0.403L2.961,4.9 C2.057,5.437,1.5,6.429,1.5,7.503v8.995c0,1.073,0.557,2.066,1.462,2.603l7.577,4.497C10.991,23.866,11.495,24,12,24 c0.505,0,1.009-0.134,1.461-0.402l7.577-4.497c0.904-0.537,1.462-1.529,1.462-2.603V7.503C22.5,6.429,21.943,5.437,21.038,4.9z M15.17,18.946l0.013,0.646c0.001,0.078-0.05,0.167-0.111,0.198l-0.383,0.22c-0.061,0.031-0.111-0.007-0.112-0.085L14.57,19.29 c-0.328,0.136-0.66,0.169-0.872,0.084c-0.04-0.016-0.057-0.075-0.041-0.142l0.139-0.584c0.011-0.046,0.036-0.092,0.069-0.121 c0.012-0.011,0.024-0.02,0.036-0.026c0.022-0.011,0.043-0.014,0.062-0.006c0.229,0.077,0.521,0.041,0.802-0.101 c0.357-0.181,0.596-0.545,0.592-0.907c-0.003-0.328-0.181-0.465-0.613-0.468c-0.55,0.001-1.064-0.107-1.072-0.917 c-0.007-0.667,0.34-1.361,0.889-1.8l-0.007-0.652c-0.001-0.08,0.048-0.168,0.111-0.2l0.37-0.236 c0.061-0.031,0.111,0.007,0.112,0.087l0.006,0.653c0.273-0.109,0.511-0.138,0.726-0.088c0.047,0.012,0.067,0.076,0.048,0.151 l-0.144,0.578c-0.011,0.044-0.036,0.088-0.065,0.116c-0.012,0.012-0.025,0.021-0.038,0.028c-0.019,0.01-0.038,0.013-0.057,0.009 c-0.098-0.022-0.332-0.073-0.699,0.113c-0.385,0.195-0.52,0.53-0.517,0.778c0.003,0.297,0.155,0.387,0.681,0.396 c0.7,0.012,1.003,0.318,1.01,1.023C16.105,17.747,15.736,18.491,15.17,18.946z M19.143,17.859c0,0.06-0.008,0.116-0.058,0.145 l-1.916,1.164c-0.05,0.029-0.09,0.004-0.09-0.056v-0.494c0-0.06,0.037-0.093,0.087-0.122l1.887-1.129 c0.05-0.029,0.09-0.004,0.09,0.056V17.859z M20.459,6.797l-7.168,4.427c-0.894,0.523-1.553,1.109-1.553,2.187v8.833 c0,0.645,0.26,1.063,0.66,1.184c-0.131,0.023-0.264,0.039-0.398,0.039c-0.42,0-0.833-0.114-1.197-0.33L3.226,18.64 c-0.741-0.44-1.201-1.261-1.201-2.142V7.503c0-0.881,0.46-1.702,1.201-2.142l7.577-4.498c0.363-0.216,0.777-0.33,1.197-0.33 c0.419,0,0.833,0.114,1.197,0.33l7.577,4.498c0.624,0.371,1.046,1.013,1.164,1.732C21.686,6.557,21.12,6.411,20.459,6.797z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2 KiB |
1
img/icons/go.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Go</title><path d="M1.811 10.231c-.047 0-.058-.023-.035-.059l.246-.315c.023-.035.081-.058.128-.058h4.172c.046 0 .058.035.035.07l-.199.303c-.023.036-.082.07-.117.07zM.047 11.306c-.047 0-.059-.023-.035-.058l.245-.316c.023-.035.082-.058.129-.058h5.328c.047 0 .07.035.058.07l-.093.28c-.012.047-.058.07-.105.07zm2.828 1.075c-.047 0-.059-.035-.035-.07l.163-.292c.023-.035.07-.07.117-.07h2.337c.047 0 .07.035.07.082l-.023.28c0 .047-.047.082-.082.082zm12.129-2.36c-.736.187-1.239.327-1.963.514-.176.046-.187.058-.34-.117-.174-.199-.303-.327-.548-.444-.737-.362-1.45-.257-2.115.175-.795.514-1.204 1.274-1.192 2.22.011.935.654 1.706 1.577 1.835.795.105 1.46-.175 1.987-.77.105-.13.198-.27.315-.434H10.47c-.245 0-.304-.152-.222-.35.152-.362.432-.97.596-1.274a.315.315 0 01.292-.187h4.253c-.023.316-.023.631-.07.947a4.983 4.983 0 01-.958 2.29c-.841 1.11-1.94 1.8-3.33 1.986-1.145.152-2.209-.07-3.143-.77-.865-.655-1.356-1.52-1.484-2.595-.152-1.274.222-2.419.993-3.424.83-1.086 1.928-1.776 3.272-2.02 1.098-.2 2.15-.07 3.096.571.62.41 1.063.97 1.356 1.648.07.105.023.164-.117.2m3.868 6.461c-1.064-.024-2.034-.328-2.852-1.029a3.665 3.665 0 01-1.262-2.255c-.21-1.32.152-2.489.947-3.529.853-1.122 1.881-1.706 3.272-1.95 1.192-.21 2.314-.095 3.33.595.923.63 1.496 1.484 1.648 2.605.198 1.578-.257 2.863-1.344 3.962-.771.783-1.718 1.273-2.805 1.495-.315.06-.63.07-.934.106zm2.78-4.72c-.011-.153-.011-.27-.034-.387-.21-1.157-1.274-1.81-2.384-1.554-1.087.245-1.788.935-2.045 2.033-.21.912.234 1.835 1.075 2.21.643.28 1.285.244 1.905-.07.923-.48 1.425-1.228 1.484-2.233z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.6 KiB |
1
img/icons/json.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>JSON</title><path d="M12.043 23.968c.479-.004.953-.029 1.426-.094a11.805 11.805 0 003.146-.863 12.404 12.404 0 003.793-2.542 11.977 11.977 0 002.44-3.427 11.794 11.794 0 001.02-3.476c.149-1.16.135-2.346-.045-3.499a11.96 11.96 0 00-.793-2.788 11.197 11.197 0 00-.854-1.617c-1.168-1.837-2.861-3.314-4.81-4.3a12.835 12.835 0 00-2.172-.87h-.005c.119.063.24.132.345.201.12.074.239.146.351.225a8.93 8.93 0 011.559 1.33c1.063 1.145 1.797 2.548 2.218 4.041.284.982.434 1.998.495 3.017.044.743.044 1.491-.047 2.229-.149 1.27-.554 2.51-1.228 3.596a7.475 7.475 0 01-1.903 2.084c-1.244.928-2.877 1.482-4.436 1.114a3.916 3.916 0 01-.748-.258 4.692 4.692 0 01-.779-.45 6.08 6.08 0 01-1.244-1.105 6.507 6.507 0 01-1.049-1.747 7.366 7.366 0 01-.494-2.54c-.03-1.273.225-2.553.854-3.67a6.43 6.43 0 011.663-1.918c.225-.178.464-.333.704-.479l.016-.007a5.121 5.121 0 00-1.441-.12 4.963 4.963 0 00-1.228.24c-.359.12-.704.27-1.019.45a6.146 6.146 0 00-.733.494c-.211.18-.42.36-.615.555-1.123 1.153-1.768 2.682-2.022 4.256-.15.973-.15 1.96-.091 2.95.105 1.395.391 2.787.945 4.062a8.518 8.518 0 001.348 2.173 8.14 8.14 0 003.132 2.23 7.934 7.934 0 002.113.54c.074.015.149.015.209.015zm-2.934-.398a4.102 4.102 0 01-.45-.228 8.5 8.5 0 01-2.038-1.534c-1.094-1.137-1.827-2.566-2.247-4.08a15.184 15.184 0 01-.495-3.172 12.14 12.14 0 01.046-2.082c.135-1.257.495-2.501 1.124-3.58a6.889 6.889 0 011.783-2.053 6.23 6.23 0 011.633-.9 5.363 5.363 0 013.522-.045c.029 0 .029 0 .045.03.015.015.045.015.06.03.045.016.104.045.165.074.239.12.479.271.704.42a6.294 6.294 0 012.097 2.502c.42.914.615 1.934.631 2.938.014 1.079-.18 2.157-.645 3.146a6.42 6.42 0 01-2.638 2.832c.09.03.18.045.271.075.225.044.449.074.688.074 1.468.045 2.892-.66 3.94-1.647.195-.18.375-.375.54-.585.225-.27.435-.54.614-.823.239-.375.435-.75.614-1.154a8.112 8.112 0 00.509-1.664c.196-1.004.211-2.022.149-3.026-.135-2.022-.673-4.045-1.842-5.724a9.054 9.054 0 00-.555-.719 9.868 9.868 0 00-1.063-1.034 8.477 8.477 0 00-1.363-.915 9.927 9.927 0 00-1.692-.598l-.3-.06c-.209-.03-.42-.044-.634-.06a8.453 8.453 0 00-1.015.016c-.704.045-1.412.16-2.112.337C5.799 1.227 2.863 3.566 1.3 6.67A11.834 11.834 0 00.238 9.801a11.81 11.81 0 00-.104 3.775c.12 1.02.374 2.023.778 2.977.227.57.511 1.124.825 1.648 1.094 1.783 2.683 3.236 4.51 4.24.688.39 1.408.69 2.157.944.226.074.45.15.689.21z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.4 KiB |
1
img/icons/kotlin.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Kotlin</title><path d="M24 24H0V0h24L12 12Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 145 B |
1
img/icons/lua.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Lua</title><path d="M.38 10.377l-.272-.037c-.048.344-.082.695-.101 1.041l.275.016c.018-.34.051-.682.098-1.02zM4.136 3.289l-.184-.205c-.258.232-.509.48-.746.734l.202.188c.231-.248.476-.49.728-.717zM5.769 2.059l-.146-.235c-.296.186-.586.385-.863.594l.166.219c.27-.203.554-.399.843-.578zM1.824 18.369c.185.297.384.586.593.863l.22-.164c-.205-.271-.399-.555-.58-.844l-.233.145zM1.127 16.402l-.255.104c.129.318.274.635.431.943l.005.01.245-.125-.005-.01c-.153-.301-.295-.611-.421-.922zM.298 9.309l.269.063c.076-.332.168-.664.272-.986l-.261-.087c-.108.332-.202.672-.28 1.01zM.274 12.42l-.275.01c.012.348.04.699.083 1.043l.273-.033c-.042-.336-.069-.68-.081-1.02zM.256 14.506c.073.34.162.682.264 1.014l.263-.08c-.1-.326-.187-.658-.258-.99l-.269.056zM11.573.275L11.563 0c-.348.012-.699.039-1.044.082l.034.273c.338-.041.68-.068 1.02-.08zM23.221 8.566c.1.326.186.66.256.992l.27-.059c-.072-.34-.16-.682-.262-1.014l-.264.081zM17.621 1.389c-.309-.164-.627-.314-.947-.449l-.107.252c.314.133.625.281.926.439l.128-.242zM15.693.572c-.332-.105-.67-.199-1.01-.277l-.063.268c.332.076.664.168.988.273l.085-.264zM6.674 1.545c.298-.15.606-.291.916-.418L7.486.873c-.317.127-.632.272-.937.428l-.015.008.125.244.015-.008zM23.727 11.588l.275-.01a11.797 11.797 0 0 0-.082-1.045l-.273.033c.041.338.068.682.08 1.022zM13.654.105c-.346-.047-.696-.08-1.043-.098l-.014.273c.339.018.683.051 1.019.098l.038-.273zM9.544.527l-.058-.27c-.34.072-.681.16-1.014.264l.081.262c.325-.099.659-.185.991-.256zM1.921 5.469l.231.15c.185-.285.384-.566.592-.834l-.217-.17c-.213.276-.417.563-.606.854zM.943 7.318l.253.107c.132-.313.28-.625.439-.924l-.243-.128c-.163.307-.314.625-.449.945zM18.223 21.943l.145.234c.295-.186.586-.385.863-.594l-.164-.219c-.272.204-.557.4-.844.579zM21.248 19.219l.217.17c.215-.273.418-.561.607-.854l-.23-.148c-.186.285-.385.564-.594.832zM19.855 20.715l.184.203c.258-.23.51-.479.746-.732l-.201-.188c-.23.248-.477.488-.729.717zM22.359 17.504l.244.129c.162-.307.314-.625.449-.945l-.254-.107a11.27 11.27 0 0 1-.439.923zM23.617 13.629l.273.039c.049-.346.082-.695.102-1.043l-.275-.014c-.018.338-.051.682-.1 1.018zM23.156 15.621l.264.086c.107-.332.201-.67.279-1.01l-.268-.063c-.077.333-.169.665-.275.987zM22.453 6.672c.154.303.297.617.424.932l.256-.104c-.131-.322-.277-.643-.436-.953l-.244.125zM8.296 23.418c.331.107.67.201 1.009.279l.062-.268c-.331-.076-.663-.168-.986-.273l-.085.262zM10.335 23.889c.345.049.696.082 1.043.102l.014-.275c-.339-.018-.682-.051-1.019-.098l-.038.271zM17.326 22.449c-.303.154-.613.297-.926.424l.104.256c.318-.131.639-.275.947-.434l.004-.002-.123-.246-.006.002zM4.613 21.467c.274.213.562.418.854.605l.149-.23c-.285-.184-.565-.385-.833-.592l-.17.217zM12.417 23.725l.009.275c.348-.014.699-.041 1.045-.084l-.035-.271c-.336.041-.68.068-1.019.08zM6.37 22.604c.307.162.625.314.946.449l.107-.254c-.313-.133-.624-.279-.924-.439l-.129.244zM3.083 20.041c.233.258.48.51.734.746l.188-.201c-.249-.23-.49-.477-.717-.729l-.205.184zM14.445 23.475l.059.27c.34-.074.68-.162 1.014-.266l-.082-.262c-.325.099-.659.185-.991.258zM21.18.129A2.689 2.689 0 1 0 21.18 5.507 2.689 2.689 0 1 0 21.18.129zM15.324 15.447c0 .471.314.66.852.66.67 0 1.297-.396 1.297-1.016v-.645c-.23.107-.379.141-1.107.24-.735.109-1.042.306-1.042.761zM12 2.818c-5.07 0-9.18 4.109-9.18 9.18 0 5.068 4.11 9.18 9.18 9.18 5.07 0 9.18-4.111 9.18-9.18 0-5.07-4.11-9.18-9.18-9.18zm-2.487 13.77H5.771v-6.023h.769v5.346h2.974v.677zm4.13 0h-.619v-.67c-.405.57-.811.793-1.446.793-.843 0-1.38-.463-1.38-1.182v-3.271h.686v3c0 .52.347.85.893.85.719 0 1.181-.578 1.181-1.461v-2.389h.686v4.33zm-.53-8.393c0-1.484 1.205-2.689 2.689-2.689s2.688 1.205 2.688 2.689-1.203 2.688-2.688 2.688-2.689-1.203-2.689-2.688zm5.567 7.856v.52c-.223.059-.33.074-.471.074-.34 0-.637-.238-.711-.57-.381.406-.918.637-1.471.637-.877 0-1.422-.463-1.422-1.248 0-.527.256-.916.76-1.123.266-.107.414-.141 1.389-.264.545-.066.719-.191.719-.48v-.182c0-.412-.348-.645-.967-.645-.645 0-.957.24-1.016.77h-.693c.041-1 .686-1.404 1.734-1.404 1.066 0 1.627.412 1.627 1.182v2.412c0 .215.133.338.373.338.041-.002.074-.002.149-.017z"/></svg>
|
||||||
|
After Width: | Height: | Size: 4 KiB |
1
img/icons/macos.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>macOS</title><circle cx="12" cy="12" r="9.6" fill="none" stroke="#11171a" stroke-width="1.4"/><path d="M12 2.4a9.6 9.6 0 0 1 0 19.2Z" fill="#11171a"/><circle cx="8" cy="9.6" r="1.15" fill="#11171a"/><path d="M6.4 13.8q1.9 2.8 4.2.7" fill="none" stroke="#11171a" stroke-linecap="round" stroke-width="1.4"/></svg>
|
||||||
|
After Width: | Height: | Size: 390 B |
1
img/icons/modrinth.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Modrinth</title><path d="M12.252.004a11.78 11.768 0 0 0-8.92 3.73 11 10.999 0 0 0-2.17 3.11 11.37 11.359 0 0 0-1.16 5.169c0 1.42.17 2.5.6 3.77.24.759.77 1.899 1.17 2.529a12.3 12.298 0 0 0 8.85 5.639c.44.05 2.54.07 2.76.02.2-.04.22.1-.26-1.7l-.36-1.37-1.01-.06a8.5 8.489 0 0 1-5.18-1.8 5.34 5.34 0 0 1-1.3-1.26c0-.05.34-.28.74-.5a37.572 37.545 0 0 1 2.88-1.629c.03 0 .5.45 1.06.98l1 .97 2.07-.43 2.06-.43 1.47-1.47c.8-.8 1.48-1.5 1.48-1.52 0-.09-.42-1.63-.46-1.7-.04-.06-.2-.03-1.02.18-.53.13-1.2.3-1.45.4l-.48.15-.53.53-.53.53-.93.1-.93.07-.52-.5a2.7 2.7 0 0 1-.96-1.7l-.13-.6.43-.57c.68-.9.68-.9 1.46-1.1.4-.1.65-.2.83-.33.13-.099.65-.579 1.14-1.069l.9-.9-.7-.7-.7-.7-1.95.54c-1.07.3-1.96.53-1.97.53-.03 0-2.23 2.48-2.63 2.97l-.29.35.28 1.03c.16.56.3 1.16.31 1.34l.03.3-.34.23c-.37.23-2.22 1.3-2.84 1.63-.36.2-.37.2-.44.1-.08-.1-.23-.6-.32-1.03-.18-.86-.17-2.75.02-3.73a8.84 8.839 0 0 1 7.9-6.93c.43-.03.77-.08.78-.1.06-.17.5-2.999.47-3.039-.01-.02-.1-.02-.2-.03Zm3.68.67c-.2 0-.3.1-.37.38-.06.23-.46 2.42-.46 2.52 0 .04.1.11.22.16a8.51 8.499 0 0 1 2.99 2 8.38 8.379 0 0 1 2.16 3.449 6.9 6.9 0 0 1 .4 2.8c0 1.07 0 1.27-.1 1.73a9.37 9.369 0 0 1-1.76 3.769c-.32.4-.98 1.06-1.37 1.38-.38.32-1.54 1.1-1.7 1.14-.1.03-.1.06-.07.26.03.18.64 2.56.7 2.78l.06.06a12.07 12.058 0 0 0 7.27-9.4c.13-.77.13-2.58 0-3.4a11.96 11.948 0 0 0-5.73-8.578c-.7-.42-2.05-1.06-2.25-1.06Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.4 KiB |
1
img/icons/rust.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Rust</title><path d="M23.8346 11.7033l-1.0073-.6236a13.7268 13.7268 0 00-.0283-.2936l.8656-.8069a.3483.3483 0 00-.1154-.578l-1.1066-.414a8.4958 8.4958 0 00-.087-.2856l.6904-.9587a.3462.3462 0 00-.2257-.5446l-1.1663-.1894a9.3574 9.3574 0 00-.1407-.2622l.49-1.0761a.3437.3437 0 00-.0274-.3361.3486.3486 0 00-.3006-.154l-1.1845.0416a6.7444 6.7444 0 00-.1873-.2268l.2723-1.153a.3472.3472 0 00-.417-.4172l-1.1532.2724a14.0183 14.0183 0 00-.2278-.1873l.0415-1.1845a.3442.3442 0 00-.49-.328l-1.076.491c-.0872-.0476-.1742-.0952-.2623-.1407l-.1903-1.1673A.3483.3483 0 0016.256.955l-.9597.6905a8.4867 8.4867 0 00-.2855-.086l-.414-1.1066a.3483.3483 0 00-.5781-.1154l-.8069.8666a9.2936 9.2936 0 00-.2936-.0284L12.2946.1683a.3462.3462 0 00-.5892 0l-.6236 1.0073a13.7383 13.7383 0 00-.2936.0284L9.9803.3374a.3462.3462 0 00-.578.1154l-.4141 1.1065c-.0962.0274-.1903.0567-.2855.086L7.744.955a.3483.3483 0 00-.5447.2258L7.009 2.348a9.3574 9.3574 0 00-.2622.1407l-1.0762-.491a.3462.3462 0 00-.49.328l.0416 1.1845a7.9826 7.9826 0 00-.2278.1873L3.8413 3.425a.3472.3472 0 00-.4171.4171l.2713 1.1531c-.0628.075-.1255.1509-.1863.2268l-1.1845-.0415a.3462.3462 0 00-.328.49l.491 1.0761a9.167 9.167 0 00-.1407.2622l-1.1662.1894a.3483.3483 0 00-.2258.5446l.6904.9587a13.303 13.303 0 00-.087.2855l-1.1065.414a.3483.3483 0 00-.1155.5781l.8656.807a9.2936 9.2936 0 00-.0283.2935l-1.0073.6236a.3442.3442 0 000 .5892l1.0073.6236c.008.0982.0182.1964.0283.2936l-.8656.8079a.3462.3462 0 00.1155.578l1.1065.4141c.0273.0962.0567.1914.087.2855l-.6904.9587a.3452.3452 0 00.2268.5447l1.1662.1893c.0456.088.0922.1751.1408.2622l-.491 1.0762a.3462.3462 0 00.328.49l1.1834-.0415c.0618.0769.1235.1528.1873.2277l-.2713 1.1541a.3462.3462 0 00.4171.4161l1.153-.2713c.075.0638.151.1255.2279.1863l-.0415 1.1845a.3442.3442 0 00.49.327l1.0761-.49c.087.0486.1741.0951.2622.1407l.1903 1.1662a.3483.3483 0 00.5447.2268l.9587-.6904a9.299 9.299 0 00.2855.087l.414 1.1066a.3452.3452 0 00.5781.1154l.8079-.8656c.0972.0111.1954.0203.2936.0294l.6236 1.0073a.3472.3472 0 00.5892 0l.6236-1.0073c.0982-.0091.1964-.0183.2936-.0294l.8069.8656a.3483.3483 0 00.578-.1154l.4141-1.1066a8.4626 8.4626 0 00.2855-.087l.9587.6904a.3452.3452 0 00.5447-.2268l.1903-1.1662c.088-.0456.1751-.0931.2622-.1407l1.0762.49a.3472.3472 0 00.49-.327l-.0415-1.1845a6.7267 6.7267 0 00.2267-.1863l1.1531.2713a.3472.3472 0 00.4171-.416l-.2713-1.1542c.0628-.0749.1255-.1508.1863-.2278l1.1845.0415a.3442.3442 0 00.328-.49l-.49-1.076c.0475-.0872.0951-.1742.1407-.2623l1.1662-.1893a.3483.3483 0 00.2258-.5447l-.6904-.9587.087-.2855 1.1066-.414a.3462.3462 0 00.1154-.5781l-.8656-.8079c.0101-.0972.0202-.1954.0283-.2936l1.0073-.6236a.3442.3442 0 000-.5892zm-6.7413 8.3551a.7138.7138 0 01.2986-1.396.714.714 0 11-.2997 1.396zm-.3422-2.3142a.649.649 0 00-.7715.5l-.3573 1.6685c-1.1035.501-2.3285.7795-3.6193.7795a8.7368 8.7368 0 01-3.6951-.814l-.3574-1.6684a.648.648 0 00-.7714-.499l-1.473.3158a8.7216 8.7216 0 01-.7613-.898h7.1676c.081 0 .1356-.0141.1356-.088v-2.536c0-.074-.0536-.0881-.1356-.0881h-2.0966v-1.6077h2.2677c.2065 0 1.1065.0587 1.394 1.2088.0901.3533.2875 1.5044.4232 1.8729.1346.413.6833 1.2381 1.2685 1.2381h3.5716a.7492.7492 0 00.1296-.0131 8.7874 8.7874 0 01-.8119.9526zM6.8369 20.024a.714.714 0 11-.2997-1.396.714.714 0 01.2997 1.396zM4.1177 8.9972a.7137.7137 0 11-1.304.5791.7137.7137 0 011.304-.579zm-.8352 1.9813l1.5347-.6824a.65.65 0 00.33-.8585l-.3158-.7147h1.2432v5.6025H3.5669a8.7753 8.7753 0 01-.2834-3.348zm6.7343-.5437V8.7836h2.9601c.153 0 1.0792.1772 1.0792.8697 0 .575-.7107.7815-1.2948.7815zm10.7574 1.4862c0 .2187-.008.4363-.0243.651h-.9c-.09 0-.1265.0586-.1265.1477v.413c0 .973-.5487 1.1846-1.0296 1.2382-.4576.0517-.9648-.1913-1.0275-.4717-.2704-1.5186-.7198-1.8436-1.4305-2.4034.8817-.5599 1.799-1.386 1.799-2.4915 0-1.1936-.819-1.9458-1.3769-2.3153-.7825-.5163-1.6491-.6195-1.883-.6195H5.4682a8.7651 8.7651 0 014.907-2.7699l1.0974 1.151a.648.648 0 00.9182.0213l1.227-1.1743a8.7753 8.7753 0 016.0044 4.2762l-.8403 1.8982a.652.652 0 00.33.8585l1.6178.7188c.0283.2875.0425.577.0425.8717zm-9.3006-9.5993a.7128.7128 0 11.984 1.0316.7137.7137 0 01-.984-1.0316zm8.3389 6.71a.7107.7107 0 01.9395-.3625.7137.7137 0 11-.9405.3635z"/></svg>
|
||||||
|
After Width: | Height: | Size: 4.2 KiB |
1
img/icons/selfhosting.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Self-hosting</title><path d="M12 2C7.03 2 3 3.34 3 5s4.03 3 9 3 9-1.34 9-3-4.03-3-9-3Z"/><path d="M3 8.2v3.3c0 1.66 4.03 3 9 3s9-1.34 9-3V8.2c-1.6 1.28-5.1 2.3-9 2.3S4.6 9.48 3 8.2Z"/><path d="M3 13.7V17c0 1.66 4.03 3 9 3s9-1.34 9-3v-3.3c-1.6 1.28-5.1 2.3-9 2.3s-7.4-1.02-9-2.3Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 381 B |
1
img/icons/typescript.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>TypeScript</title><path d="M1.125 0C.502 0 0 .502 0 1.125v21.75C0 23.498.502 24 1.125 24h21.75c.623 0 1.125-.502 1.125-1.125V1.125C24 .502 23.498 0 22.875 0zm17.363 9.75c.612 0 1.154.037 1.627.111a6.38 6.38 0 0 1 1.306.34v2.458a3.95 3.95 0 0 0-.643-.361 5.093 5.093 0 0 0-.717-.26 5.453 5.453 0 0 0-1.426-.2c-.3 0-.573.028-.819.086a2.1 2.1 0 0 0-.623.242c-.17.104-.3.229-.393.374a.888.888 0 0 0-.14.49c0 .196.053.373.156.529.104.156.252.304.443.444s.423.276.696.41c.273.135.582.274.926.416.47.197.892.407 1.266.628.374.222.695.473.963.753.268.279.472.598.614.957.142.359.214.776.214 1.253 0 .657-.125 1.21-.373 1.656a3.033 3.033 0 0 1-1.012 1.085 4.38 4.38 0 0 1-1.487.596c-.566.12-1.163.18-1.79.18a9.916 9.916 0 0 1-1.84-.164 5.544 5.544 0 0 1-1.512-.493v-2.63a5.033 5.033 0 0 0 3.237 1.2c.333 0 .624-.03.872-.09.249-.06.456-.144.623-.25.166-.108.29-.234.373-.38a1.023 1.023 0 0 0-.074-1.089 2.12 2.12 0 0 0-.537-.5 5.597 5.597 0 0 0-.807-.444 27.72 27.72 0 0 0-1.007-.436c-.918-.383-1.602-.852-2.053-1.405-.45-.553-.676-1.222-.676-2.005 0-.614.123-1.141.369-1.582.246-.441.58-.804 1.004-1.089a4.494 4.494 0 0 1 1.47-.629 7.536 7.536 0 0 1 1.77-.201zm-15.113.188h9.563v2.166H9.506v9.646H6.789v-9.646H3.375z"/></svg>
|
||||||
|
After Width: | Height: | Size: 1.3 KiB |
1
img/icons/ubuntu.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Ubuntu</title><path d="M17.61.455a3.41 3.41 0 0 0-3.41 3.41 3.41 3.41 0 0 0 3.41 3.41 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41zM12.92.8C8.923.777 5.137 2.941 3.148 6.451a4.5 4.5 0 0 1 .26-.007 4.92 4.92 0 0 1 2.585.737A8.316 8.316 0 0 1 12.688 3.6 4.944 4.944 0 0 1 13.723.834 11.008 11.008 0 0 0 12.92.8zm9.226 4.994a4.915 4.915 0 0 1-1.918 2.246 8.36 8.36 0 0 1-.273 8.303 4.89 4.89 0 0 1 1.632 2.54 11.156 11.156 0 0 0 .559-13.089zM3.41 7.932A3.41 3.41 0 0 0 0 11.342a3.41 3.41 0 0 0 3.41 3.409 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41zm2.027 7.866a4.908 4.908 0 0 1-2.915.358 11.1 11.1 0 0 0 7.991 6.698 11.234 11.234 0 0 0 2.422.249 4.879 4.879 0 0 1-.999-2.85 8.484 8.484 0 0 1-.836-.136 8.304 8.304 0 0 1-5.663-4.32zm11.405.928a3.41 3.41 0 0 0-3.41 3.41 3.41 3.41 0 0 0 3.41 3.41 3.41 3.41 0 0 0 3.41-3.41 3.41 3.41 0 0 0-3.41-3.41z"/></svg>
|
||||||
|
After Width: | Height: | Size: 963 B |
1
img/icons/wayland.svg
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<svg fill="#11171a" role="img" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg"><title>Wayland</title><path d="M23.982 12c0 9.224-9.985 14.989-17.973 10.377A11.982 11.982 0 0 1 .018 12c0-3.386 1.346-6.306 3.444-8.418v.675l.553 3.413v1.07l.077 1.365c-.23 0-.344.053-.344.159v.09l.344-.09h.076v2.366c.178 1.578.509 2.981.992 4.21.05.864.146 1.297.286 1.297.229.864.394 1.297.496 1.297a5.494 5.494 0 0 0 2.422 1.865c.344 0 .63-.432.859-1.296.292-1.866.528-3.004.706-3.414.165.167.451.281.858.342v-.091c0-.152-.26-.311-.782-.478.14-.728.33-1.35.572-1.866.038-.88.49-2.1 1.354-3.663l.344.09c.05.714.146 1.305.286 1.776.521 1.061.782 2.063.782 3.003 0 .364.407 1.585 1.22 3.663.204 1.366.465 2.17.783 2.412h.152c.42 0 .916-.538 1.488-1.615.56-1.214 1.011-1.889 1.354-2.025 0-.106-.12-.16-.362-.16v-.09c.19 0 .286-.054.286-.16v-.068l-.076-.091.801-.319c0-.273-.095-.41-.286-.41v-.09h.572v-.16c-.292 0-.483-.159-.572-.477h.133v-.319l-.076-.091c.432 0 .649-.083.649-.25v-.069h-.134v-.09c.153 0 .267-.16.343-.478-.42-.137-.629-.273-.629-.41l.553-1.615c0-.82.146-1.715.439-2.685.33-.94.566-1.965.706-3.072.304-1.064.617-1.787.938-2.17A11.979 11.979 0 0 1 23.982 12Zm-5.99-10.377c.587.34 1.138.724 1.65 1.148a33.45 33.45 0 0 0-.157 1.736c-.458 1.077-.96 2.594-1.507 4.55h-1.411l-.725.07v.181h.362l.286-.09 1.202.158v.092c-.343 1.183-.553 1.774-.63 1.774-.445 2.215-.8 3.322-1.068 3.322h-.229c-.318-.288-.674-.88-1.068-1.774a.861.861 0 0 0-.21-.57L13.42 9.855c-.216-1-.502-1.752-.858-2.252-.115-.334-.305-.5-.572-.5h-.134c-.42 0-.826.295-1.22.887l.076.25c-.178.607-.465 1.312-.859 2.116-.61 1.775-1.01 2.662-1.201 2.662h-.153l-.286-.227c-.483-1.305-.814-3.224-.992-5.757h-.134v-.16h.058a1.33 1.33 0 0 1 .515-.75v-.069l-.077-.09-.076.09-.076-.09-.344.09h-.152c-.09 0-.23-.348-.42-1.046v-.183h.496v-.16h-.648c-.139-.698-.545-1.53-1.218-2.494.296-.206.601-.398.915-.577h.016l.077.068.438-.068h.783c.228 0 .464.083.705.25.115-.152.33-.235.649-.25v-.16h-2.38c3.11-1.667 6.947-1.99 10.51-.392l-.443.142v.16c.24 0 .4-.095.482-.284.368.166.734.354 1.095.562Zm3.477 3.203h-.134l.035-.295.179.23-.08.065ZM9.719 1.595h.572l.133.159.363-.16.42.069.438-.068v-.16H9.72v.16Zm3.7 1.297v.159l.21.09v-.09l.076-.091c.14 0 .21-.053.21-.16v-.068h-.21l-.286.16Zm-1.144.887h-.058l-.076-.068-.42.227v.16c.61-.137.916-.266.916-.387V3.62l-.362.159Zm-2.842.978c0 .167-.051.25-.153.25v.16c.598-.197 1.03-.417 1.297-.66V4.44l-1.144.318Zm4.482.57v.068l.153.182h.076l.134-.182v-.069l-.077-.09-.286.09Zm2.785.159v.09h.076l.134-.181v-.069h-.076l-.134.16Zm-1.354 1.206.152.25c.23 0 .37-.084.42-.25l-.286-.228c-.19.076-.286.152-.286.228Zm1.85.978v.091h.153l.057-.091v-.068l-.057-.091-.153.159Zm-3.128.978v.091h.21l.362-.34v-.07c-.382.077-.572.183-.572.32ZM2.24 12.13v.341h.153c.14-.106.305-.41.496-.91v-.068h-.076l-.573.637Zm9.328 3.413v.159l.076.091.572-.341v-.16h-.076l-.572.25Zm-7.267.887.21.82h.152l-.229-.888h-.133v.068Zm7.63 1.07.133.227h.076l.076-.068-.228-.25h-.058v.09Z"/></svg>
|
||||||
|
After Width: | Height: | Size: 2.9 KiB |
|
Before Width: | Height: | Size: 234 KiB After Width: | Height: | Size: 171 KiB |
|
Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 32 KiB |
179
index.html
|
|
@ -5,10 +5,10 @@
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
<title>Fi3w0 / Alex - Systems Builder</title>
|
<title>Fi3w0 / Alex - Systems Builder</title>
|
||||||
<meta name="description" content="Alex, also known as Fi3w0, builds Linux and macOS desktop tools, homelab infrastructure, server tooling, and configurable Minecraft frameworks.">
|
<meta name="description" content="Alex, also known as Fi3w0, builds TideWM, Scorium, Linux and macOS desktop tools, homelab infrastructure, and configurable server tooling.">
|
||||||
|
|
||||||
<meta property="og:title" content="Fi3w0 / Alex - Systems Builder">
|
<meta property="og:title" content="Fi3w0 / Alex - Systems Builder">
|
||||||
<meta property="og:description" content="Linux, homelabs, server tooling, and the systems holding everything up.">
|
<meta property="og:description" content="TideWM, Scorium, Linux desktops, homelabs, and the systems holding everything up.">
|
||||||
<meta property="og:type" content="website">
|
<meta property="og:type" content="website">
|
||||||
<meta property="og:url" content="https://fiwlabs.dev/">
|
<meta property="og:url" content="https://fiwlabs.dev/">
|
||||||
<meta property="og:image" content="https://fiwlabs.dev/img/moonlit-shell.jpg">
|
<meta property="og:image" content="https://fiwlabs.dev/img/moonlit-shell.jpg">
|
||||||
|
|
@ -16,11 +16,8 @@
|
||||||
<meta name="twitter:card" content="summary_large_image">
|
<meta name="twitter:card" content="summary_large_image">
|
||||||
|
|
||||||
<link rel="canonical" href="https://fiwlabs.dev/">
|
<link rel="canonical" href="https://fiwlabs.dev/">
|
||||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
<link rel="stylesheet" href="css/style.css?v=10">
|
||||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
<link rel="stylesheet" href="css/editorial.css?v=11">
|
||||||
<link href="https://fonts.googleapis.com/css2?family=Archivo+Black&family=IBM+Plex+Mono:ital,wght@0,400;0,500;0,600;1,400&display=swap" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="css/style.css?v=5">
|
|
||||||
<link rel="stylesheet" href="css/editorial.css?v=5">
|
|
||||||
|
|
||||||
<link rel="icon" href="/favicon.ico" sizes="any">
|
<link rel="icon" href="/favicon.ico" sizes="any">
|
||||||
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
<link rel="icon" href="/icon.svg" type="image/svg+xml">
|
||||||
|
|
@ -68,7 +65,7 @@
|
||||||
<p class="eyebrow"><span class="status-dot" aria-hidden="true"></span> alex@fiw:~ $ cat about.txt</p>
|
<p class="eyebrow"><span class="status-dot" aria-hidden="true"></span> alex@fiw:~ $ cat about.txt</p>
|
||||||
<h1 id="hero-title">Alex <span id="glitch-name" data-text="/ Fi3w0">/ Fi3w0</span></h1>
|
<h1 id="hero-title">Alex <span id="glitch-name" data-text="/ Fi3w0">/ Fi3w0</span></h1>
|
||||||
<p class="hero-intro">
|
<p class="hero-intro">
|
||||||
I make Linux and macOS desktop tools, run a homelab, and publish Minecraft server mods.
|
I build a Wayland desktop, a programmable configuration language, macOS tools, and Minecraft server mods.
|
||||||
Most of my projects start as something I wanted for my own machine or server,
|
Most of my projects start as something I wanted for my own machine or server,
|
||||||
then grow until they are useful to somebody else too.
|
then grow until they are useful to somebody else too.
|
||||||
</p>
|
</p>
|
||||||
|
|
@ -92,15 +89,15 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="terminal-body">
|
<div class="terminal-body">
|
||||||
<p><span class="prompt">$</span> git status --short</p>
|
<p><span class="prompt">$</span> git status --short</p>
|
||||||
<p class="terminal-output"><span>M</span> homelab/<br><span>M</span> minecraft-mods/<br><span>??</span> desktop-experiments/</p>
|
<p class="terminal-output"><span>M</span> TideWM/<br><span>M</span> Scorium/<br><span>??</span> next-system/</p>
|
||||||
<p><span class="prompt">$</span> cat NOTES</p>
|
<p><span class="prompt">$</span> cat NOTES</p>
|
||||||
<p class="terminal-output">linux + macos / servers / modding<br>automation / config / self-hosting</p>
|
<p class="terminal-output">wayland + rust / config languages<br>automation / servers / self-hosting</p>
|
||||||
<p class="terminal-note"># there is always something unfinished</p>
|
<p class="terminal-note"># there is always something unfinished</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="profile-chip">
|
<div class="profile-chip">
|
||||||
<img src="img/avatar.png" alt="Fi3w0's GitHub avatar" width="460" height="460">
|
<img src="img/avatar.png" alt="Fi3w0's GitHub avatar" width="460" height="460">
|
||||||
<div><strong>Alex / Fi3w0</strong><span>Linux, servers, mods</span></div>
|
<div><strong>Alex / Fi3w0</strong><span>Systems over categories</span></div>
|
||||||
</div>
|
</div>
|
||||||
</aside>
|
</aside>
|
||||||
</section>
|
</section>
|
||||||
|
|
@ -108,40 +105,40 @@
|
||||||
<div class="signal-strip" aria-label="Core interests">
|
<div class="signal-strip" aria-label="Core interests">
|
||||||
<div class="signal-track">
|
<div class="signal-track">
|
||||||
<div class="signal-set">
|
<div class="signal-set">
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/archlinux/11171a" alt="" loading="lazy" onerror="this.remove()">Arch Linux</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/archlinux.svg" alt="" loading="lazy">Arch Linux</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ubuntu/11171a" alt="" loading="lazy" onerror="this.remove()">Ubuntu Server</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/ubuntu.svg" alt="" loading="lazy">Ubuntu Server</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/docker/11171a" alt="" loading="lazy" onerror="this.remove()">Docker</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/docker.svg" alt="" loading="lazy">Docker</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/modrinth/11171a" alt="" loading="lazy" onerror="this.remove()">Minecraft tooling</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/modrinth.svg" alt="" loading="lazy">Minecraft tooling</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/wayland/11171a" alt="" loading="lazy" onerror="this.remove()">Wayland</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/wayland.svg" alt="" loading="lazy">Wayland</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%2311171a%22%3E%3Cpath%20d%3D%22M12%202C7.03%202%203%203.34%203%205s4.03%203%209%203%209-1.34%209-3-4.03-3-9-3z%22%2F%3E%3Cpath%20d%3D%22M3%208.2v3.3c0%201.66%204.03%203%209%203s9-1.34%209-3V8.2c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3Cpath%20d%3D%22M3%2013.7V17c0%201.66%204.03%203%209%203s9-1.34%209-3v-3.3c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3C%2Fsvg%3E" alt="">Self-hosting</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/selfhosting.svg" alt="" loading="lazy">Self-hosting</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/json/11171a" alt="" loading="lazy" onerror="this.remove()">Things configured with JSON</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/json.svg" alt="" loading="lazy">Things configured with JSON</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/gnubash/11171a" alt="" loading="lazy" onerror="this.remove()">Bash</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/gnubash.svg" alt="" loading="lazy">Bash</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/rust/11171a" alt="" loading="lazy" onerror="this.remove()">Rust</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/rust.svg" alt="" loading="lazy">Rust</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/go/11171a" alt="" loading="lazy" onerror="this.remove()">Go</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/go.svg" alt="" loading="lazy">Go</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/lua/11171a" alt="" loading="lazy" onerror="this.remove()">Lua</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/lua.svg" alt="" loading="lazy">Lua</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/kotlin/11171a" alt="" loading="lazy" onerror="this.remove()">Kotlin</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/kotlin.svg" alt="" loading="lazy">Kotlin</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/typescript/11171a" alt="" loading="lazy" onerror="this.remove()">TypeScript</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/typescript.svg" alt="" loading="lazy">TypeScript</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/githubactions/11171a" alt="" loading="lazy" onerror="this.remove()">GitHub Actions</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/githubactions.svg" alt="" loading="lazy">GitHub Actions</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ffmpeg/11171a" alt="" loading="lazy" onerror="this.remove()">FFmpeg</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/ffmpeg.svg" alt="" loading="lazy">FFmpeg</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%229.6%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%2F%3E%3Cpath%20d%3D%22M12%2C2.4%20A9.6%2C9.6%200%200%2C1%2012%2C21.6%20Z%22%20fill%3D%22%2311171a%22%2F%3E%3Ccircle%20cx%3D%228%22%20cy%3D%229.6%22%20r%3D%221.15%22%20fill%3D%22%2311171a%22%2F%3E%3Cpath%20d%3D%22M6.4%2013.8%20Q8.3%2016.6%2010.6%2014.5%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E" alt="">macOS</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/macos.svg" alt="" loading="lazy">macOS</span><i></i>
|
||||||
</div>
|
</div>
|
||||||
<div class="signal-set" aria-hidden="true">
|
<div class="signal-set" aria-hidden="true">
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/archlinux/11171a" alt="" loading="lazy" onerror="this.remove()">Arch Linux</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/archlinux.svg" alt="" loading="lazy">Arch Linux</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ubuntu/11171a" alt="" loading="lazy" onerror="this.remove()">Ubuntu Server</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/ubuntu.svg" alt="" loading="lazy">Ubuntu Server</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/docker/11171a" alt="" loading="lazy" onerror="this.remove()">Docker</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/docker.svg" alt="" loading="lazy">Docker</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/modrinth/11171a" alt="" loading="lazy" onerror="this.remove()">Minecraft tooling</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/modrinth.svg" alt="" loading="lazy">Minecraft tooling</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/wayland/11171a" alt="" loading="lazy" onerror="this.remove()">Wayland</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/wayland.svg" alt="" loading="lazy">Wayland</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%20fill%3D%22%2311171a%22%3E%3Cpath%20d%3D%22M12%202C7.03%202%203%203.34%203%205s4.03%203%209%203%209-1.34%209-3-4.03-3-9-3z%22%2F%3E%3Cpath%20d%3D%22M3%208.2v3.3c0%201.66%204.03%203%209%203s9-1.34%209-3V8.2c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3Cpath%20d%3D%22M3%2013.7V17c0%201.66%204.03%203%209%203s9-1.34%209-3v-3.3c-1.6%201.28-5.1%202.3-9%202.3s-7.4-1.02-9-2.3z%22%2F%3E%3C%2Fsvg%3E" alt="">Self-hosting</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/selfhosting.svg" alt="" loading="lazy">Self-hosting</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/json/11171a" alt="" loading="lazy" onerror="this.remove()">Things configured with JSON</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/json.svg" alt="" loading="lazy">Things configured with JSON</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/gnubash/11171a" alt="" loading="lazy" onerror="this.remove()">Bash</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/gnubash.svg" alt="" loading="lazy">Bash</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/rust/11171a" alt="" loading="lazy" onerror="this.remove()">Rust</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/rust.svg" alt="" loading="lazy">Rust</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/go/11171a" alt="" loading="lazy" onerror="this.remove()">Go</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/go.svg" alt="" loading="lazy">Go</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/lua/11171a" alt="" loading="lazy" onerror="this.remove()">Lua</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/lua.svg" alt="" loading="lazy">Lua</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/kotlin/11171a" alt="" loading="lazy" onerror="this.remove()">Kotlin</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/kotlin.svg" alt="" loading="lazy">Kotlin</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/typescript/11171a" alt="" loading="lazy" onerror="this.remove()">TypeScript</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/typescript.svg" alt="" loading="lazy">TypeScript</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/githubactions/11171a" alt="" loading="lazy" onerror="this.remove()">GitHub Actions</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/githubactions.svg" alt="" loading="lazy">GitHub Actions</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="https://cdn.simpleicons.org/ffmpeg/11171a" alt="" loading="lazy" onerror="this.remove()">FFmpeg</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/ffmpeg.svg" alt="" loading="lazy">FFmpeg</span><i></i>
|
||||||
<span class="signal-item"><img class="signal-icon" src="data:image/svg+xml,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20viewBox%3D%220%200%2024%2024%22%3E%3Ccircle%20cx%3D%2212%22%20cy%3D%2212%22%20r%3D%229.6%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%2F%3E%3Cpath%20d%3D%22M12%2C2.4%20A9.6%2C9.6%200%200%2C1%2012%2C21.6%20Z%22%20fill%3D%22%2311171a%22%2F%3E%3Ccircle%20cx%3D%228%22%20cy%3D%229.6%22%20r%3D%221.15%22%20fill%3D%22%2311171a%22%2F%3E%3Cpath%20d%3D%22M6.4%2013.8%20Q8.3%2016.6%2010.6%2014.5%22%20fill%3D%22none%22%20stroke%3D%22%2311171a%22%20stroke-width%3D%221.4%22%20stroke-linecap%3D%22round%22%2F%3E%3C%2Fsvg%3E" alt="">macOS</span><i></i>
|
<span class="signal-item"><img class="signal-icon" src="img/icons/macos.svg" alt="" loading="lazy">macOS</span><i></i>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
@ -173,8 +170,8 @@
|
||||||
hot reload, readable config files, updates that can fail safely, and logs that say what went wrong.
|
hot reload, readable config files, updates that can fail safely, and logs that say what went wrong.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
<p>
|
||||||
I write Bash, Go, Lua, and modify Java directly. For Rust, Swift, and larger Kotlin, TypeScript, Python,
|
I write Bash, Go, Lua, and Python directly, and modify Java. For heavier Rust, Swift, Kotlin,
|
||||||
and QML builds, I use AI agents heavily, then review, test, and integrate the result myself.
|
TypeScript, and QML builds, I direct AI agents, then own the design, review, testing, and integration.
|
||||||
</p>
|
</p>
|
||||||
<div class="language-line">
|
<div class="language-line">
|
||||||
<span>Ukrainian + Russian</span><span>Spanish</span><span>English C1</span>
|
<span>Ukrainian + Russian</span><span>Spanish</span><span>English C1</span>
|
||||||
|
|
@ -201,50 +198,74 @@
|
||||||
<p>Public repositories, installable mods, and rougher ideas that started on my own machines.</p>
|
<p>Public repositories, installable mods, and rougher ideas that started on my own machines.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<article class="current-project reveal" aria-labelledby="tidewm-title">
|
<article class="current-project current-project-tide reveal" aria-labelledby="tidewm-title">
|
||||||
<div class="current-project-main">
|
<div class="current-project-main">
|
||||||
<div class="current-meta"><span><i aria-hidden="true"></i> Daily-driver compositor</span><span>AI-assisted / evolving</span></div>
|
<div class="current-meta"><span><i aria-hidden="true"></i> Functional pre-release</span><span>v0.90 / daily-driver path</span></div>
|
||||||
<p class="current-kicker">Wayland compositor / Rust</p>
|
<p class="current-kicker">Wayland compositor / Rust</p>
|
||||||
<div class="tide-heading">
|
<div class="tide-heading">
|
||||||
<img class="tide-mark" src="img/tidewm-logo.png" alt="" width="48" height="48" loading="lazy">
|
<img class="tide-mark" src="img/tidewm-logo.png" alt="" width="48" height="48" loading="lazy">
|
||||||
<h3 id="tidewm-title">TideWM</h3>
|
<h3 id="tidewm-title">TideWM</h3>
|
||||||
</div>
|
</div>
|
||||||
<p>A water-themed Wayland compositor built on Smithay, made because I wanted to see how far I could take it. Tiling (BSP and master/stack), multi-monitor, XWayland, workspaces, and IPC already run as my daily driver; the signature aqua render effects are next.</p>
|
<p>A Wayland desktop that feels like water, built in Rust on Smithay. Under the ripples, wave transitions, glass, shadows, and window sway is a real tiling compositor with multi-monitor, XWayland, IPC, screencasting, and two spatial engines you can switch live.</p>
|
||||||
<div class="tag-row current-tags"><span>AI-assisted</span><span>Smithay</span><span>Rust</span><span>Wayland</span></div>
|
<div class="tide-proof" aria-label="TideWM project facts">
|
||||||
|
<div><strong>2</strong><span>spatial engines</span></div>
|
||||||
|
<div><strong>~63 MB</strong><span>nine glass windows</span></div>
|
||||||
|
<div><strong>live</strong><span>config reload</span></div>
|
||||||
|
</div>
|
||||||
|
<div class="tag-row current-tags"><span>Smithay</span><span>Rust</span><span>Wayland</span><span>GPL-3.0</span></div>
|
||||||
<a class="text-link current-repo-link" href="https://github.com/Fi3w0/TideWM" target="_blank" rel="noreferrer">View repository <span aria-hidden="true">↗</span></a>
|
<a class="text-link current-repo-link" href="https://github.com/Fi3w0/TideWM" target="_blank" rel="noreferrer">View repository <span aria-hidden="true">↗</span></a>
|
||||||
</div>
|
</div>
|
||||||
<div class="current-roadmap">
|
<div class="current-roadmap">
|
||||||
<p>Project shape</p>
|
<p>Core systems</p>
|
||||||
<ol>
|
<ol>
|
||||||
<li><span>01</span><div><strong>Foundation</strong><small>Rust compositor built directly on Smithay</small></div><b>base</b></li>
|
<li><span>01</span><div><strong>Classic</strong><small>BSP, master/stack, cascade, workspaces, Depth Deck</small></div><b>done</b></li>
|
||||||
<li><span>02</span><div><strong>Tiling & compat</strong><small>BSP/master-stack layouts, multi-monitor, XWayland</small></div><b>done</b></li>
|
<li><span>02</span><div><strong>Ocean</strong><small>One continuous 2D world with reefs, cameras, and bookmarks</small></div><b>done</b></li>
|
||||||
<li><span>03</span><div><strong>Visual direction</strong><small>Water/aqua render effects, still pending</small></div><b>next</b></li>
|
<li><span>03</span><div><strong>Water stack</strong><small>Ripples, waves, glass, caustics, sway, depth, and buoyancy</small></div><b>done</b></li>
|
||||||
<li><span>04</span><div><strong>Development method</strong><small>AI-assisted implementation, tested and reviewed by me</small></div><b>method</b></li>
|
<li><span>04</span><div><strong>Next pass</strong><small>More hardware coverage, feel tuning, Nvidia native, AUR</small></div><b>testing</b></li>
|
||||||
</ol>
|
</ol>
|
||||||
</div>
|
</div>
|
||||||
<span class="current-watermark" aria-hidden="true">TIDE</span>
|
<span class="current-watermark" aria-hidden="true">TIDE</span>
|
||||||
<div class="tide-ascii" id="tide-ascii" aria-hidden="true"></div>
|
</article>
|
||||||
|
|
||||||
|
<article class="project-feature project-feature-scorium reveal" data-repo="Scorium">
|
||||||
|
<a class="project-visual project-visual-scorium" href="https://github.com/Fi3w0/Scorium" target="_blank" rel="noreferrer" aria-label="View Scorium on GitHub">
|
||||||
|
<span class="scorium-window" aria-hidden="true">
|
||||||
|
<span class="scorium-titlebar"><b>SCORIUM</b><span>examples/services.scor</span><i>v0.1.0</i></span>
|
||||||
|
<code class="scorium-code"><span class="scorium-line"><span class="scorium-dim">01</span> <em>@base_port</em> = <strong>8000</strong></span><span class="scorium-line"><span class="scorium-dim">02</span></span><span class="scorium-line"><span class="scorium-dim">03</span> <b>server</b> {</span><span class="scorium-line"><span class="scorium-dim">04</span><span class="scorium-indent"></span>host = <i>localhost</i></span><span class="scorium-line"><span class="scorium-dim">05</span><span class="scorium-indent"></span>port = <em>base_port</em> + <strong>80</strong></span><span class="scorium-line"><span class="scorium-dim">06</span><span class="scorium-indent"></span>timeout = <strong>5s</strong></span><span class="scorium-line"><span class="scorium-dim">07</span> }</span><span class="scorium-line"><span class="scorium-dim">08</span></span><span class="scorium-line"><span class="scorium-dim">09</span> <b>for</b> i = <strong>1</strong>, <strong>3</strong> <b>do</b></span><span class="scorium-line"><span class="scorium-dim">10</span><span class="scorium-indent"></span>worker {</span><span class="scorium-line"><span class="scorium-dim">11</span><span class="scorium-indent scorium-indent-double"></span>name = worker-$i</span><span class="scorium-line"><span class="scorium-dim">12</span><span class="scorium-indent"></span>}</span><span class="scorium-line"><span class="scorium-dim">13</span> <b>end</b></span></code>
|
||||||
|
<span class="scorium-diagnostic"><i></i> parsed · sandboxed · schema valid</span>
|
||||||
|
</span>
|
||||||
|
<span class="scorium-pipeline" aria-hidden="true"><b>source</b><i>→</i><b>parse</b><i>→</i><b>evaluate</b><i>→</i><b>validate</b><i>→</i><b>apply</b></span>
|
||||||
|
<span class="scorium-watermark" aria-hidden="true">.SCOR</span>
|
||||||
|
<span class="image-label">Readable config / programmable core</span>
|
||||||
|
</a>
|
||||||
|
<div class="project-feature-copy">
|
||||||
|
<div class="project-meta"><span>Newest release</span><span>Rust / Lua 5.4 / pre-1.0</span></div>
|
||||||
|
<h3>Scorium</h3>
|
||||||
|
<p>A readable, embeddable configuration framework: declarative for simple files, programmable when repetition or conditions appear. It combines typed values, precise diagnostics, built-in schema validation, a canonical formatter, and sandboxed Lua—with no filesystem, network, or process access by default.</p>
|
||||||
|
<ul class="project-facts">
|
||||||
|
<li><strong>5</strong><span>focused Rust crates</span></li>
|
||||||
|
<li><strong>5</strong><span>CLI workflows</span></li>
|
||||||
|
<li><strong>0</strong><span>ambient system access</span></li>
|
||||||
|
</ul>
|
||||||
|
<div class="tag-row scorium-tags"><span>Source-available</span><span>PolyForm Strict</span><span>Sandboxed</span></div>
|
||||||
|
<a class="text-link" href="https://github.com/Fi3w0/Scorium" target="_blank" rel="noreferrer">Read the repository <span aria-hidden="true">↗</span></a>
|
||||||
|
</div>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="project-feature reveal" data-repo="Flux">
|
<article class="project-feature reveal" data-repo="Flux">
|
||||||
<a class="project-visual project-visual-flux" href="https://github.com/Fi3w0/Flux" target="_blank" rel="noreferrer" aria-label="View Flux on GitHub">
|
<a class="project-visual project-visual-flux" href="https://github.com/Fi3w0/Flux" target="_blank" rel="noreferrer" aria-label="View Flux on GitHub">
|
||||||
<span class="flux-chrome" aria-hidden="true"><i></i><i></i><i></i></span>
|
<span class="flux-product-frame">
|
||||||
<img class="flux-icon" src="img/flux-icon.png" alt="" width="256" height="256" loading="lazy">
|
<span class="flux-product-bar" aria-hidden="true"><span><i></i><i></i><i></i></span><b>Flux / Workbench</b><em>LOCAL</em></span>
|
||||||
<span class="flux-tags" aria-hidden="true"><span>SwiftUI</span><span>AppKit</span><span>Local-first</span></span>
|
<img class="flux-product-shot" src="img/flux-overview.jpg" alt="Flux media workbench showing a download ready to add to the processing queue" width="1600" height="922" loading="lazy">
|
||||||
<span class="flux-stack" aria-hidden="true">
|
|
||||||
<b>Tool stack</b>
|
|
||||||
<span class="flux-stack-row"><span>01</span><span>FFmpeg<small>transcode + inspect</small></span></span>
|
|
||||||
<span class="flux-stack-row"><span>02</span><span>yt-dlp<small>downloads</small></span></span>
|
|
||||||
<span class="flux-stack-row"><span>03</span><span>Apple Vision<small>subject isolation</small></span></span>
|
|
||||||
<span class="flux-stack-row"><span>04</span><span>Sparkle<small>auto updates</small></span></span>
|
|
||||||
</span>
|
</span>
|
||||||
<span class="flux-status" aria-hidden="true"><i></i> pre-flight checks passed</span>
|
<span class="flux-product-caption" aria-hidden="true">
|
||||||
<span class="flux-watermark" aria-hidden="true">FLUX</span>
|
<img src="img/flux-icon.png" alt="" width="256" height="256" loading="lazy">
|
||||||
<div class="flux-ascii" id="flux-ascii" aria-hidden="true"></div>
|
<span><strong>Native by design</strong><small>SwiftUI · AppKit · local processing</small></span>
|
||||||
<span class="image-label">macOS Tahoe / Apple silicon</span>
|
</span>
|
||||||
|
<span class="image-label">Current interface / macOS</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="project-feature-copy">
|
<div class="project-feature-copy">
|
||||||
<div class="project-meta"><span>Latest project</span><span>SwiftUI / AppKit / macOS</span></div>
|
<div class="project-meta"><span>Native media workbench</span><span>SwiftUI / AppKit / macOS</span></div>
|
||||||
<h3>Flux</h3>
|
<h3>Flux</h3>
|
||||||
<p>A native macOS media workbench: convert, download, edit, and compose media in one focused app. FFmpeg, yt-dlp, and Apple Vision underneath, pre-flight checks that explain failures before a job ever starts, and everything processed locally.</p>
|
<p>A native macOS media workbench: convert, download, edit, and compose media in one focused app. FFmpeg, yt-dlp, and Apple Vision underneath, pre-flight checks that explain failures before a job ever starts, and everything processed locally.</p>
|
||||||
<ul class="project-facts">
|
<ul class="project-facts">
|
||||||
|
|
@ -257,14 +278,19 @@
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
<article class="project-feature reveal" data-repo="Moonlit-shell">
|
<article class="project-feature reveal" data-repo="Moonlit-shell">
|
||||||
<a class="project-visual" href="https://github.com/Fi3w0/Moonlit-shell" target="_blank" rel="noreferrer" aria-label="View Moonlit Shell on GitHub">
|
<a class="project-visual project-visual-moonlit" href="https://github.com/Fi3w0/Moonlit-shell" target="_blank" rel="noreferrer" aria-label="View Moonlit Shell on GitHub">
|
||||||
<img src="img/moonlit-shell.jpg" alt="Moonlit Shell desktop with its top bar over a nocturnal landscape" width="1600" height="900">
|
<span class="moonlit-screen">
|
||||||
<span class="image-label">Daily driver / ThinkPad T14</span>
|
<span class="moonlit-camera" aria-hidden="true"></span>
|
||||||
|
<img src="img/moonlit-shell.jpg" alt="Moonlit Shell running on Arch Linux with its custom top panels over a nocturnal landscape" width="1600" height="900" loading="lazy">
|
||||||
|
<span class="moonlit-session" aria-hidden="true"><b>Moonlit Shell</b><span>ThinkPad T14 · live session</span><i>ACTIVE</i></span>
|
||||||
|
</span>
|
||||||
|
<span class="moonlit-specs" aria-hidden="true"><span><b>12</b> native panels</span><span><b>/proc</b> direct telemetry</span><span><b>safe</b> dotfile updates</span></span>
|
||||||
|
<span class="image-label">Arch · Hyprland · Quickshell</span>
|
||||||
</a>
|
</a>
|
||||||
<div class="project-feature-copy">
|
<div class="project-feature-copy">
|
||||||
<div class="project-meta"><span>Featured system</span><span>QML / Go / Linux</span></div>
|
<div class="project-meta"><span>Daily system</span><span>QML / Go / Linux</span></div>
|
||||||
<h3>Moonlit Shell</h3>
|
<h3>Moonlit Shell</h3>
|
||||||
<p>This is the desktop I actually use: Arch, Hyprland, and Quickshell with twelve panels, a live settings app, safe dotfile updates, and no monitoring daemon between the UI and <code>/proc</code>.</p>
|
<p>My daily Arch desktop, built around Hyprland and Quickshell. Twelve native panels, one live settings surface, recoverable dotfile updates, and direct system telemetry without another monitoring daemon in the middle.</p>
|
||||||
<ul class="project-facts">
|
<ul class="project-facts">
|
||||||
<li><strong>12</strong><span>native panels</span></li>
|
<li><strong>12</strong><span>native panels</span></li>
|
||||||
<li><strong>1</strong><span>live config</span></li>
|
<li><strong>1</strong><span>live config</span></li>
|
||||||
|
|
@ -370,7 +396,7 @@
|
||||||
<header class="section-heading reveal">
|
<header class="section-heading reveal">
|
||||||
<p class="section-index">04 / Tools</p>
|
<p class="section-index">04 / Tools</p>
|
||||||
<h2 id="stack-title">What is usually open in my terminal.</h2>
|
<h2 id="stack-title">What is usually open in my terminal.</h2>
|
||||||
<p class="stack-intro">Kitty as the daily emulator, running on a MacBook (macOS) — my main device — SSH'd out to the Arch desktop and the Ubuntu server for everything else.</p>
|
<p class="stack-intro">Kitty on a MacBook Air M4 is the main console, SSH'd out to the Arch desktop, ThinkPad T14, and Ubuntu server for everything else.</p>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div class="stack-layout">
|
<div class="stack-layout">
|
||||||
|
|
@ -403,6 +429,7 @@
|
||||||
<h2 id="contact-title" class="reveal">Say hello.</h2>
|
<h2 id="contact-title" class="reveal">Say hello.</h2>
|
||||||
<p class="contact-copy reveal">Linux, infrastructure, self-hosting, Minecraft tooling, or a strange project you cannot stop thinking about are all good reasons to message me.</p>
|
<p class="contact-copy reveal">Linux, infrastructure, self-hosting, Minecraft tooling, or a strange project you cannot stop thinking about are all good reasons to message me.</p>
|
||||||
<div class="contact-links reveal">
|
<div class="contact-links reveal">
|
||||||
|
<a href="mailto:alex@fiwlabs.dev"><span>Email</span><small>alex@fiwlabs.dev</small><b>→</b></a>
|
||||||
<a href="https://github.com/Fi3w0" target="_blank" rel="noreferrer"><span>GitHub</span><small>@Fi3w0</small><b>↗</b></a>
|
<a href="https://github.com/Fi3w0" target="_blank" rel="noreferrer"><span>GitHub</span><small>@Fi3w0</small><b>↗</b></a>
|
||||||
<a href="https://t.me/fi3w0" target="_blank" rel="noreferrer"><span>Telegram</span><small>@fi3w0</small><b>↗</b></a>
|
<a href="https://t.me/fi3w0" target="_blank" rel="noreferrer"><span>Telegram</span><small>@fi3w0</small><b>↗</b></a>
|
||||||
<button type="button" id="copy-discord"><span>Discord</span><small id="discord-label">fi3w0 - click to copy</small><b aria-hidden="true">+</b></button>
|
<button type="button" id="copy-discord"><span>Discord</span><small id="discord-label">fi3w0 - click to copy</small><b aria-hidden="true">+</b></button>
|
||||||
|
|
@ -418,7 +445,7 @@
|
||||||
<p>© <span id="year">2026</span> Alex / Fi3w0</p>
|
<p>© <span id="year">2026</span> Alex / Fi3w0</p>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
<script src="js/app.js?v=5" defer></script>
|
<script src="js/app.js?v=9" defer></script>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
379
js/app.js
|
|
@ -241,383 +241,6 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// --- ASCIIText, ported from React Bits (vanilla JS, three.js loaded on
|
|
||||||
// demand). Replaces the flat "TIDE" watermark on the TideWM card with a
|
|
||||||
// rippling, wave-distorted ASCII render once the card scrolls into view.
|
|
||||||
// The plain-text watermark stays in the DOM as the fallback if WebGL or the
|
|
||||||
// three.js CDN load ever fails. ---
|
|
||||||
(() => {
|
|
||||||
const targets = [
|
|
||||||
{
|
|
||||||
host: document.querySelector("#tide-ascii"),
|
|
||||||
ancestorSelector: ".current-project",
|
|
||||||
readyClass: "has-tide-ascii",
|
|
||||||
text: "TIDE",
|
|
||||||
enableWaves: true,
|
|
||||||
asciiFontSize: 11,
|
|
||||||
textFontSize: 220,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
// Flux is a media-editing tool, not a water compositor — keep its
|
|
||||||
// watermark shape still and crisp (no mesh ripple), but let the
|
|
||||||
// glyphs themselves flicker/decode for a "processing feed" feel.
|
|
||||||
host: document.querySelector("#flux-ascii"),
|
|
||||||
ancestorSelector: ".project-visual-flux",
|
|
||||||
readyClass: "has-flux-ascii",
|
|
||||||
text: "FLUX",
|
|
||||||
enableWaves: false,
|
|
||||||
charJitter: true,
|
|
||||||
asciiFontSize: 8,
|
|
||||||
textFontSize: 240,
|
|
||||||
},
|
|
||||||
]
|
|
||||||
.map((t) => ({ ...t, ancestor: t.host?.closest(t.ancestorSelector) }))
|
|
||||||
.filter((t) => t.host && t.ancestor);
|
|
||||||
|
|
||||||
if (!targets.length || reducedMotion) return;
|
|
||||||
|
|
||||||
let hasWebGL = false;
|
|
||||||
try {
|
|
||||||
const testCanvas = document.createElement("canvas");
|
|
||||||
hasWebGL = Boolean(testCanvas.getContext("webgl") || testCanvas.getContext("experimental-webgl"));
|
|
||||||
} catch {
|
|
||||||
hasWebGL = false;
|
|
||||||
}
|
|
||||||
if (!hasWebGL) return;
|
|
||||||
|
|
||||||
const THREE_CDN_URL = "https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js";
|
|
||||||
let threeLoadPromise = null;
|
|
||||||
|
|
||||||
const loadThree = () => {
|
|
||||||
if (window.THREE) return Promise.resolve();
|
|
||||||
if (!threeLoadPromise) {
|
|
||||||
threeLoadPromise = new Promise((resolve, reject) => {
|
|
||||||
const script = document.createElement("script");
|
|
||||||
script.src = THREE_CDN_URL;
|
|
||||||
script.onload = () => resolve();
|
|
||||||
script.onerror = () => reject(new Error("three.js failed to load"));
|
|
||||||
document.head.append(script);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return threeLoadPromise;
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapRange = (n, start, stop, start2, stop2) => ((n - start) / (stop - start)) * (stop2 - start2) + start2;
|
|
||||||
|
|
||||||
const vertexShader = `
|
|
||||||
varying vec2 vUv;
|
|
||||||
uniform float uTime;
|
|
||||||
uniform float uEnableWaves;
|
|
||||||
void main() {
|
|
||||||
vUv = uv;
|
|
||||||
float time = uTime * 5.0;
|
|
||||||
float waveFactor = uEnableWaves;
|
|
||||||
vec3 transformed = position;
|
|
||||||
transformed.x += sin(time + position.y) * 0.5 * waveFactor;
|
|
||||||
transformed.y += cos(time + position.z) * 0.15 * waveFactor;
|
|
||||||
transformed.z += sin(time + position.x) * waveFactor;
|
|
||||||
gl_Position = projectionMatrix * modelViewMatrix * vec4(transformed, 1.0);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const fragmentShader = `
|
|
||||||
varying vec2 vUv;
|
|
||||||
uniform float uTime;
|
|
||||||
uniform float uEnableWaves;
|
|
||||||
uniform sampler2D uTexture;
|
|
||||||
void main() {
|
|
||||||
float time = uTime;
|
|
||||||
vec2 pos = vUv;
|
|
||||||
float amount = mix(0.006, 0.01, uEnableWaves);
|
|
||||||
float r = texture2D(uTexture, pos + cos(time * 2.0 - time + pos.x) * amount).r;
|
|
||||||
float g = texture2D(uTexture, pos + tan(time * 0.5 + pos.x - time) * amount).g;
|
|
||||||
float b = texture2D(uTexture, pos - cos(time * 2.0 + time + pos.y) * amount).b;
|
|
||||||
float a = texture2D(uTexture, pos).a;
|
|
||||||
gl_FragColor = vec4(r, g, b, a);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
class AsciiFilter {
|
|
||||||
constructor(renderer, { fontSize, fontFamily, charset, charJitter } = {}) {
|
|
||||||
this.renderer = renderer;
|
|
||||||
this.domElement = document.createElement("div");
|
|
||||||
this.domElement.className = "tide-ascii-inner";
|
|
||||||
this.pre = document.createElement("pre");
|
|
||||||
this.pre.className = "tide-ascii-pre";
|
|
||||||
this.domElement.append(this.pre);
|
|
||||||
this.canvas = document.createElement("canvas");
|
|
||||||
this.canvas.className = "tide-ascii-canvas";
|
|
||||||
this.context = this.canvas.getContext("2d");
|
|
||||||
this.domElement.append(this.canvas);
|
|
||||||
this.fontSize = fontSize ?? 8;
|
|
||||||
this.fontFamily = fontFamily ?? "'IBM Plex Mono', monospace";
|
|
||||||
this.charset = charset ?? " .'`^\",:;Il!i~+_-?][}{1)(|/tfjrxnuvczXYUJCLQ0OZmwqpdbkhao*#MW&8%B@$";
|
|
||||||
// Optional per-cell character flicker — a subtle "decoding" glitch
|
|
||||||
// where a handful of glyphs jump to a denser/lighter char and settle
|
|
||||||
// back, independent of any mesh/wave motion.
|
|
||||||
this.charJitter = charJitter ?? false;
|
|
||||||
this.context.imageSmoothingEnabled = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
setSize(width, height) {
|
|
||||||
if (!width || !height) return;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.renderer.setSize(width, height);
|
|
||||||
this.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
reset() {
|
|
||||||
this.context.font = `${this.fontSize}px ${this.fontFamily}`;
|
|
||||||
const charWidth = this.context.measureText("A").width;
|
|
||||||
this.cols = Math.max(1, Math.floor(this.width / (this.fontSize * (charWidth / this.fontSize))));
|
|
||||||
this.rows = Math.max(1, Math.floor(this.height / this.fontSize));
|
|
||||||
this.canvas.width = this.cols;
|
|
||||||
this.canvas.height = this.rows;
|
|
||||||
this.pre.style.fontFamily = this.fontFamily;
|
|
||||||
this.pre.style.fontSize = `${this.fontSize}px`;
|
|
||||||
}
|
|
||||||
|
|
||||||
render(scene, camera, time) {
|
|
||||||
this.renderer.render(scene, camera);
|
|
||||||
const w = this.canvas.width;
|
|
||||||
const h = this.canvas.height;
|
|
||||||
this.context.clearRect(0, 0, w, h);
|
|
||||||
if (this.context && w && h) this.context.drawImage(this.renderer.domElement, 0, 0, w, h);
|
|
||||||
this.asciify(this.context, w, h, time ?? 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
asciify(ctx, w, h, time = 0) {
|
|
||||||
if (!w || !h) return;
|
|
||||||
const imgData = ctx.getImageData(0, 0, w, h).data;
|
|
||||||
const jitter = this.charJitter;
|
|
||||||
const frameBucket = Math.floor(time * 7);
|
|
||||||
const last = this.charset.length - 1;
|
|
||||||
let str = "";
|
|
||||||
for (let y = 0; y < h; y += 1) {
|
|
||||||
for (let x = 0; x < w; x += 1) {
|
|
||||||
const i = x * 4 + y * 4 * w;
|
|
||||||
const [r, g, b, a] = [imgData[i], imgData[i + 1], imgData[i + 2], imgData[i + 3]];
|
|
||||||
if (a === 0) {
|
|
||||||
str += " ";
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
const gray = (0.3 * r + 0.6 * g + 0.1 * b) / 255;
|
|
||||||
let idx = this.charset.length - Math.floor((1 - gray) * last) - 1;
|
|
||||||
if (jitter) {
|
|
||||||
const seed = Math.sin(x * 12.9898 + y * 78.233 + frameBucket * 37.719) * 43758.5453;
|
|
||||||
const rnd = seed - Math.floor(seed);
|
|
||||||
if (rnd > 0.85) {
|
|
||||||
const swing = Math.floor(((rnd - 0.85) / 0.15) * 8) - 4;
|
|
||||||
idx = Math.min(last, Math.max(0, idx + swing));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
str += this.charset[idx];
|
|
||||||
}
|
|
||||||
str += "\n";
|
|
||||||
}
|
|
||||||
this.pre.textContent = str;
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CanvasTxt {
|
|
||||||
constructor(txt, { fontSize = 200, fontFamily = "Arial", color = "#fdf9f3" } = {}) {
|
|
||||||
this.canvas = document.createElement("canvas");
|
|
||||||
this.context = this.canvas.getContext("2d");
|
|
||||||
this.txt = txt;
|
|
||||||
this.font = `600 ${fontSize}px ${fontFamily}`;
|
|
||||||
this.color = color;
|
|
||||||
}
|
|
||||||
|
|
||||||
resize() {
|
|
||||||
this.context.font = this.font;
|
|
||||||
const metrics = this.context.measureText(this.txt);
|
|
||||||
this.canvas.width = Math.ceil(metrics.width) + 20;
|
|
||||||
this.canvas.height = Math.ceil((metrics.actualBoundingBoxAscent || 0) + (metrics.actualBoundingBoxDescent || 0)) + 20;
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
this.context.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
|
||||||
this.context.fillStyle = this.color;
|
|
||||||
this.context.font = this.font;
|
|
||||||
const metrics = this.context.measureText(this.txt);
|
|
||||||
this.context.fillText(this.txt, 10, 10 + metrics.actualBoundingBoxAscent);
|
|
||||||
}
|
|
||||||
|
|
||||||
get width() {
|
|
||||||
return this.canvas.width;
|
|
||||||
}
|
|
||||||
|
|
||||||
get height() {
|
|
||||||
return this.canvas.height;
|
|
||||||
}
|
|
||||||
|
|
||||||
get texture() {
|
|
||||||
return this.canvas;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class CanvAscii {
|
|
||||||
constructor({ text, asciiFontSize, textFontSize, textColor, planeBaseHeight, enableWaves, charJitter }, container, width, height) {
|
|
||||||
this.textString = text;
|
|
||||||
this.asciiFontSize = asciiFontSize;
|
|
||||||
this.textFontSize = textFontSize;
|
|
||||||
this.textColor = textColor;
|
|
||||||
this.planeBaseHeight = planeBaseHeight;
|
|
||||||
this.container = container;
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.enableWaves = enableWaves;
|
|
||||||
this.charJitter = charJitter;
|
|
||||||
this.camera = new window.THREE.PerspectiveCamera(45, this.width / this.height, 1, 1000);
|
|
||||||
this.camera.position.z = 30;
|
|
||||||
this.scene = new window.THREE.Scene();
|
|
||||||
}
|
|
||||||
|
|
||||||
async init() {
|
|
||||||
try {
|
|
||||||
await document.fonts.load(`600 200px "IBM Plex Mono"`);
|
|
||||||
} catch {
|
|
||||||
// fall back to default font metrics if the webfont isn't ready yet
|
|
||||||
}
|
|
||||||
this.setMesh();
|
|
||||||
this.setRenderer();
|
|
||||||
}
|
|
||||||
|
|
||||||
setMesh() {
|
|
||||||
const THREE = window.THREE;
|
|
||||||
this.textCanvas = new CanvasTxt(this.textString, {
|
|
||||||
fontSize: this.textFontSize,
|
|
||||||
fontFamily: "IBM Plex Mono",
|
|
||||||
color: this.textColor,
|
|
||||||
});
|
|
||||||
this.textCanvas.resize();
|
|
||||||
this.textCanvas.render();
|
|
||||||
this.texture = new THREE.CanvasTexture(this.textCanvas.texture);
|
|
||||||
this.texture.minFilter = THREE.NearestFilter;
|
|
||||||
const textAspect = this.textCanvas.width / this.textCanvas.height;
|
|
||||||
const planeW = this.planeBaseHeight * textAspect;
|
|
||||||
this.geometry = new THREE.PlaneGeometry(planeW, this.planeBaseHeight, 36, 36);
|
|
||||||
this.material = new THREE.ShaderMaterial({
|
|
||||||
vertexShader,
|
|
||||||
fragmentShader,
|
|
||||||
transparent: true,
|
|
||||||
uniforms: {
|
|
||||||
uTime: { value: 0 },
|
|
||||||
uTexture: { value: this.texture },
|
|
||||||
uEnableWaves: { value: this.enableWaves ? 1 : 0 },
|
|
||||||
},
|
|
||||||
});
|
|
||||||
this.mesh = new THREE.Mesh(this.geometry, this.material);
|
|
||||||
this.scene.add(this.mesh);
|
|
||||||
}
|
|
||||||
|
|
||||||
setRenderer() {
|
|
||||||
const THREE = window.THREE;
|
|
||||||
this.renderer = new THREE.WebGLRenderer({ antialias: false, alpha: true });
|
|
||||||
this.renderer.setPixelRatio(1);
|
|
||||||
this.renderer.setClearColor(0x000000, 0);
|
|
||||||
this.filter = new AsciiFilter(this.renderer, { fontSize: this.asciiFontSize, charJitter: this.charJitter });
|
|
||||||
this.container.append(this.filter.domElement);
|
|
||||||
this.setSize(this.width, this.height);
|
|
||||||
}
|
|
||||||
|
|
||||||
setSize(w, h) {
|
|
||||||
if (!w || !h) return;
|
|
||||||
this.width = w;
|
|
||||||
this.height = h;
|
|
||||||
this.camera.aspect = w / h;
|
|
||||||
this.camera.updateProjectionMatrix();
|
|
||||||
this.filter.setSize(w, h);
|
|
||||||
}
|
|
||||||
|
|
||||||
animate() {
|
|
||||||
const frame = () => {
|
|
||||||
this.animationFrameId = requestAnimationFrame(frame);
|
|
||||||
this.renderFrame();
|
|
||||||
};
|
|
||||||
frame();
|
|
||||||
}
|
|
||||||
|
|
||||||
renderFrame() {
|
|
||||||
const time = Date.now() * 0.001;
|
|
||||||
this.mesh.material.uniforms.uTime.value = Math.sin(time);
|
|
||||||
this.filter.render(this.scene, this.camera, time);
|
|
||||||
}
|
|
||||||
|
|
||||||
dispose() {
|
|
||||||
if (this.animationFrameId) cancelAnimationFrame(this.animationFrameId);
|
|
||||||
this.filter?.dispose();
|
|
||||||
this.filter?.domElement?.remove();
|
|
||||||
this.renderer?.dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const mount = async ({ host, ancestor, readyClass, text, enableWaves, charJitter, asciiFontSize, textFontSize }) => {
|
|
||||||
try {
|
|
||||||
await loadThree();
|
|
||||||
} catch {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (!window.THREE) return;
|
|
||||||
|
|
||||||
const rect = host.getBoundingClientRect();
|
|
||||||
if (rect.width < 20 || rect.height < 20) return;
|
|
||||||
|
|
||||||
let instance;
|
|
||||||
try {
|
|
||||||
instance = new CanvAscii(
|
|
||||||
{
|
|
||||||
text,
|
|
||||||
asciiFontSize: asciiFontSize ?? 11,
|
|
||||||
textFontSize: textFontSize ?? 220,
|
|
||||||
textColor: "#eaf9f7",
|
|
||||||
planeBaseHeight: 8,
|
|
||||||
enableWaves: enableWaves ?? true,
|
|
||||||
charJitter: charJitter ?? false,
|
|
||||||
},
|
|
||||||
host,
|
|
||||||
rect.width,
|
|
||||||
rect.height,
|
|
||||||
);
|
|
||||||
await instance.init();
|
|
||||||
instance.animate();
|
|
||||||
} catch {
|
|
||||||
instance?.dispose();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
host.classList.add("is-ready");
|
|
||||||
ancestor.classList.add(readyClass);
|
|
||||||
|
|
||||||
if ("ResizeObserver" in window) {
|
|
||||||
const resizeObserver = new ResizeObserver((entries) => {
|
|
||||||
const entry = entries[0];
|
|
||||||
if (!entry) return;
|
|
||||||
const { width, height } = entry.contentRect;
|
|
||||||
if (width > 20 && height > 20) instance.setSize(width, height);
|
|
||||||
});
|
|
||||||
resizeObserver.observe(host);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
targets.forEach((target) => {
|
|
||||||
const observer = new IntersectionObserver(
|
|
||||||
(entries, obs) => {
|
|
||||||
entries.forEach((entry) => {
|
|
||||||
if (!entry.isIntersecting) return;
|
|
||||||
obs.disconnect();
|
|
||||||
mount(target);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
{ rootMargin: "200px" },
|
|
||||||
);
|
|
||||||
observer.observe(target.host);
|
|
||||||
});
|
|
||||||
})();
|
|
||||||
|
|
||||||
// --- Liquid ASCII: a real FLIP/PIC fluid simulation (grid + particles,
|
// --- Liquid ASCII: a real FLIP/PIC fluid simulation (grid + particles,
|
||||||
// incompressible pressure solve, particle-particle separation) rendered as
|
// incompressible pressure solve, particle-particle separation) rendered as
|
||||||
// ASCII. No React, no registry, no CDN. Ported by hand from
|
// ASCII. No React, no registry, no CDN. Ported by hand from
|
||||||
|
|
@ -1159,7 +782,7 @@ if (gooeyNav && gooeyLinks.length && !reducedMotion) {
|
||||||
let renderTick = 0;
|
let renderTick = 0;
|
||||||
|
|
||||||
// Monospace glyph *advance width* is narrower than the font-size (for
|
// Monospace glyph *advance width* is narrower than the font-size (for
|
||||||
// IBM Plex Mono, roughly 0.6x) — assuming 1 character == CELL px wide
|
// A typical monospace font is roughly 0.6x as wide as it is tall — assuming 1 character == CELL px wide
|
||||||
// under-filled the row by the same margin, which is why the water
|
// under-filled the row by the same margin, which is why the water
|
||||||
// stopped a good way short of the right edge even though the box itself
|
// stopped a good way short of the right edge even though the box itself
|
||||||
// was full width. Measure the real rendered glyph box instead of
|
// was full width. Measure the real rendered glyph box instead of
|
||||||
|
|
|
||||||
1703
package-lock.json
generated
13
package.json
|
|
@ -1,5 +1,5 @@
|
||||||
{
|
{
|
||||||
"name": "fi3w0-portfolio",
|
"name": "fiwlabs-homepage",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"description": "Personal systems and development portfolio for Alex (Fi3w0).",
|
"description": "Personal systems and development portfolio for Alex (Fi3w0).",
|
||||||
"private": true,
|
"private": true,
|
||||||
|
|
@ -10,17 +10,22 @@
|
||||||
"homelab"
|
"homelab"
|
||||||
],
|
],
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Alex (Fi3w0)",
|
"author": "Alex (Fi3w0) <alex@fiwlabs.dev>",
|
||||||
|
"homepage": "https://fiwlabs.dev",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24",
|
||||||
|
"npm": ">=11"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "webpack serve --open --config webpack.config.dev.js",
|
"start": "webpack serve --open --config webpack.config.dev.js",
|
||||||
"build": "webpack --config webpack.config.prod.js"
|
"build": "webpack --config webpack.config.prod.js"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"copy-webpack-plugin": "^11.0.0",
|
"copy-webpack-plugin": "^14.0.0",
|
||||||
"html-webpack-plugin": "^5.6.0",
|
"html-webpack-plugin": "^5.6.0",
|
||||||
"webpack": "^5.91.0",
|
"webpack": "^5.91.0",
|
||||||
"webpack-cli": "^5.1.4",
|
"webpack-cli": "^5.1.4",
|
||||||
"webpack-dev-server": "^5.0.4",
|
"webpack-dev-server": "^6.0.0",
|
||||||
"webpack-merge": "^5.10.0"
|
"webpack-merge": "^5.10.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||