Refresh selected projects and publish commissions page

This commit is contained in:
Fi3w0 2026-10-11 00:35:10 +02:00
parent 592d19fea6
commit 29f9c964f0
10 changed files with 1203 additions and 73 deletions

View file

@ -9,6 +9,7 @@ RUN npm ci --ignore-scripts --no-audit --no-fund
COPY webpack.common.js webpack.config.prod.js ./
COPY index.html 404.html favicon.ico icon.png icon.svg robots.txt site.webmanifest ./
COPY commissions ./commissions
COPY css ./css
COPY img ./img
COPY js ./js

View file

@ -1,6 +1,6 @@
# 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 source for [fiwlabs.dev](https://fiwlabs.dev), the personal portfolio of Alex (`Fi3w0`). It presents selected systems and desktop projects including TideWM, Scorium, Flux, Moonlit Shell, Prismarine, FiwNode, Nereus, 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.
@ -80,6 +80,7 @@ Keep secrets out of the frontend and out of Docker build arguments. Everything d
```text
css/ Site styles
commissions/ Commission services page
deploy/nginx.conf Static server and security headers
img/ Optimized project artwork
js/app.js Interaction and visual effects
@ -91,3 +92,11 @@ 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.
## Keeping the portfolio current
The homepage is a curated portfolio, not a release feed. Project descriptions focus on what each tool does; versions, download totals, supported Minecraft versions, and release roadmaps belong in the linked repositories or Modrinth pages. Scorium links to its organization so its implementations can grow without adding more homepage cards.
Keep the four illustrated projects as the main showcase. Use the compact project rows for other substantial work, and replace entries rather than continually adding sections. Small utilities belong in the short workbench list; archived prototypes remain discoverable through the all-repositories link.
The GitHub avatar is a local snapshot in `img/avatar.png`, with a cache version on its image URL. Replace it and increment that version when changing the profile picture. The browser needs no GitHub API access, tokens, or scheduled refreshes to display the portfolio. Routine project releases do not require a website update.

102
commissions/index.html Normal file
View file

@ -0,0 +1,102 @@
<!doctype html>
<html class="no-js" lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Commissions - Fi3w0 / Alex</title>
<meta name="description" content="Linux help, homelab and Docker setup, Minecraft server work, automation, web infrastructure, and honest technical troubleshooting by Alex / Fi3w0.">
<meta property="og:title" content="Commissions - Fi3w0 / Alex">
<meta property="og:description" content="Send the weird setup, the broken server, or the idea that needs shipping.">
<meta property="og:type" content="website">
<meta property="og:url" content="https://fiwlabs.dev/commissions/">
<meta property="og:image" content="https://fiwlabs.dev/img/moonlit-shell.jpg">
<meta property="og:image:alt" content="A custom Linux desktop built by Alex">
<meta name="twitter:card" content="summary_large_image">
<link rel="canonical" href="https://fiwlabs.dev/commissions/">
<link rel="stylesheet" href="/css/style.css?v=11">
<link rel="stylesheet" href="/css/editorial.css?v=13">
<link rel="stylesheet" href="/css/commissions.css?v=3">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
<link rel="apple-touch-icon" href="/icon.png">
<link rel="manifest" href="/site.webmanifest">
<meta name="theme-color" content="#0b1014">
</head>
<body class="commissions-page">
<a class="skip-link" href="#main">Skip to content</a>
<div class="commission-grid" aria-hidden="true"></div>
<header class="site-header commission-nav-header">
<a class="brand" href="/" aria-label="Fi3w0 home"><span class="brand-mark" aria-hidden="true">&gt;_</span><span>fiw / commissions</span></a>
<button class="nav-toggle" type="button" aria-expanded="false" aria-controls="primary-navigation"><span>Menu</span><span aria-hidden="true">+</span></button>
<nav class="site-nav" id="primary-navigation" aria-label="Primary navigation">
<span class="nav-gooey" aria-hidden="true"><span class="nav-gooey-blob nav-gooey-blob--trail"></span><span class="nav-gooey-blob nav-gooey-blob--lead"></span></span>
<a href="#work">What I touch</a><a href="#pricing">Prices</a><a href="#process">How it works</a>
<a class="nav-pill" href="/">Back home <span aria-hidden="true">&nearr;</span></a>
</nav>
</header>
<main id="main">
<section class="commission-hero-new" id="top" aria-labelledby="hero-title">
<div class="commission-glow" aria-hidden="true"></div>
<div class="commission-hero-copy reveal is-visible">
<div class="commission-availability"><span><i aria-hidden="true"></i> queue open</span><span>remote / worldwide</span></div>
<p class="commission-command">alex@fiw:~ $ solve --whatever-this-is</p>
<h1 id="hero-title">Your system is <span class="commission-word" data-words="weird,broken,unfinished,haunted">weird</span>.<br>Good.</h1>
<p class="commission-lede">Linux, servers, Minecraft, automation, and small websites. Bring me the setup snag, broken deployment, or idea with a clear finish line. I inspect the problem before quoting the work.</p>
<div class="commission-actions"><a href="#pricing">See starting prices <span aria-hidden="true">&darr;</span></a><a href="mailto:alex@fiwlabs.dev?subject=Commission%20enquiry">Send the problem <span aria-hidden="true">&rarr;</span></a></div>
<div class="commission-facts"><p><strong>€10</strong><span>smallest starting point</span></p><p><strong>6</strong><span>main rabbit holes</span></p><p><strong>0</strong><span>need to know the jargon</span></p></div>
</div>
<div class="scope-console reveal is-visible" aria-label="Example commission triage board">
<div class="scope-console-top"><span>FIW_SCOPE_MONITOR</span><span class="scope-clock" aria-hidden="true">00:00:00</span></div>
<div class="scope-radar" aria-hidden="true"><span class="radar-ring radar-ring-one"></span><span class="radar-ring radar-ring-two"></span><span class="radar-sweep"></span><i class="radar-blip radar-blip-one"></i><i class="radar-blip radar-blip-two"></i><i class="radar-blip radar-blip-three"></i><strong>PROBLEM<br>DETECTED</strong></div>
<div class="scope-queue"><div><span>01</span><p><b>Minecraft server</b><small>crashes on startup</small></p><em>TRACE</em></div><div><span>02</span><p><b>Docker stack</b><small>works only on Tuesdays</small></p><em>INSPECT</em></div><div><span>03</span><p><b>Linux laptop</b><small>Wi-Fi has left the chat</small></p><em>FIX</em></div></div>
<p class="scope-console-foot"><span>INPUT:</span> symptoms + context + logs <i></i> <span>OUTPUT:</span> tested result + handover</p>
</div>
</section>
<div class="commission-tape" aria-hidden="true"><div class="commission-tape-track">
<div class="commission-tape-set"><span>FIX</span><i></i><span>BUILD</span><i></i><span>DEPLOY</span><i></i><span>MIGRATE</span><i></i><span>EXPLAIN</span><i></i><span>AUTOMATE</span><i></i></div>
<div class="commission-tape-set"><span>FIX</span><i></i><span>BUILD</span><i></i><span>DEPLOY</span><i></i><span>MIGRATE</span><i></i><span>EXPLAIN</span><i></i><span>AUTOMATE</span><i></i></div>
</div></div>
<section class="commission-shell capability-section" id="work" aria-labelledby="capability-title">
<header class="commission-section-head reveal"><p>01 / WHAT I TOUCH</p><h2 id="capability-title">Choose your chaos.</h2><span>Pick the closest box. The links below show related work I have built and documented.</span></header>
<div class="capability-grid">
<article class="capability-card capability-linux reveal"><span>01 / daily driver</span><h3>Linux</h3><p>Desktop installs, packages, dev environments, and custom setups. Boot, driver, and performance problems start with a diagnosis.</p><a class="capability-proof" href="https://github.com/Fi3w0/Moonlit-shell" target="_blank" rel="noreferrer">Related work: Moonlit Shell <span aria-hidden="true">&nearr;</span></a><div><b>Arch</b><b>Gentoo</b><b>Ubuntu</b><b>KDE</b><b>Wayland</b></div></article>
<article class="capability-card capability-server reveal"><span>02 / lives in a rack</span><h3>Homelab + servers</h3><p>Docker stacks, reverse proxies, HTTPS, DNS, and monitoring for small servers. Migrations and backups include an agreed recovery plan and restore checks.</p><a class="capability-proof" href="https://github.com/Fi3w0/Nereus" target="_blank" rel="noreferrer">Related work: Nereus <span aria-hidden="true">&nearr;</span></a><div><b>Compose</b><b>Traefik</b><b>Forgejo</b><b>Portainer</b></div></article>
<article class="capability-card capability-minecraft reveal"><span>03 / block infrastructure</span><h3>Minecraft</h3><p>Server setups, modpack configuration, crash diagnosis, and focused custom mods. Version updates and loader ports are quoted after inspecting the code and dependencies.</p><a class="capability-proof" href="https://github.com/Fi3w0/Fiw-Bosses" target="_blank" rel="noreferrer">Related work: FIW Bosses <span aria-hidden="true">&nearr;</span></a><div><b>Fabric</b><b>Forge</b><b>NeoForge</b><b>Paper</b></div></article>
<article class="capability-card capability-automation reveal"><span>04 / do it once</span><h3>Automation</h3><p>Scripts, CLI tools, small bots, APIs, and CI jobs that solve one defined problem. Inputs, integrations, and failure handling are part of the scope.</p><a class="capability-proof" href="https://github.com/Fi3w0/AquaTTY" target="_blank" rel="noreferrer">Related work: AquaTTY <span aria-hidden="true">&nearr;</span></a><div><b>Bash</b><b>Python</b><b>Go</b><b>CI/CD</b></div></article>
<article class="capability-card capability-web reveal"><span>05 / ship the page</span><h3>Small web</h3><p>Personal pages, landing pages, and documentation sites, plus domains, HTTPS, Docker deployment, and build repairs. Content and page count are agreed up front.</p><a class="capability-proof" href="https://github.com/Fi3w0/HomePage" target="_blank" rel="noreferrer">Related work: HomePage <span aria-hidden="true">&nearr;</span></a><div><b>Static</b><b>Nginx</b><b>Deploy</b><b>DNS</b></div></article>
<article class="capability-card capability-hardware reveal"><span>06 / before checkout</span><h3>Hardware</h3><p>PC and homelab parts lists for your budget and workload. I check compatibility, upgrade options, and the limits of advice given remotely.</p><div><b>PC builds</b><b>Mini PCs</b><b>Servers</b></div></article>
</div>
</section>
<section class="price-section" id="pricing" aria-labelledby="price-title"><div class="commission-shell">
<header class="price-head reveal"><div><p>02 / STARTING PRICES</p><h2 id="price-title">A number before the email.</h2></div><div class="price-head-note"><strong>Quoted in EUR / USD approximate</strong><span>These ranges cover small, defined jobs. You get a fixed quote after I inspect the setup; larger jobs are quoted individually. Hosting, domains, and paid services are separate.</span></div></header>
<div class="price-filter reveal" role="group" aria-label="Filter commission prices"><button type="button" class="is-active" data-price-filter="all" aria-pressed="true">All <span>27</span></button><button type="button" data-price-filter="linux" aria-pressed="false">Linux help <span>5</span></button><button type="button" data-price-filter="servers" aria-pressed="false">Servers <span>8</span></button><button type="button" data-price-filter="automation" aria-pressed="false">Automation <span>2</span></button><button type="button" data-price-filter="minecraft" aria-pressed="false">Minecraft <span>8</span></button><button type="button" data-price-filter="web" aria-pressed="false">Web + custom <span>4</span></button></div>
<div class="price-catalog">
<section class="price-group reveal" data-price-category="linux" aria-labelledby="price-linux"><div class="price-group-label"><span>01</span><h3 id="price-linux">Help + Linux</h3><small>5 jobs</small></div><div class="price-list"><div><p>General troubleshooting</p><strong>€10–25</strong><small>$12–30</small></div><div><p>Linux help / setup session</p><strong>€15–30</strong><small>$18–35</small></div><div><p>Linux desktop customization</p><strong>€20–45</strong><small>$25–55</small></div><div><p>Developer environment setup</p><strong>€20–40</strong><small>$25–45</small></div><div><p>Hardware consultation / parts list</p><strong>€10–25</strong><small>$12–30</small></div></div></section>
<section class="price-group reveal" data-price-category="servers" aria-labelledby="price-servers"><div class="price-group-label"><span>02</span><h3 id="price-servers">Servers + infrastructure</h3><small>8 jobs</small></div><div class="price-list"><div><p>Server / PC performance diagnosis</p><strong>€15–35</strong><small>$18–40</small></div><div><p>Dockerize an existing app</p><strong>€25–60</strong><small>$30–70</small></div><div><p>Deploy a self-hosted service</p><strong>€25–60</strong><small>$30–70</small></div><div><p>Basic homelab / server setup</p><strong>€40–90</strong><small>$45–105</small></div><div><p>Full Docker Compose stack</p><strong>€50–100</strong><small>$60–115</small></div><div><p>Reverse proxy + domain + HTTPS</p><strong>€25–55</strong><small>$30–65</small></div><div><p>Backup setup + restore check</p><strong>€25–60</strong><small>$30–70</small></div><div><p>CI/CD setup</p><strong>€30–70</strong><small>$35–80</small></div></div></section>
<section class="price-group reveal" data-price-category="automation" aria-labelledby="price-automation"><div class="price-group-label"><span>03</span><h3 id="price-automation">Automation + tools</h3><small>2 jobs</small></div><div class="price-list"><div><p>Small automation script</p><strong>€15–50</strong><small>$18–60</small></div><div><p>Small Discord / utility bot</p><strong>€30–80</strong><small>$35–95</small></div></div></section>
<section class="price-group reveal" data-price-category="minecraft" aria-labelledby="price-minecraft"><div class="price-group-label"><span>04</span><h3 id="price-minecraft">Minecraft</h3><small>8 jobs</small></div><div class="price-list"><div><p>Basic server setup</p><strong>€20–40</strong><small>$25–45</small></div><div><p>Modded server setup</p><strong>€35–75</strong><small>$40–90</small></div><div><p>Server crash diagnosis / targeted fix</p><strong>€15–45</strong><small>$18–55</small></div><div><p>Performance review / targeted tuning</p><strong>€20–50</strong><small>$25–60</small></div><div><p>Modpack setup / configuration</p><strong>€25–60</strong><small>$30–70</small></div><div><p>Simple custom mod</p><strong>€40–120</strong><small>$45–140</small></div><div><p>Small mod port / version update</p><strong>€25–80</strong><small>$30–95</small></div><div><p>Permissions / proxy configuration</p><strong>€20–50</strong><small>$25–60</small></div></div></section>
<section class="price-group reveal" data-price-category="web" aria-labelledby="price-web"><div class="price-group-label"><span>05</span><h3 id="price-web">Web + strange ideas</h3><small>4 jobs</small></div><div class="price-list"><div><p>One-page website</p><strong>€50–90</strong><small>$60–105</small></div><div><p>One-page website + deployment / domain</p><strong>€70–120</strong><small>$80–140</small></div><div><p>Website / server deployment only</p><strong>€20–40</strong><small>$25–45</small></div><div class="price-custom"><p>Weird / custom project</p><strong>Tell me the idea</strong><small>quoted individually</small></div></div></section>
</div>
<p class="price-count" aria-live="polite"><span id="price-visible-count">27</span> starting points visible</p>
</div></section>
<section class="commission-shell job-section" aria-labelledby="job-title"><header class="commission-section-head reveal"><p>03 / JOB SIZE</p><h2 id="job-title">How much mess?</h2><span>No mystery package names. Just three useful shapes for the work.</span></header><div class="job-tickets"><article class="job-ticket reveal"><div><span>FIW-001</span><b>QUICK FIX</b></div><h3>One sharp problem.</h3><p>One config issue, small script, setup snag, or diagnosis session. We agree on the time or task limit and the result you will receive.</p><small>often starts around €10–30</small></article><article class="job-ticket job-ticket-main reveal"><div><span>FIW-002</span><b>SETUP / BUILD</b></div><h3>Make the whole thing work.</h3><p>One agreed server, Docker stack, site, modded setup, or automation. Delivered with checks and notes for running it yourself.</p><small>priced from the catalog above</small></article><article class="job-ticket reveal"><div><span>FIW-003</span><b>CUSTOM</b></div><h3>The odd one.</h3><p>Several systems, an unusual idea, or unfamiliar compatibility work. I check feasibility first and quote the parts I can deliver.</p><small>quoted after I understand it</small></article></div></section>
<section class="process-section-new" id="process" aria-labelledby="process-title"><div class="commission-shell"><header class="process-head reveal"><p>04 / HOW IT WORKS</p><h2 id="process-title">Four steps. No ceremony.</h2></header><ol class="process-steps"><li class="reveal"><span>01</span><div><h3>Show me the thing</h3><p>Send the goal, symptoms, setup, versions, budget, deadline, and useful logs. Tell me what a successful result looks like.</p></div><b>hello</b></li><li class="reveal"><span>02</span><div><h3>I trace the edges</h3><p>I inspect the setup and define the deliverable, access, dependencies, and checks. Troubleshooting may be scoped as diagnosis before a fix is agreed.</p></div><b>scope</b></li><li class="reveal"><span>03</span><div><h3>You get a quote</h3><p>A price and plan before work begins, including revisions and any support period. Extra features or changed requirements get a separate quote first.</p></div><b>agree</b></li><li class="reveal"><span>04</span><div><h3>I make it behave</h3><p>Build, check the agreed result, and hand over the files and setup notes. Follow-up fixes and ongoing maintenance follow the terms in your quote.</p></div><b>ship</b></li></ol><p class="commission-work-note">I use AI tools for parts of development. I own the design, review, testing, and integration, and agree on checks for your setup before delivery.</p></div></section>
<section class="commission-shell payment-section-new" aria-labelledby="payment-title"><header class="commission-section-head reveal"><p>05 / PAYMENT</p><h2 id="payment-title">Pick the boring money pipe.</h2><span>We choose after the quote. Three routes are live; Stripe joins them when its checkout is connected.</span></header><div class="payment-rail"><a class="reveal" href="https://ko-fi.com/fi3w0" target="_blank" rel="noreferrer"><span>01 / OPEN &nearr;</span><h3>Ko-fi</h3><p>Small jobs + tips</p></a><a class="reveal" href="https://paypal.me/fi3w0" target="_blank" rel="noreferrer"><span>02 / OPEN &nearr;</span><h3>PayPal</h3><p>Direct work</p></a><article class="reveal"><span>03 / SOON</span><h3>Stripe</h3><p>Larger quoted jobs</p></article><a class="reveal" href="https://github.com/sponsors/Fi3w0" target="_blank" rel="noreferrer"><span>04 / OPEN &nearr;</span><h3>GitHub Sponsors</h3><p>Open-source support</p></a></div></section>
<section class="commission-cta" id="contact" aria-labelledby="contact-title"><div class="commission-shell"><p class="reveal">06 / YOUR TURN</p><h2 id="contact-title" class="reveal">Show me<br>the logs.</h2><p class="commission-cta-copy reveal">Or the screenshot. Or the half-finished idea. You do not need to know whether the villain is DNS, Java, Docker, Linux, permissions, or networking.</p><div class="commission-contact-grid reveal"><a href="mailto:alex@fiwlabs.dev?subject=Commission%20enquiry"><span>Email</span><small>alex@fiwlabs.dev</small><b>&rarr;</b></a><a href="https://t.me/fi3w0" target="_blank" rel="noreferrer"><span>Telegram</span><small>@fi3w0</small><b>&nearr;</b></a><button type="button" id="copy-discord"><span>Discord</span><small id="discord-label">fi3w0 - click to copy</small><b>+</b></button><button type="button" id="copy-brief"><span>Copy brief</span><small>get a message template</small><b>+</b></button></div><p class="copy-status" id="copy-status" role="status" aria-live="polite"></p></div></section>
</main>
<footer class="commission-footer"><a class="brand" href="/"><span class="brand-mark" aria-hidden="true">&gt;_</span><span>fi3w0</span></a><p>Built for useful problems.</p><p>&copy; <span id="year">2026</span> Alex / Fi3w0</p></footer>
<script src="/js/app.js?v=10" defer></script>
<script src="/js/commissions.js?v=2" defer></script>
</body>
</html>

902
css/commissions.css Normal file
View file

@ -0,0 +1,902 @@
:root {
--commission-pad: clamp(1.25rem, 4vw, 4.5rem);
--commission-width: 96rem;
}
.commissions-page {
--mx: 72%;
--my: 22%;
background: #0b1014;
}
.commissions-page::before,
.commissions-page .page-grid {
display: none;
}
.commission-grid {
position: fixed;
z-index: -2;
inset: 0;
opacity: 0.34;
background-image:
linear-gradient(rgba(143, 212, 208, 0.035) 1px, transparent 1px),
linear-gradient(90deg, rgba(143, 212, 208, 0.035) 1px, transparent 1px);
background-size: 2rem 2rem;
pointer-events: none;
mask-image: radial-gradient(circle at var(--mx) var(--my), black, transparent 55%);
}
.commission-shell {
width: min(100%, var(--commission-width));
margin: 0 auto;
padding-right: var(--commission-pad);
padding-left: var(--commission-pad);
}
.commission-nav-header {
border-bottom-color: rgba(143, 212, 208, 0.2);
background: rgba(11, 16, 20, 0.9);
backdrop-filter: blur(16px);
}
.commission-nav-header .brand > span:last-child {
color: var(--cyan);
}
.commission-nav-header .nav-pill {
color: var(--coral);
}
.commission-hero-new {
--hx: 72%;
--hy: 24%;
position: relative;
display: grid;
width: min(100%, var(--commission-width));
min-height: 100svh;
margin: 0 auto;
padding: clamp(8.5rem, 13vh, 10rem) var(--commission-pad) clamp(4rem, 8vh, 7rem);
grid-template-columns: minmax(0, 7fr) minmax(24rem, 5fr);
align-items: center;
gap: clamp(2.5rem, 6vw, 7rem);
}
.commission-glow {
position: absolute;
z-index: -1;
inset: 0;
background:
radial-gradient(circle at var(--hx) var(--hy), rgba(143, 212, 208, 0.16), transparent 18rem),
radial-gradient(circle at 15% 82%, rgba(240, 153, 110, 0.1), transparent 24rem);
pointer-events: none;
}
.commission-hero-copy {
min-width: 0;
}
.commission-availability {
display: flex;
max-width: 46rem;
justify-content: space-between;
margin-bottom: 2rem;
padding-bottom: 0.65rem;
border-bottom: 1px dashed var(--line);
color: var(--muted);
font-size: 0.62rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.commission-availability span:first-child {
color: var(--lime);
}
.commission-availability i {
display: inline-block;
width: 0.45rem;
height: 0.45rem;
margin-right: 0.45rem;
border-radius: 50%;
background: var(--lime);
box-shadow: 0 0 0 0 rgba(200, 238, 128, 0.4);
animation: commission-pulse 2s ease-out infinite;
}
.commission-command {
margin-bottom: 1.3rem;
color: var(--cyan);
font-size: 0.72rem;
letter-spacing: 0.05em;
}
.commission-hero-new h1 {
max-width: 9ch;
margin-bottom: 2rem;
font-size: clamp(4.4rem, 8.5vw, 9rem);
letter-spacing: -0.08em;
line-height: 0.82;
}
.commission-word {
position: relative;
display: inline-block;
color: var(--coral);
}
.commission-word.is-changing {
animation: word-swap 260ms steps(2, end);
}
.commission-lede {
max-width: 48rem;
margin-bottom: 2.2rem;
color: var(--muted);
font-size: clamp(0.95rem, 1.35vw, 1.14rem);
}
.commission-actions {
display: flex;
flex-wrap: wrap;
gap: 0.8rem;
margin-bottom: 3rem;
}
.commission-actions a {
display: inline-flex;
min-height: 3rem;
align-items: center;
justify-content: space-between;
gap: 1.5rem;
padding: 0.7rem 1rem;
border: 1px solid var(--line);
color: var(--text);
font-size: 0.7rem;
font-weight: 600;
text-transform: uppercase;
transition: background 160ms ease, color 160ms ease, transform 160ms ease;
}
.commission-actions a:first-child {
border-color: var(--coral);
background: var(--coral);
color: var(--ink);
}
.commission-actions a:hover {
transform: translateY(-0.25rem);
background: var(--lime);
color: var(--ink);
}
.commission-facts {
display: grid;
max-width: 46rem;
border-top: 1px solid var(--line);
border-bottom: 1px solid var(--line);
grid-template-columns: repeat(3, 1fr);
}
.commission-facts p {
display: grid;
min-height: 5.5rem;
margin: 0;
padding: 0.75rem;
align-content: center;
}
.commission-facts p + p {
border-left: 1px solid var(--line);
}
.commission-facts strong,
.commission-facts span {
display: block;
}
.commission-facts strong {
color: var(--coral);
font-family: var(--display);
font-size: 1.75rem;
line-height: 1;
}
.commission-facts span {
margin-top: 0.35rem;
color: var(--muted);
font-size: 0.54rem;
text-transform: uppercase;
}
.scope-console {
position: relative;
min-width: 0;
border: 2px solid rgba(143, 212, 208, 0.4);
background: rgba(13, 22, 28, 0.9);
box-shadow: 1rem 1rem 0 rgba(240, 153, 110, 0.75);
}
.scope-console::before {
position: absolute;
z-index: -1;
inset: -1.4rem 1.4rem 1.4rem -1.4rem;
border: 1px dashed rgba(143, 212, 208, 0.28);
content: "";
}
.scope-console-top {
display: flex;
min-height: 3rem;
align-items: center;
justify-content: space-between;
padding: 0 1rem;
border-bottom: 1px solid rgba(143, 212, 208, 0.25);
color: var(--cyan);
font-size: 0.58rem;
letter-spacing: 0.08em;
}
.scope-clock {
color: var(--lime);
}
.scope-radar {
position: relative;
display: grid;
width: min(72%, 19rem);
aspect-ratio: 1;
margin: 2rem auto;
overflow: hidden;
place-items: center;
border: 1px solid rgba(143, 212, 208, 0.3);
border-radius: 50%;
background:
linear-gradient(rgba(143, 212, 208, 0.16) 1px, transparent 1px),
linear-gradient(90deg, rgba(143, 212, 208, 0.16) 1px, transparent 1px),
radial-gradient(circle, rgba(143, 212, 208, 0.08), transparent 68%);
background-size: 2rem 2rem, 2rem 2rem, auto;
}
.scope-radar::before,
.scope-radar::after {
position: absolute;
background: rgba(143, 212, 208, 0.28);
content: "";
}
.scope-radar::before { width: 100%; height: 1px; }
.scope-radar::after { width: 1px; height: 100%; }
.radar-ring {
position: absolute;
border: 1px solid rgba(143, 212, 208, 0.28);
border-radius: 50%;
}
.radar-ring-one { inset: 18%; }
.radar-ring-two { inset: 36%; }
.radar-sweep {
position: absolute;
inset: 50% 50% 0 0;
transform-origin: 100% 0;
background: linear-gradient(35deg, transparent 45%, rgba(200, 238, 128, 0.45));
animation: radar-spin 4.5s linear infinite;
}
.radar-blip {
position: absolute;
width: 0.55rem;
height: 0.55rem;
border: 2px solid var(--coral);
border-radius: 50%;
box-shadow: 0 0 0.9rem var(--coral);
animation: radar-blip 1.8s ease-in-out infinite;
}
.radar-blip-one { top: 23%; left: 62%; }
.radar-blip-two { top: 67%; left: 27%; animation-delay: 0.55s; }
.radar-blip-three { top: 72%; left: 74%; animation-delay: 1.1s; }
.scope-radar strong {
position: relative;
z-index: 2;
padding: 0.45rem;
background: rgba(11, 16, 20, 0.75);
color: var(--lime);
font-size: 0.68rem;
font-weight: 600;
letter-spacing: 0.08em;
line-height: 1.3;
text-align: center;
}
.scope-queue {
border-top: 1px solid rgba(143, 212, 208, 0.25);
}
.scope-queue > div {
display: grid;
min-height: 4.4rem;
padding: 0.7rem 1rem;
border-bottom: 1px dashed var(--line);
grid-template-columns: 2rem minmax(0, 1fr) auto;
align-items: center;
gap: 0.7rem;
transition: background 150ms ease, padding 150ms ease;
}
.scope-queue > div:hover {
padding-left: 1.35rem;
background: rgba(143, 212, 208, 0.08);
}
.scope-queue > div > span,
.scope-queue em {
color: var(--coral);
font-size: 0.55rem;
font-style: normal;
}
.scope-queue p { margin: 0; }
.scope-queue b,
.scope-queue small { display: block; }
.scope-queue b { font-size: 0.72rem; font-weight: 500; }
.scope-queue small { color: var(--muted); font-size: 0.58rem; }
.scope-console-foot {
display: flex;
min-height: 3rem;
align-items: center;
justify-content: center;
gap: 0.5rem;
margin: 0;
color: var(--muted);
font-size: 0.5rem;
text-transform: uppercase;
}
.scope-console-foot span { color: var(--cyan); }
.scope-console-foot i { width: 1.2rem; height: 1px; background: var(--line); }
.commission-tape {
overflow: hidden;
border-top: 3px solid var(--ink);
border-bottom: 3px solid var(--ink);
background: var(--lime);
color: var(--ink);
}
.commission-tape-track {
display: flex;
width: max-content;
min-height: 3.6rem;
align-items: center;
animation: commission-tape 24s linear infinite;
}
.commission-tape-set {
display: flex;
min-width: 100vw;
flex-shrink: 0;
align-items: center;
justify-content: space-around;
gap: 1.3rem;
padding: 0 1.3rem;
}
.commission-tape span {
font-family: var(--display);
font-size: 1rem;
letter-spacing: 0.08em;
}
.commission-tape i {
width: 0.55rem;
height: 0.55rem;
transform: rotate(45deg);
background: var(--ink);
}
.capability-section,
.job-section,
.payment-section-new {
padding-top: clamp(5rem, 9vw, 9rem);
padding-bottom: clamp(5rem, 9vw, 9rem);
}
.commission-section-head {
display: grid;
margin-bottom: clamp(3rem, 6vw, 5rem);
padding-top: 1.2rem;
border-top: 4px solid var(--text);
grid-template-columns: 2fr 7fr 3fr;
gap: 1.5rem;
align-items: start;
}
.commission-section-head p,
.price-head p,
.process-head p,
.commission-cta > .commission-shell > p {
color: var(--coral);
font-size: 0.65rem;
font-weight: 600;
letter-spacing: 0.09em;
}
.commission-section-head h2,
.price-head h2,
.process-head h2 {
margin: 0;
font-size: clamp(3.2rem, 6.8vw, 7rem);
letter-spacing: -0.07em;
line-height: 0.88;
}
.commission-section-head > span {
color: var(--muted);
font-size: 0.78rem;
}
.capability-grid {
display: grid;
gap: 1rem;
grid-template-columns: repeat(12, minmax(0, 1fr));
}
.capability-card {
position: relative;
display: flex;
min-height: 27rem;
flex-direction: column;
padding: clamp(1.4rem, 2.5vw, 2.3rem);
overflow: hidden;
border: 2px solid var(--line);
background: rgba(17, 25, 31, 0.86);
transition: transform 180ms ease, border-color 180ms ease;
}
.capability-card:hover {
transform: translateY(-0.4rem);
border-color: currentColor;
}
.capability-card > span {
font-size: 0.58rem;
letter-spacing: 0.08em;
text-transform: uppercase;
}
.capability-card h3 {
margin: auto 0 1.5rem;
font-size: clamp(2.2rem, 4.5vw, 5rem);
letter-spacing: -0.07em;
}
.capability-card p {
max-width: 42rem;
color: var(--muted);
font-size: 0.78rem;
}
.capability-card > div {
display: flex;
flex-wrap: wrap;
gap: 0.35rem;
margin-top: 1rem;
}
.capability-card b {
padding: 0.2rem 0.5rem;
border: 1px solid currentColor;
font-size: 0.54rem;
font-weight: 500;
}
.capability-linux { grid-column: span 4; color: var(--coral); }
.capability-server { grid-column: span 8; color: var(--cyan); }
.capability-minecraft { grid-column: span 7; color: var(--lime); }
.capability-automation { grid-column: span 5; color: var(--coral); }
.capability-web { grid-column: span 5; color: var(--cyan); }
.capability-hardware { grid-column: span 7; color: var(--lime); }
.capability-server,
.capability-minecraft,
.capability-hardware {
min-height: 31rem;
}
.capability-card::after {
position: absolute;
right: -0.08em;
bottom: -0.18em;
color: currentColor;
content: "#";
font-family: var(--display);
font-size: 13rem;
line-height: 0.8;
opacity: 0.035;
pointer-events: none;
}
.price-section,
.process-section-new,
.commission-cta {
box-shadow: 0 0 0 100vmax currentColor;
clip-path: inset(0 -100vmax);
}
.price-section {
padding: clamp(5rem, 9vw, 9rem) 0;
background: var(--paper);
box-shadow: 0 0 0 100vmax var(--paper);
color: var(--ink);
}
.price-head {
display: grid;
margin-bottom: 3rem;
grid-template-columns: minmax(0, 8fr) minmax(18rem, 4fr);
gap: 3rem;
align-items: end;
}
.price-head h2 { max-width: 10ch; }
.price-head-note {
padding: 1rem;
border: 2px solid var(--ink);
background: var(--yellow);
transform: rotate(1deg);
}
.price-head-note strong,
.price-head-note span { display: block; }
.price-head-note strong { margin-bottom: 0.5rem; font-size: 0.68rem; text-transform: uppercase; }
.price-head-note span { color: #3f4848; font-size: 0.7rem; }
.price-filter {
display: flex;
flex-wrap: wrap;
margin-bottom: 2.5rem;
border-top: 4px solid var(--ink);
border-bottom: 2px solid var(--ink);
}
.price-filter button {
flex: 1;
min-width: max-content;
padding: 0.9rem 0.8rem;
border: 0;
border-right: 1px solid var(--ink);
background: transparent;
color: var(--ink);
cursor: pointer;
font-size: 0.62rem;
font-weight: 600;
text-transform: uppercase;
transition: background 150ms ease, color 150ms ease;
}
.price-filter button:last-child { border-right: 0; }
.price-filter button:hover,
.price-filter button.is-active { background: var(--ink); color: var(--paper); }
.price-filter button span { margin-left: 0.35rem; color: #a74b2c; }
.price-filter button.is-active span { color: var(--coral); }
.price-group {
display: grid;
border-top: 2px solid var(--ink);
grid-template-columns: minmax(14rem, 4fr) minmax(0, 8fr);
}
.price-group[hidden] { display: none; }
.price-group-label {
position: sticky;
top: 5.5rem;
align-self: start;
padding: 1.5rem 2rem 1.5rem 0;
}
.price-group-label span,
.price-group-label small { color: #a74b2c; font-size: 0.6rem; text-transform: uppercase; }
.price-group-label h3 { max-width: 9ch; margin: 2rem 0 1rem; font-size: clamp(2rem, 4vw, 4rem); letter-spacing: -0.06em; }
.price-list { border-left: 2px solid var(--ink); }
.price-list > div {
display: grid;
min-height: 5.4rem;
padding: 0.9rem 0 0.9rem 1.5rem;
border-bottom: 1px solid rgba(17, 23, 26, 0.28);
grid-template-columns: minmax(0, 1fr) 8rem 5rem;
align-items: center;
gap: 1rem;
transition: padding 150ms ease, background 150ms ease;
}
.price-list > div:hover {
padding-right: 0.8rem;
padding-left: 2rem;
background: var(--lime);
}
.price-list p { margin: 0; font-size: 0.8rem; }
.price-list strong { font-family: var(--display); font-size: 1.45rem; letter-spacing: -0.04em; text-align: right; }
.price-list small { color: #5c6665; font-size: 0.62rem; text-align: right; }
.price-custom { background: var(--coral); }
.price-custom strong { font-family: var(--mono); font-size: 0.7rem; text-transform: uppercase; }
.price-count {
margin: 1.5rem 0 0;
color: #5c6665;
font-size: 0.65rem;
text-align: right;
text-transform: uppercase;
}
.price-count span { color: #a74b2c; font-weight: 700; }
.job-tickets {
display: grid;
gap: 1rem;
grid-template-columns: repeat(3, minmax(0, 1fr));
}
.job-ticket {
display: flex;
min-height: 28rem;
flex-direction: column;
padding: 1.5rem;
border: 2px dashed var(--line);
background: rgba(17, 25, 31, 0.72);
transition: transform 180ms ease;
}
.job-ticket:hover { transform: rotate(-0.6deg) translateY(-0.3rem); }
.job-ticket-main { border: 0; background: var(--lime); color: var(--ink); transform: rotate(1deg); }
.job-ticket-main:hover { transform: rotate(0.3deg) translateY(-0.3rem); }
.job-ticket > div { display: flex; justify-content: space-between; font-size: 0.58rem; color: var(--coral); }
.job-ticket-main > div { color: #6c4b2f; }
.job-ticket h3 { margin: auto 0 1.3rem; font-size: clamp(2.2rem, 3.7vw, 4rem); letter-spacing: -0.065em; }
.job-ticket p { color: var(--muted); font-size: 0.78rem; }
.job-ticket-main p { color: #3f4848; }
.job-ticket small { margin-top: 1rem; padding-top: 0.7rem; border-top: 1px dashed currentColor; font-size: 0.58rem; text-transform: uppercase; }
.process-section-new {
padding: clamp(5rem, 9vw, 9rem) 0;
background: var(--cyan);
box-shadow: 0 0 0 100vmax var(--cyan);
color: var(--ink);
}
.process-head {
display: grid;
margin-bottom: 3.5rem;
padding-top: 1.2rem;
border-top: 4px solid var(--ink);
grid-template-columns: 2fr 10fr;
gap: 1.5rem;
}
.process-head h2 { max-width: 9ch; }
.process-steps {
margin: 0;
padding: 0;
border-top: 2px solid var(--ink);
border-bottom: 2px solid var(--ink);
list-style: none;
}
.process-steps li {
display: grid;
min-height: 8rem;
padding: 1.2rem 0;
border-bottom: 1px solid rgba(17, 23, 26, 0.28);
grid-template-columns: 5rem minmax(0, 1fr) auto;
align-items: center;
gap: 1.5rem;
transition: padding 150ms ease, background 150ms ease;
}
.process-steps li:last-child { border-bottom: 0; }
.process-steps li:hover { padding-right: 1rem; padding-left: 1rem; background: rgba(233, 229, 218, 0.35); }
.process-steps > li > span { color: #a74b2c; font-size: 0.7rem; }
.process-steps h3 { margin: 0 0 0.35rem; font-family: var(--mono); font-size: clamp(1.1rem, 2vw, 1.65rem); }
.process-steps p { margin: 0; color: #3f4848; font-size: 0.72rem; }
.process-steps b { padding: 0.2rem 0.6rem; border: 1px solid var(--ink); font-size: 0.55rem; font-weight: 500; text-transform: uppercase; }
.payment-rail {
display: grid;
border-top: 4px solid var(--text);
border-bottom: 2px solid var(--text);
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.payment-rail > * {
display: block;
min-height: 18rem;
padding: 1.3rem;
border-right: 1px solid var(--line);
transition: background 160ms ease, color 160ms ease;
}
.payment-rail > *:last-child { border-right: 0; }
.payment-rail > *:hover { background: var(--coral); color: var(--ink); }
.payment-rail span { color: var(--coral); font-size: 0.58rem; }
.payment-rail > *:hover span { color: var(--ink); }
.payment-rail h3 { margin: 6rem 0 0.8rem; font-size: clamp(1.8rem, 3vw, 3rem); letter-spacing: -0.055em; }
.payment-rail p { color: var(--muted); font-size: 0.67rem; text-transform: uppercase; }
.payment-rail > *:hover p { color: var(--ink); }
.commission-cta {
padding: clamp(5rem, 9vw, 9rem) 0;
background: var(--coral);
box-shadow: 0 0 0 100vmax var(--coral);
color: var(--ink);
}
.commission-cta h2 {
max-width: 8ch;
margin-bottom: 2rem;
font-size: clamp(5rem, 14vw, 13rem);
letter-spacing: -0.09em;
line-height: 0.72;
}
.commission-cta-copy {
max-width: 48rem;
margin-bottom: 3rem;
font-size: clamp(0.9rem, 1.5vw, 1.1rem);
}
.commission-contact-grid {
display: grid;
border-top: 4px solid var(--ink);
border-bottom: 2px solid var(--ink);
grid-template-columns: repeat(4, minmax(0, 1fr));
}
.commission-contact-grid > * {
display: grid;
min-width: 0;
min-height: 7.5rem;
padding: 1rem;
border: 0;
border-right: 1px solid var(--ink);
background: transparent;
color: var(--ink);
cursor: pointer;
text-align: left;
grid-template-columns: 1fr auto;
transition: background 150ms ease, padding 150ms ease;
}
.commission-contact-grid > *:last-child { border-right: 0; }
.commission-contact-grid > *:hover { padding-left: 1.4rem; background: var(--yellow); }
.commission-contact-grid span { font-family: var(--display); font-size: clamp(1.4rem, 2.4vw, 2.3rem); letter-spacing: -0.04em; }
.commission-contact-grid small { align-self: end; color: #5b4235; font-size: 0.55rem; }
.commission-contact-grid b { font-size: 1.2rem; }
.commission-footer {
display: grid;
width: min(100%, var(--commission-width));
min-height: 7rem;
margin: 0 auto;
padding: 1.5rem var(--commission-pad);
grid-template-columns: 1fr auto auto;
align-items: center;
gap: 2rem;
}
.commission-footer p { margin: 0; color: var(--muted); font-size: 0.6rem; }
@keyframes radar-spin { to { transform: rotate(360deg); } }
@keyframes radar-blip { 0%, 100% { opacity: 0.35; transform: scale(0.7); } 45% { opacity: 1; transform: scale(1.3); } }
@keyframes commission-tape { to { transform: translateX(-50%); } }
@keyframes commission-pulse { 70%, 100% { box-shadow: 0 0 0 0.5rem rgba(200, 238, 128, 0); } }
@keyframes word-swap { 0% { transform: translate(0); } 25% { transform: translate(-0.05em, 0.04em); text-shadow: 0.06em 0 var(--cyan); } 50% { transform: translate(0.05em, -0.03em); text-shadow: -0.06em 0 var(--lime); } 100% { transform: translate(0); } }
@media (max-width: 74rem) {
.commission-hero-new { min-height: auto; grid-template-columns: 1fr; }
.scope-console { width: min(100%, 48rem); margin: 1rem auto 0; }
.capability-linux,
.capability-automation,
.capability-web { grid-column: span 6; }
.capability-server,
.capability-minecraft,
.capability-hardware { grid-column: span 6; min-height: 27rem; }
.commission-section-head { grid-template-columns: 2fr 7fr; }
.commission-section-head > span { grid-column: 2; }
.payment-rail { grid-template-columns: repeat(2, 1fr); }
.payment-rail > *:nth-child(2) { border-right: 0; }
.payment-rail > *:nth-child(-n + 2) { border-bottom: 1px solid var(--line); }
.commission-contact-grid { grid-template-columns: repeat(2, 1fr); }
.commission-contact-grid > *:nth-child(2) { border-right: 0; }
.commission-contact-grid > *:nth-child(-n + 2) { border-bottom: 1px solid var(--ink); }
}
@media (max-width: 52rem) {
.commission-nav-header .brand > span:last-child { font-size: 0; }
.commission-nav-header .brand > span:last-child::before { content: "fiw / work"; font-size: 0.75rem; }
.commission-hero-new { padding-top: 7.8rem; }
.commission-hero-new h1 { font-size: clamp(3.8rem, 15vw, 6rem); }
.commission-section-head,
.price-head,
.process-head { grid-template-columns: 1fr; }
.commission-section-head > span { grid-column: 1; max-width: 36rem; }
.capability-grid { grid-template-columns: 1fr; }
.capability-linux,
.capability-server,
.capability-minecraft,
.capability-automation,
.capability-web,
.capability-hardware { min-height: 24rem; grid-column: auto; }
.price-group { grid-template-columns: 1fr; }
.price-group-label { position: static; padding-right: 0; }
.price-list { border-top: 2px solid var(--ink); border-left: 0; }
.job-tickets { grid-template-columns: 1fr; }
.job-ticket { min-height: 23rem; }
.job-ticket-main { transform: none; }
.process-head { gap: 0; }
}
@media (max-width: 36rem) {
.commission-availability { display: grid; gap: 0.4rem; }
.commission-command { font-size: 0.62rem; }
.commission-actions { display: grid; }
.commission-actions a { width: 100%; }
.commission-facts { grid-template-columns: 1fr; }
.commission-facts p { min-height: 4.5rem; grid-template-columns: 4rem 1fr; align-items: center; }
.commission-facts p + p { border-top: 1px solid var(--line); border-left: 0; }
.commission-facts span { margin-top: 0; }
.scope-console { box-shadow: 0.55rem 0.55rem 0 rgba(240, 153, 110, 0.75); }
.scope-console::before { display: none; }
.scope-console-foot { display: none; }
.price-filter { display: grid; grid-template-columns: repeat(2, 1fr); }
.price-filter button { min-width: 0; border-right: 1px solid var(--ink); border-bottom: 1px solid var(--ink); }
.price-filter button:nth-child(even) { border-right: 0; }
.price-filter button:nth-last-child(-n + 2) { border-bottom: 0; }
.price-list > div { min-height: 6rem; padding-left: 0; grid-template-columns: minmax(0, 1fr) auto; gap: 0.4rem; }
.price-list > div:hover { padding-left: 0.5rem; }
.price-list small { grid-column: 2; }
.price-list strong { font-size: 1.2rem; }
.process-steps li { grid-template-columns: 2.2rem 1fr; gap: 0.7rem; }
.process-steps b { display: none; }
.payment-rail,
.commission-contact-grid { grid-template-columns: 1fr; }
.payment-rail > *,
.payment-rail > *:nth-child(2) { min-height: 13rem; border-right: 0; border-bottom: 1px solid var(--line); }
.payment-rail > *:last-child { border-bottom: 0; }
.payment-rail h3 { margin-top: 3rem; }
.commission-contact-grid > *,
.commission-contact-grid > *:nth-child(2) { border-right: 0; border-bottom: 1px solid var(--ink); }
.commission-contact-grid > *:last-child { border-bottom: 0; }
.commission-footer { grid-template-columns: 1fr auto; }
.commission-footer > p:first-of-type { display: none; }
}
@media (prefers-reduced-motion: reduce) {
.commission-availability i,
.radar-sweep,
.radar-blip,
.commission-tape-track,
.commission-word { animation: none !important; }
}
/* Proof links sit with each service instead of adding another portfolio section. */
.capability-proof {
align-self: flex-start;
font-size: 0.68rem;
text-underline-offset: 0.25em;
text-decoration: underline;
}
.capability-proof:hover {
color: var(--text);
}
.commission-work-note {
max-width: 68ch;
margin: 2rem 0 0;
color: #3f4848;
font-size: 0.78rem;
}

View file

@ -2664,3 +2664,22 @@ article[data-repo="Moonlit-shell"] .project-feature-copy {
display: none;
}
}
/* Keep the selected tools understandable on phones without expanding cards. */
@media (max-width: 52rem) {
.work-section .project-row {
row-gap: 0.65rem;
align-items: start;
}
.work-section .project-row > p {
display: block;
grid-column: 2 / 3;
grid-row: 2;
}
.work-section .project-row > a {
grid-column: 3;
grid-row: 1;
}
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

After

Width:  |  Height:  |  Size: 43 KiB

View file

@ -5,7 +5,7 @@
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Fi3w0 / Alex - Systems Builder</title>
<meta name="description" content="Alex, also known as Fi3w0, builds TideWM, Scorium, Linux and macOS desktop tools, homelab infrastructure, and configurable server tooling.">
<meta name="description" content="Alex / Fi3w0 builds TideWM, Scorium, Prismarine, native desktop tools, Kubernetes infrastructure, and configurable Minecraft server mods.">
<meta property="og:title" content="Fi3w0 / Alex - Systems Builder">
<meta property="og:description" content="TideWM, Scorium, Linux desktops, homelabs, and the systems holding everything up.">
@ -17,7 +17,7 @@
<link rel="canonical" href="https://fiwlabs.dev/">
<link rel="stylesheet" href="css/style.css?v=10">
<link rel="stylesheet" href="css/editorial.css?v=11">
<link rel="stylesheet" href="css/editorial.css?v=13">
<link rel="icon" href="/favicon.ico" sizes="any">
<link rel="icon" href="/icon.svg" type="image/svg+xml">
@ -31,7 +31,7 @@
<div class="page-grid" aria-hidden="true"></div>
<header class="site-header">
<a class="brand" href="#top" aria-label="Fi3w0 home">
<a class="brand" href="/" aria-label="Fi3w0 home">
<span class="brand-mark" aria-hidden="true">&gt;_</span>
<span>fi3w0</span>
</a>
@ -48,6 +48,7 @@
<a href="#about">About</a>
<a href="#work">Work</a>
<a href="#stack">Stack</a>
<a href="/commissions/">Commissions</a>
<a href="#contact">Contact</a>
<a class="nav-pill" href="https://github.com/Fi3w0" target="_blank" rel="noreferrer">
GitHub <span aria-hidden="true">&nearr;</span>
@ -65,7 +66,7 @@
<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>
<p class="hero-intro">
I build a Wayland desktop, a programmable configuration language, macOS tools, and Minecraft server mods.
I build desktops, a configuration language, native apps, and the infrastructure to run them.
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.
</p>
@ -76,27 +77,27 @@
</div>
<ul class="hero-signals" aria-label="Profile details">
<li><span>01</span> Arch at the desk</li>
<li><span>01</span> Gentoo + Arch at the desk</li>
<li><span>02</span> Ubuntu on the server</li>
<li><span>03</span> break it, learn, repeat</li>
</ul>
</div>
<aside class="terminal-card reveal is-visible" aria-label="A snapshot of Alex's current work">
<aside class="terminal-card reveal is-visible" aria-label="A selection of Alex's work">
<div class="terminal-chrome">
<span>~/workbench</span>
<span class="terminal-state">LOCAL</span>
</div>
<div class="terminal-body">
<p><span class="prompt">$</span> git status --short</p>
<p class="terminal-output"><span>M</span> TideWM/<br><span>M</span> Scorium/<br><span>??</span> next-system/</p>
<p class="terminal-output"><span>M</span> TideWM/<br><span>M</span> Scorium/<br><span>M</span> Prismarine/</p>
<p><span class="prompt">$</span> cat NOTES</p>
<p class="terminal-output">wayland + rust / config languages<br>automation / servers / self-hosting</p>
<p class="terminal-output">native apps / config languages<br>kubernetes / servers / self-hosting</p>
<p class="terminal-note"># there is always something unfinished</p>
</div>
<div class="profile-chip">
<img src="img/avatar.png" alt="Fi3w0's GitHub avatar" width="460" height="460">
<img src="img/avatar.png?v=2" alt="Fi3w0's GitHub avatar" width="160" height="160">
<div><strong>Alex / Fi3w0</strong><span>Systems over categories</span></div>
</div>
</aside>
@ -158,15 +159,15 @@
<div class="about-copy reveal">
<p class="lead">
Some weeks I am building a Minecraft framework. Other weeks I replace half my desktop
shell or add something unnecessary to the mini PC under my desk.
Some weeks I am building a Minecraft framework or a native desktop app. Other weeks I replace half my desktop
shell or work out how to stop a bad deployment reaching my server.
</p>
<p>
My projects jump between low-level desktop work and data-driven server tools, but the part I enjoy
is the same: turning rough pieces into something I can run and maintain myself.
</p>
<p>
Linux, systemd, Docker, Traefik, backups, and CI pipelines are familiar territory. I like
Linux, Docker, Kubernetes, Traefik, backups, and CI pipelines are familiar territory. I like
hot reload, readable config files, updates that can fail safely, and logs that say what went wrong.
</p>
<p>
@ -194,22 +195,22 @@
<section class="section work-section" id="work" aria-labelledby="work-title">
<header class="section-heading section-heading-row reveal">
<div><p class="section-index">02 / Work</p><h2 id="work-title">Projects, tools, experiments.</h2></div>
<p>Public repositories, installable mods, and rougher ideas that started on my own machines.</p>
<div><p class="section-index">02 / Work</p><h2 id="work-title">Selected projects.</h2></div>
<p>A few projects worth a closer look. Desktop systems, native tools, and infrastructure I can run myself.</p>
</header>
<article class="current-project current-project-tide reveal" aria-labelledby="tidewm-title">
<div class="current-project-main">
<div class="current-meta"><span><i aria-hidden="true"></i> Functional pre-release</span><span>v0.90 / daily-driver path</span></div>
<div class="current-meta"><span><i aria-hidden="true"></i> Independent compositor</span><span>Desktop / Linux</span></div>
<p class="current-kicker">Wayland compositor / Rust</p>
<div class="tide-heading">
<img class="tide-mark" src="img/tidewm-logo.png" alt="" width="48" height="48" loading="lazy">
<h3 id="tidewm-title">TideWM</h3>
</div>
<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>
<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, optional screencasting, and two spatial engines you can switch live.</p>
<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>GPU</strong><span>water + glass</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>
@ -218,19 +219,19 @@
<div class="current-roadmap">
<p>Core systems</p>
<ol>
<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>Ocean</strong><small>One continuous 2D world with reefs, cameras, and bookmarks</small></div><b>done</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>Next pass</strong><small>More hardware coverage, feel tuning, Nvidia native, AUR</small></div><b>testing</b></li>
<li><span>01</span><div><strong>Classic</strong><small>BSP, master/stack, cascade, workspaces, Depth Deck</small></div><b>built in</b></li>
<li><span>02</span><div><strong>Ocean</strong><small>One continuous 2D world with reefs, cameras, and bookmarks</small></div><b>built in</b></li>
<li><span>03</span><div><strong>Water stack</strong><small>Ripples, waves, glass, caustics, sway, depth, and buoyancy</small></div><b>built in</b></li>
<li><span>04</span><div><strong>Configuration</strong><small>Wave config, live reload, keybinds, and custom shaders</small></div><b>live</b></li>
</ol>
</div>
<span class="current-watermark" aria-hidden="true">TIDE</span>
</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">
<article class="project-feature project-feature-scorium reveal" data-repo="Scorium-lang/scorium-rs">
<a class="project-visual project-visual-scorium" href="https://github.com/Scorium-lang" 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>
<span class="scorium-titlebar"><b>SCORIUM</b><span>examples/services.scor</span><i>.scor</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>
@ -239,16 +240,16 @@
<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>
<div class="project-meta"><span>Configuration language</span><span>Rust / TypeScript / Go</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>
<p>A readable configuration language with typed values, conditions, loops, and precise diagnostics. Native Rust, TypeScript, and Go implementations share a specification and conformance fixtures; the Rust runtime adds sandboxed Lua for applications that need it.</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>
<li><strong>Typed</strong><span>colors + durations</span></li>
<li><strong>Shared</strong><span>language spec</span></li>
<li><strong>Native</strong><span>host-language APIs</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">&nearr;</span></a>
<a class="text-link" href="https://github.com/Scorium-lang" target="_blank" rel="noreferrer">Explore the implementations <span aria-hidden="true">&nearr;</span></a>
</div>
</article>
@ -306,7 +307,7 @@
<div>
<p class="project-kicker">Framework / Java</p>
<h3>FIW Bosses</h3>
<p>A JSON-driven, multi-loader boss framework with HP phases, custom minions, dialogue, loot, hot reload, and more than 50 configurable abilities.</p>
<p>A JSON-driven, multi-loader boss framework with HP phases, custom minions, dialogue, loot, hot reload, and configurable abilities.</p>
</div>
<div class="card-footer">
<div class="tag-row"><span>Fabric</span><span>NeoForge</span><span>Forge</span></div>
@ -329,26 +330,26 @@
</div>
<div class="project-list">
<article class="project-row reveal" data-repo="Fiw-Anticheat">
<article class="project-row reveal" data-repo="Prismarine">
<span class="row-index">03</span>
<div><p>Security tooling</p><h3>FIW AntiCheat</h3></div>
<p>Nonce challenges, mod signatures, resource-pack auditing, and honest limits across six loader/version targets.</p>
<span class="row-tech">Java / SHA-256</span>
<a href="https://github.com/Fi3w0/Fiw-Anticheat" target="_blank" rel="noreferrer" aria-label="View FIW AntiCheat on GitHub">&nearr;</a>
<div><p>Native launcher</p><h3>Prismarine</h3></div>
<p>A Qt interface for Prism Launcher: organise instances, browse mods and modpacks, follow live game logs, and diagnose crashes. Requires Prism Launcher.</p>
<span class="row-tech">Qt / C++ / QML</span>
<a href="https://github.com/Fi3w0/Prismarine" target="_blank" rel="noreferrer" aria-label="View Prismarine on GitHub">&nearr;</a>
</article>
<article class="project-row reveal" data-repo="Fiw-Graves">
<article class="project-row reveal" data-repo="FiwNode">
<span class="row-index">04</span>
<div><p>Reliability</p><h3>FIW Graves</h3></div>
<p>Packet-only grave markers, world-native persistence, rollback history, and a second loot safety net.</p>
<span class="row-tech">Java / Packets</span>
<a href="https://github.com/Fi3w0/Fiw-Graves" target="_blank" rel="noreferrer" aria-label="View FIW Graves on GitHub">&nearr;</a>
<div><p>Linux audio</p><h3>FiwNode</h3></div>
<p>Mix your voice and local sounds into one PipeWire microphone, with separate controls for what your chat hears and what you hear. Desktop app, CLI, and independent audio daemon.</p>
<span class="row-tech">Rust / Qt / PipeWire</span>
<a href="https://github.com/Fi3w0/FiwNode" target="_blank" rel="noreferrer" aria-label="View FiwNode on GitHub">&nearr;</a>
</article>
<article class="project-row reveal">
<article class="project-row reveal" data-repo="Nereus">
<span class="row-index">05</span>
<div><p>Server operations</p><h3>FIW Admin Utilities</h3></div>
<p>Maintenance mode, lag watchdog, entity sweeping, vanish, freeze, and item bans in one lightweight admin mod.</p>
<span class="row-tech">Fabric / NeoForge</span>
<a href="https://modrinth.com/mod/fiw-admin-utilitys" target="_blank" rel="noreferrer" aria-label="View FIW Admin Utilities on Modrinth">&nearr;</a>
<div><p>Infrastructure</p><h3>Nereus</h3></div>
<p>A Go telemetry API on Kubernetes, with Ansible provisioning, joined-up logs, metrics and traces, and blue-green deployments that abort a bad release before promotion.</p>
<span class="row-tech">Go / k3s / Ansible</span>
<a href="https://github.com/Fi3w0/Nereus" target="_blank" rel="noreferrer" aria-label="View Nereus on GitHub">&nearr;</a>
</article>
</div>
@ -361,34 +362,23 @@
<a class="text-link" href="https://modrinth.com/user/Fi3w0" target="_blank" rel="noreferrer">View creator profile <span aria-hidden="true">&nearr;</span></a>
</header>
<p class="modrinth-summary">Every mod above (plus Warp, Graves, and Tickwatch from the archive) ships through one Modrinth profile — same JSON-config philosophy, same hot reload, across Fabric, Forge, and NeoForge.</p>
<div class="modrinth-stats">
<div><strong>7</strong><span>mods published</span></div>
<div><strong>3</strong><span>loaders supported</span></div>
<div><strong>1</strong><span>creator profile</span></div>
</div>
<p class="modrinth-summary">Bosses, custom items, graves, warps, and admin tools for Minecraft servers. Installation, supported loaders, and release details live on Modrinth.</p>
</div>
</section>
<section class="section archive-section" aria-labelledby="archive-title">
<header class="section-heading section-heading-row reveal">
<div><p class="section-index">03 / Repositories</p><h2 id="archive-title">Other things I made.</h2></div>
<div><p class="section-index">03 / Repositories</p><h2 id="archive-title">More from the workbench.</h2></div>
<a class="text-link" href="https://github.com/Fi3w0?tab=repositories" target="_blank" rel="noreferrer">All repositories <span aria-hidden="true">&nearr;</span></a>
</header>
<div class="archive-grid">
<a class="archive-item reveal" href="https://github.com/Fi3w0/Fiw-Warp" target="_blank" rel="noreferrer"><span>Fiw Warp</span><small>Kotlin / server teleports</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Fiw-Clock" target="_blank" rel="noreferrer"><span>Tickwatch</span><small>Crash-safe player stats</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Firefox-Privacy" target="_blank" rel="noreferrer"><span>Firefox Privacy</span><small>Cross-platform hardening</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/HomePage" target="_blank" rel="noreferrer"><span>HomePage</span><small>Live homelab dashboard</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/FiwStoryMod" target="_blank" rel="noreferrer"><span>Fiw Story Mod</span><small>SMP artifacts + lore</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Naoya-Mod" target="_blank" rel="noreferrer"><span>Naoya Mod</span><small>Fabric ability system</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/DevourerPlugin" target="_blank" rel="noreferrer"><span>Devourer Plugin</span><small>Asymmetric Paper PvP</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Fi3w0-Hyprland" target="_blank" rel="noreferrer"><span>Fi3w0 Hyprland</span><small>The original daily dots</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Fiw-ModChecklist" target="_blank" rel="noreferrer"><span>Fiw ModChecklist</span><small>Whitelist prototype</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/VeeBotV4.2" target="_blank" rel="noreferrer"><span>VeeBot V4.2</span><small>Python / Discord</small><b>&nearr;</b></a>
<a class="archive-item reveal archive-item-paused" href="https://github.com/Fi3w0/Brota" target="_blank" rel="noreferrer"><span>Brota</span><small>Paused mobile prototype</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/AquaTTY" target="_blank" rel="noreferrer"><span>AquaTTY</span><small>Go / ASCII liquid physics</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/HomePage" target="_blank" rel="noreferrer"><span>HomePage</span><small>Self-hosted homelab dashboard</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Fiw-Anticheat" target="_blank" rel="noreferrer"><span>FIW AntiCheat</span><small>Modpack verification</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Fiw-Graves" target="_blank" rel="noreferrer"><span>FIW Graves</span><small>Persistent loot recovery</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/fiw-admin-utilitys" target="_blank" rel="noreferrer"><span>FIW Admin Utilities</span><small>Server maintenance + moderation</small><b>&nearr;</b></a>
<a class="archive-item reveal" href="https://github.com/Fi3w0/Firefox-Privacy" target="_blank" rel="noreferrer"><span>Firefox Privacy</span><small>Cross-platform browser hardening</small><b>&nearr;</b></a>
</div>
</section>
@ -396,19 +386,19 @@
<header class="section-heading reveal">
<p class="section-index">04 / Tools</p>
<h2 id="stack-title">What is usually open in my terminal.</h2>
<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>
<p class="stack-intro">Kitty on a MacBook Air M4 is the main console, SSH'd out to the Arch desktop, ThinkPad T14 running Gentoo or Arch, and Ubuntu server for everything else.</p>
</header>
<div class="stack-layout">
<article class="stack-card stack-card-main reveal">
<p class="stack-label">Home turf</p>
<h3>Linux / systems</h3>
<div class="stack-tags"><span class="tag-strong">Arch Linux</span><span class="tag-strong">Bash</span><span>Ubuntu Server</span><span>systemd</span><span>SSH</span><span>Networking</span></div>
<div class="stack-tags"><span class="tag-strong">Gentoo</span><span class="tag-strong">Arch Linux</span><span>Bash</span><span>Ubuntu Server</span><span>systemd</span><span>SSH</span><span>Networking</span></div>
</article>
<article class="stack-card reveal">
<p class="stack-label">Ship + operate</p>
<h3>Infrastructure</h3>
<div class="stack-tags"><span class="tag-strong">Docker</span><span class="tag-strong">Compose</span><span>Traefik</span><span>Portainer</span><span>GitHub Actions</span><span>Backups</span></div>
<div class="stack-tags"><span class="tag-strong">Docker</span><span class="tag-strong">Compose</span><span>Traefik</span><span>Kubernetes / k3s</span><span>Ansible</span><span>GitHub Actions</span><span>Backups</span></div>
</article>
<article class="stack-card reveal">
<p class="stack-label">Build surface</p>
@ -416,9 +406,9 @@
<div class="stack-tags"><span class="tag-strong">Go</span><span class="tag-strong">Lua</span><span>Rust / AI-assisted</span><span>Java</span><span>Kotlin</span><span>TypeScript</span><span>QML</span><span>JSON</span></div>
</article>
<article class="stack-card stack-card-learning reveal">
<p class="stack-label">Learning next</p>
<h3>Kubernetes, Terraform, Ansible</h3>
<p>I am learning these when a real problem gives me a reason, not just to collect more logos.</p>
<p class="stack-label">Deploy + recover</p>
<h3>Observe it. Roll it back.</h3>
<p>Prometheus, Grafana, OpenTelemetry, and Argo Rollouts: see what broke and keep a bad release away from real traffic.</p>
</article>
</div>
</section>

101
js/commissions.js Normal file
View file

@ -0,0 +1,101 @@
const commissionPage = document.querySelector(".commissions-page");
if (commissionPage) {
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const finePointer = window.matchMedia("(hover: hover) and (pointer: fine)").matches;
if (finePointer && !reduceMotion) {
const hero = document.querySelector(".commission-hero-new");
window.addEventListener("pointermove", (event) => {
commissionPage.style.setProperty("--mx", `${event.clientX}px`);
commissionPage.style.setProperty("--my", `${event.clientY}px`);
if (hero) {
const bounds = hero.getBoundingClientRect();
hero.style.setProperty("--hx", `${event.clientX - bounds.left}px`);
hero.style.setProperty("--hy", `${event.clientY - bounds.top}px`);
}
}, { passive: true });
}
const clock = document.querySelector(".scope-clock");
if (clock) {
const startedAt = Date.now();
const updateClock = () => {
const seconds = Math.floor((Date.now() - startedAt) / 1000);
const hours = String(Math.floor(seconds / 3600)).padStart(2, "0");
const minutes = String(Math.floor((seconds % 3600) / 60)).padStart(2, "0");
const remainder = String(seconds % 60).padStart(2, "0");
clock.textContent = `${hours}:${minutes}:${remainder}`;
};
updateClock();
window.setInterval(updateClock, 1000);
}
const rotatingWord = document.querySelector(".commission-word");
if (rotatingWord && !reduceMotion) {
const words = (rotatingWord.dataset.words || "weird").split(",");
let wordIndex = 0;
window.setInterval(() => {
rotatingWord.classList.add("is-changing");
window.setTimeout(() => {
wordIndex = (wordIndex + 1) % words.length;
rotatingWord.textContent = words[wordIndex];
}, 110);
window.setTimeout(() => rotatingWord.classList.remove("is-changing"), 280);
}, 2800);
}
const filterButtons = document.querySelectorAll("[data-price-filter]");
const priceGroups = document.querySelectorAll("[data-price-category]");
const visibleCount = document.querySelector("#price-visible-count");
filterButtons.forEach((button) => {
button.addEventListener("click", () => {
const selected = button.dataset.priceFilter;
let count = 0;
filterButtons.forEach((candidate) => {
const active = candidate === button;
candidate.classList.toggle("is-active", active);
candidate.setAttribute("aria-pressed", String(active));
});
priceGroups.forEach((group) => {
const visible = selected === "all" || group.dataset.priceCategory === selected;
group.hidden = !visible;
if (visible) count += group.querySelectorAll(".price-list > div").length;
});
if (visibleCount) visibleCount.textContent = String(count);
});
});
const copyBrief = document.querySelector("#copy-brief");
const copyStatus = document.querySelector("#copy-status");
const brief = [
"Hi Alex — I have a commission request.",
"",
"What I want to build or fix:",
"Current system / versions:",
"What happens now:",
"What I want to happen:",
"What counts as done:",
"Budget range:",
"Deadline, if any:",
"Useful logs / screenshots:",
].join("\n");
if (copyBrief && copyStatus) {
copyBrief.addEventListener("click", async () => {
try {
await navigator.clipboard.writeText(brief);
copyStatus.textContent = "Commission brief copied. Paste it into an email, Telegram, or Discord.";
} catch {
copyStatus.textContent = "Clipboard access was blocked. Email me the goal, setup, symptoms, and any logs.";
}
window.setTimeout(() => { copyStatus.textContent = ""; }, 4000);
});
}
}

View file

@ -3,10 +3,11 @@ const path = require('path');
module.exports = {
entry: {
app: './js/app.js',
commissions: './js/commissions.js',
},
output: {
path: path.resolve(__dirname, 'dist'),
clean: true,
filename: './js/app.js',
filename: './js/[name].js',
},
};

View file

@ -10,6 +10,11 @@ module.exports = merge(common, {
template: './index.html',
inject: false,
}),
new HtmlWebpackPlugin({
template: './commissions/index.html',
filename: 'commissions/index.html',
inject: false,
}),
new CopyPlugin({
patterns: [
{ from: 'img', to: 'img' },