Add interactive homelab radar and redesign Projects & Hardware pages

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
pepaskopec-blip 2026-10-14 22:00:17 +02:00
commit e30d472d61
7 changed files with 2374 additions and 238 deletions

View file

@ -28,7 +28,7 @@
} }
})(); })();
</script> </script>
<link rel="stylesheet" href="style.css?v=20261018"> <link rel="stylesheet" href="style.css?v=20261022">
</head> </head>
<body> <body>
<a class="skip-link" href="#main">Skip to content</a> <a class="skip-link" href="#main">Skip to content</a>

View file

@ -34,7 +34,7 @@
} }
})(); })();
</script> </script>
<link rel="stylesheet" href="style.css?v=20261018"> <link rel="stylesheet" href="style.css?v=20261022">
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@context": "https://schema.org", "@context": "https://schema.org",
@ -82,70 +82,362 @@
<li><span aria-current="page">Hardware</span></li> <li><span aria-current="page">Hardware</span></li>
</ol> </ol>
</nav> </nav>
<div class="projects-hero-top">
<div>
<h1>Hardware</h1> <h1>Hardware</h1>
<p>The machines I use every day — desktop build, daily driver laptop, Proxmox server, and phone.</p> <p>The machines I use every day — desktop build, daily driver laptop, Proxmox server, and phone.</p>
</div>
<div class="project-filters" role="toolbar" aria-label="Filter hardware">
<button class="filter-chip is-active" type="button" data-hw-filter="all">All <span>4</span></button>
<button class="filter-chip" type="button" data-hw-filter="desktop">Desktop</button>
<button class="filter-chip" type="button" data-hw-filter="laptop">Laptop</button>
<button class="filter-chip" type="button" data-hw-filter="server">Server</button>
<button class="filter-chip" type="button" data-hw-filter="mobile">Mobile</button>
</div>
</div>
<ul class="hw-overview" aria-label="Combined hardware totals">
<li>
<span class="hw-overview-label">Total cores</span>
<strong>34 cores</strong>
<span class="hw-overview-sub">48 threads across 4 devices</span>
</li>
<li>
<span class="hw-overview-label">Combined memory</span>
<strong>78 GB RAM</strong>
<span class="hw-overview-sub">ECC DDR4 + DDR4 + Unified + LPDDR4X</span>
</li>
<li>
<span class="hw-overview-label">Total storage</span>
<strong>~2.7 TB</strong>
<span class="hw-overview-sub">NVMe SSDs + Proxmox HDD array</span>
</li>
<li>
<span class="hw-overview-label">Operating systems</span>
<strong>4 platforms</strong>
<span class="hw-overview-sub">Artix Linux · macOS · Proxmox · Android</span>
</li>
</ul>
</header> </header>
<section class="section" aria-labelledby="machines-title"> <section class="section projects-section" aria-labelledby="machines-title">
<div class="wrap reveal"> <div class="wrap reveal">
<p class="section-label">Gear</p> <p class="section-label">Gear</p>
<h2 id="machines-title">My machines</h2> <h2 id="machines-title">My machines</h2>
<p class="section-lead">Full component breakdown with key specs from each part.</p> <p class="section-lead">Full component breakdown with key specs from each part.</p>
<ul class="stack-list">
<li class="stack-item"> <ul class="hw-grid">
<!-- 1. Desktop -->
<li class="hw-card" data-hw-category="desktop">
<div class="hw-card-top">
<div class="hw-card-intro">
<div class="project-card-meta">
<span class="tag">Desktop</span> <span class="tag">Desktop</span>
<span class="project-status">Artix Linux · SwayFX</span>
</div>
<h3>Custom build · 2022</h3> <h3>Custom build · 2022</h3>
<p>Main PC running Artix Linux with SwayFX wm — assembled in 2022.</p> <p>Main PC running Artix Linux with SwayFX wm — assembled in 2022.</p>
<dl class="spec-list"> <ul class="project-pills" aria-label="Key desktop specs">
<div><dt>CPU</dt><dd>AMD Ryzen 5 5600 — Zen 3 / Vermeer, 6C/12T, 3.5–4.4 GHz, 32 MB L3, 65 W TDP, AM4, unlocked</dd></div> <li>Ryzen 5 5600</li>
<div><dt>GPU</dt><dd>XFX Speedster SWFT 309 RX 6700 — RDNA 2, 2304 SP, 10 GB GDDR6 @ 16 Gbps (160-bit, 320 GB/s), boost up to ~2425 MHz, triple-fan, HDMI + 3× DP</dd></div> <li>RX 6700 10 GB</li>
<div><dt>RAM</dt><dd>Kingston FURY Beast RGB — 16 GB DDR4-3200 · CL16 (16-18-18) · ~1.35 V · Intel XMP 2.0</dd></div> <li>16 GB DDR4-3200</li>
<div><dt>Storage</dt><dd>WD Blue SN570 1 TB — M.2 2280 NVMe, PCIe 3.0 ×4, up to ~3500/3000 MB/s read/write, TLC, ~600 TBW, 5-year warranty</dd></div> <li>1 TB NVMe</li>
<div><dt>Board</dt><dd>ASUS TUF Gaming B550-Plus — AM4 ATX, B550, 8+2 DrMOS VRM, 4× DDR4 (max 128 GB), PCIe 4.0 ×16, dual M.2, 2.5 GbE, USB 3.2 Gen 2</dd></div> <li>600 W 80+ Bronze</li>
<div><dt>PSU</dt><dd>SilentiumPC Vero M3 Bronze 600 W — ATX, 80 PLUS Bronze (~90% @ 50% load), semi-modular, single +12 V rail up to ~588 W</dd></div> </ul>
<div><dt>Cooler</dt><dd>SilentiumPC Fera 5 — tower air cooler, 4× heatpipes, Fluctus 120 PWM (300–1800 RPM), rated ~220 W TDP</dd></div> </div>
<div><dt>Case</dt><dd>SilentiumPC Signum SG1V EVO TG ARGB — ATX mid-tower (~447×216×413 mm), tempered glass, 4× Stella HP ARGB 120 mm included, GPU clearance ~325 mm</dd></div> <div class="hw-visual" aria-hidden="true">
<div class="mock-fetch">
<div class="mock-fetch-bar">
<span class="mock-dots"><i></i><i></i><i></i></span>
<span>josef@artix ~ fastfetch</span>
</div>
<div class="mock-fetch-body">
<pre class="fetch-ascii"> /\
/ \
/ /\ \
/ / \ \
\/ \/</pre>
<div class="fetch-lines">
<div><b>OS</b> Artix Linux x86_64</div>
<div><b>WM</b> SwayFX (Wayland)</div>
<div><b>CPU</b> Ryzen 5 5600 (12) @ 4.4GHz</div>
<div><b>GPU</b> Radeon RX 6700 [10GB]</div>
<div><b>MEM</b> 3.1 GiB / 16.0 GiB DDR4</div>
<div class="fetch-palette"><i></i><i></i><i></i><i></i><i></i><i></i></div>
</div>
</div>
</div>
</div>
</div>
<dl class="hw-spec-grid">
<div>
<dt>CPU</dt>
<dd><strong>AMD Ryzen 5 5600</strong>Zen 3 / Vermeer, 6C/12T, 3.5–4.4 GHz, 32 MB L3, 65 W TDP, AM4, unlocked</dd>
</div>
<div>
<dt>GPU</dt>
<dd><strong>XFX Speedster SWFT 309 RX 6700</strong>RDNA 2, 2304 SP, 10 GB GDDR6 @ 16 Gbps (160-bit, 320 GB/s), boost up to ~2425 MHz, triple-fan, HDMI + 3× DP</dd>
</div>
<div>
<dt>RAM</dt>
<dd><strong>Kingston FURY Beast RGB — 16 GB</strong>DDR4-3200 · CL16 (16-18-18) · ~1.35 V · Intel XMP 2.0</dd>
</div>
<div>
<dt>Storage</dt>
<dd><strong>WD Blue SN570 1 TB</strong>M.2 2280 NVMe, PCIe 3.0 ×4, up to ~3500/3000 MB/s read/write, TLC, ~600 TBW, 5-year warranty</dd>
</div>
<div>
<dt>Board</dt>
<dd><strong>ASUS TUF Gaming B550-Plus</strong>AM4 ATX, B550, 8+2 DrMOS VRM, 4× DDR4 (max 128 GB), PCIe 4.0 ×16, dual M.2, 2.5 GbE, USB 3.2 Gen 2</dd>
</div>
<div>
<dt>PSU</dt>
<dd><strong>SilentiumPC Vero M3 Bronze 600 W</strong>ATX, 80 PLUS Bronze (~90% @ 50% load), semi-modular, single +12 V rail up to ~588 W</dd>
</div>
<div>
<dt>Cooler</dt>
<dd><strong>SilentiumPC Fera 5</strong>Tower air cooler, 4× heatpipes, Fluctus 120 PWM (300–1800 RPM), rated ~220 W TDP</dd>
</div>
<div>
<dt>Case</dt>
<dd><strong>SilentiumPC Signum SG1V EVO TG ARGB</strong>ATX mid-tower (~447×216×413 mm), tempered glass, 4× Stella HP ARGB 120 mm included, GPU clearance ~325 mm</dd>
</div>
</dl> </dl>
</li> </li>
<li class="stack-item">
<!-- 2. Laptop -->
<li class="hw-card" data-hw-category="laptop">
<div class="hw-card-top">
<div class="hw-card-intro">
<div class="project-card-meta">
<span class="tag">Laptop</span> <span class="tag">Laptop</span>
<span class="project-status">macOS · Daily driver</span>
</div>
<h3>MacBook Air 13″ · M5 · base</h3> <h3>MacBook Air 13″ · M5 · base</h3>
<p>Portable machine for school and on-the-go work.</p> <p>Portable machine for school and on-the-go work.</p>
<dl class="spec-list"> <ul class="project-pills" aria-label="Key laptop specs">
<div><dt>Chip</dt><dd>Apple M5 — 10-core CPU (4 performance + 6 efficiency), 8-core GPU, 16-core Neural Engine, ~153 GB/s memory bandwidth</dd></div> <li>Apple M5 (10C)</li>
<div><dt>Memory</dt><dd>16 GB unified memory (base)</dd></div> <li>16 GB Unified</li>
<div><dt>Storage</dt><dd>512 GB SSD (base; Apple claims ~2× faster vs previous Air gen)</dd></div> <li>512 GB SSD</li>
<div><dt>Display</dt><dd>13.6″ Liquid Retina IPS — 2560×1664 @ 224 ppi, 500 nits, 60 Hz, P3</dd></div> <li>13.6″ Liquid Retina</li>
<div><dt>Battery</dt><dd>Up to ~18 h video streaming / ~15 h wireless web (Apple rating)</dd></div> <li>Wi-Fi 7 · 1.24 kg</li>
<div><dt>I/O</dt><dd>2× Thunderbolt 4 · MagSafe · 3.5 mm · Wi-Fi 7 (N1) · Bluetooth 6 · ~1.24 kg</dd></div> </ul>
</div>
<div class="hw-visual" aria-hidden="true">
<div class="mock-soc">
<div class="mock-soc-die">
<span class="soc-label">APPLE SILICON</span>
<strong class="soc-title">M5</strong>
<div class="soc-blocks">
<span>4P + 6E CPU</span>
<span>8-Core GPU</span>
<span>16-Core ANE</span>
<span>153 GB/s UMA</span>
</div>
</div>
<div class="mock-soc-side">
<div class="soc-stat">
<span>Display</span>
<strong>2560 × 1664 · P3</strong>
</div>
<div class="soc-stat">
<span>Battery</span>
<strong>Up to 18 h</strong>
</div>
<div class="soc-stat">
<span>Wireless</span>
<strong>Wi-Fi 7 (N1) · BT 6</strong>
</div>
</div>
</div>
</div>
</div>
<dl class="hw-spec-grid">
<div>
<dt>Chip</dt>
<dd><strong>Apple M5</strong>10-core CPU (4 performance + 6 efficiency), 8-core GPU, 16-core Neural Engine, ~153 GB/s memory bandwidth</dd>
</div>
<div>
<dt>Memory</dt>
<dd><strong>16 GB unified memory</strong>Base configuration</dd>
</div>
<div>
<dt>Storage</dt>
<dd><strong>512 GB SSD</strong>Base; Apple claims ~2× faster vs previous Air gen</dd>
</div>
<div>
<dt>Display</dt>
<dd><strong>13.6″ Liquid Retina IPS</strong>2560×1664 @ 224 ppi, 500 nits, 60 Hz, P3 wide color</dd>
</div>
<div>
<dt>Battery</dt>
<dd><strong>Up to ~18 h video / ~15 h web</strong>Apple wireless web &amp; streaming rating</dd>
</div>
<div>
<dt>I/O</dt>
<dd><strong>2× Thunderbolt 4 · MagSafe · 3.5 mm</strong>Wi-Fi 7 (N1) · Bluetooth 6 · ~1.24 kg</dd>
</div>
</dl> </dl>
</li> </li>
<li class="stack-item">
<!-- 3. Server -->
<li class="hw-card" data-hw-category="server">
<div class="hw-card-top">
<div class="hw-card-intro">
<div class="project-card-meta">
<span class="tag">Server</span> <span class="tag">Server</span>
<span class="project-status">Proxmox VE · Headless 24/7</span>
</div>
<h3>Proxmox server</h3> <h3>Proxmox server</h3>
<p>Home lab box for VMs and containers — usually runs headless.If you need hosting, VM, VPS or a wesbsite dm me on IG or sent me an email. </p> <p>Home lab box for VMs and containers — usually runs headless. If you need hosting, VM, VPS or a website dm me on IG or send me an email.</p>
<dl class="spec-list"> <ul class="project-pills" aria-label="Key server specs">
<div><dt>CPU</dt><dd>Intel Xeon E5-2630L v4 — Broadwell-EP, 14 nm, 10C/20T, 1.8–2.9 GHz, 25 MB L3, 55 W TDP, LGA 2011-3, DDR4 ECC</dd></div> <li>Xeon E5-2630L v4</li>
<div><dt>GPU</dt><dd>NVIDIA GeForce GT 440 — Fermi GF108-class entry GPU (~96 CUDA, ~65 W); used only for BIOS, then headless</dd></div> <li>10C / 20T · 55 W</li>
<div><dt>RAM</dt><dd>40 GB ECC registered DDR4 (RDIMM) — dual-channel on this board, CPU supports DDR4-1600/1866/2133</dd></div> <li>40 GB ECC RDIMM</li>
<div><dt>Storage</dt><dd>Samsung 970 EVO OEM 256 GB NVMe (PCIe 3.0 ×4, Phoenix controller, ~3400/1500 MB/s class) · WD 2.5″ 512 GB HDD · WD 3.5″ 320 GB HDD</dd></div> <li>NVMe + 2× WD HDD</li>
<div><dt>Board</dt><dd>Huananzhi X99-P4F — mATX (215×185 mm), LGA 2011-3, 4× DDR4 (max 128 GB), 2× M.2 NVMe PCIe 3.0 ×4, 4× SATA 3 Gb/s, 1× PCIe 3.0 ×16, Realtek GbE, 6-phase VRM</dd></div> </ul>
<div><dt>PSU</dt><dd>Corsair CX450 — 450 W ATX, 80 PLUS Bronze, non-modular, ~100k h MTBF rating</dd></div> <div class="hw-hosting-cta">
<div><dt>Cooler</dt><dd>Endorfy Fera 5 — same Synergy Cooling design as SilentiumPC Fera 5; 4× heatpipes, 120 mm PWM, ~220 W TDP class</dd></div> <span>Need a VM, VPS or web hosting?</span>
<div><dt>Case</dt><dd>mATX case reused from an old office PC</dd></div> <a href="mailto:josef@skopec.dev">josef@skopec.dev →</a>
<a href="https://www.instagram.com/skopcaak1" target="_blank" rel="noopener noreferrer">@skopcaak1 →</a>
</div>
</div>
<div class="hw-visual" aria-hidden="true">
<div class="mock-server-unit">
<div class="server-unit-top">
<span class="mock-status-dot"></span>
<span class="server-unit-name">PROXMOX NODE · LGA 2011-3</span>
<span class="server-unit-tdp">55 W TDP</span>
</div>
<div class="server-bays">
<div class="server-bay">
<span class="bay-type">NVMe</span>
<strong>970 EVO 256 GB</strong>
<span class="bay-sub">PCIe 3.0 ×4 · OS &amp; VMs</span>
</div>
<div class="server-bay">
<span class="bay-type">SATA 2.5″</span>
<strong>WD 512 GB</strong>
<span class="bay-sub">Bulk storage</span>
</div>
<div class="server-bay">
<span class="bay-type">SATA 3.5″</span>
<strong>WD 320 GB</strong>
<span class="bay-sub">Backups &amp; media</span>
</div>
</div>
<div class="server-mem-strip">
<span>RAM</span>
<strong>40 GB DDR4 ECC Registered (RDIMM)</strong>
</div>
</div>
</div>
</div>
<dl class="hw-spec-grid">
<div>
<dt>CPU</dt>
<dd><strong>Intel Xeon E5-2630L v4</strong>Broadwell-EP, 14 nm, 10C/20T, 1.8–2.9 GHz, 25 MB L3, 55 W TDP, LGA 2011-3, DDR4 ECC</dd>
</div>
<div>
<dt>GPU</dt>
<dd><strong>NVIDIA GeForce GT 440</strong>Fermi GF108-class entry GPU (~96 CUDA, ~65 W); used only for BIOS, then headless</dd>
</div>
<div>
<dt>RAM</dt>
<dd><strong>40 GB ECC registered DDR4 (RDIMM)</strong>Dual-channel on this board, CPU supports DDR4-1600/1866/2133</dd>
</div>
<div>
<dt>Storage</dt>
<dd><strong>Samsung 970 EVO OEM 256 GB NVMe + 2× WD HDD</strong>PCIe 3.0 ×4, Phoenix controller, ~3400/1500 MB/s class · WD 2.5″ 512 GB HDD · WD 3.5″ 320 GB HDD</dd>
</div>
<div>
<dt>Board</dt>
<dd><strong>Huananzhi X99-P4F</strong>mATX (215×185 mm), LGA 2011-3, 4× DDR4 (max 128 GB), 2× M.2 NVMe PCIe 3.0 ×4, 4× SATA 3 Gb/s, 1× PCIe 3.0 ×16, Realtek GbE, 6-phase VRM</dd>
</div>
<div>
<dt>PSU</dt>
<dd><strong>Corsair CX450 — 450 W</strong>ATX, 80 PLUS Bronze, non-modular, ~100k h MTBF rating</dd>
</div>
<div>
<dt>Cooler</dt>
<dd><strong>Endorfy Fera 5</strong>Same Synergy Cooling design as SilentiumPC Fera 5; 4× heatpipes, 120 mm PWM, ~220 W TDP class</dd>
</div>
<div>
<dt>Case</dt>
<dd><strong>Reused mATX office chassis</strong>mATX case reused from an old office PC</dd>
</div>
</dl> </dl>
</li> </li>
<li class="stack-item">
<!-- 4. Mobile -->
<li class="hw-card" data-hw-category="mobile">
<div class="hw-card-top">
<div class="hw-card-intro">
<div class="project-card-meta">
<span class="tag">Mobile</span> <span class="tag">Mobile</span>
<span class="project-status">Daily driver</span>
</div>
<h3>Xiaomi Redmi Note 11 Pro 5G · 6/128</h3> <h3>Xiaomi Redmi Note 11 Pro 5G · 6/128</h3>
<p>Daily driver phone.</p> <p>Daily driver phone.</p>
<dl class="spec-list"> <ul class="project-pills" aria-label="Key phone specs">
<div><dt>Chipset</dt><dd>Qualcomm Snapdragon 695 5G (SM6375, 6 nm) — 2× Kryo 660 @ up to 2.2 GHz + 6× @ 1.7 GHz · Adreno 619</dd></div> <li>Snapdragon 695 5G</li>
<div><dt>Display</dt><dd>6.67″ Super AMOLED DotDisplay — 1080×2400, 120 Hz, ~395 ppi, DCI-P3, HBM ~700 nits / peak ~1200 nits, Gorilla Glass 5</dd></div> <li>6.67″ 120 Hz AMOLED</li>
<div><dt>Memory</dt><dd>6 GB LPDDR4X + 128 GB UFS 2.2 · microSD expandable (shared SIM slot)</dd></div> <li>108 MP Main Cam</li>
<div><dt>Camera</dt><dd>108 MP f/1.9 wide (1/1.52″) + 8 MP ultrawide 118° + 2 MP macro · 16 MP front</dd></div> <li>5000 mAh · 67 W</li>
<div><dt>Battery</dt><dd>5000 mAh · 67 W wired turbo charge (USB-C)</dd></div> </ul>
<div><dt>Body</dt><dd>~164.2 × 76.1 × 8.1 mm · ~202 g · dual speakers · 3.5 mm jack · side fingerprint · Bluetooth 5.1 / Wi-Fi 5</dd></div> </div>
<div class="hw-visual" aria-hidden="true">
<div class="mock-phone">
<div class="mock-phone-cam">
<span class="cam-lens cam-main"><i></i></span>
<div class="cam-aux">
<span class="cam-lens"></span>
<span class="cam-lens"></span>
</div>
<span class="cam-tag">108 MP · 1/1.52″</span>
</div>
<div class="mock-phone-specs">
<div>
<span>Panel</span>
<strong>120 Hz Super AMOLED</strong>
</div>
<div>
<span>Peak Brightness</span>
<strong>1200 nits · DCI-P3</strong>
</div>
<div>
<span>Charging</span>
<strong>67 W Turbo · 5000 mAh</strong>
</div>
</div>
</div>
</div>
</div>
<dl class="hw-spec-grid">
<div>
<dt>Chipset</dt>
<dd><strong>Qualcomm Snapdragon 695 5G (SM6375, 6 nm)</strong>2× Kryo 660 @ up to 2.2 GHz + 6× @ 1.7 GHz · Adreno 619</dd>
</div>
<div>
<dt>Display</dt>
<dd><strong>6.67″ Super AMOLED DotDisplay</strong>1080×2400, 120 Hz, ~395 ppi, DCI-P3, HBM ~700 nits / peak ~1200 nits, Gorilla Glass 5</dd>
</div>
<div>
<dt>Memory</dt>
<dd><strong>6 GB LPDDR4X + 128 GB UFS 2.2</strong>microSD expandable (shared SIM slot)</dd>
</div>
<div>
<dt>Camera</dt>
<dd><strong>108 MP f/1.9 wide (1/1.52″)</strong>+ 8 MP ultrawide 118° + 2 MP macro · 16 MP front</dd>
</div>
<div>
<dt>Battery</dt>
<dd><strong>5000 mAh</strong>67 W wired turbo charge (USB-C)</dd>
</div>
<div>
<dt>Body</dt>
<dd><strong>~164.2 × 76.1 × 8.1 mm · ~202 g</strong>Dual speakers · 3.5 mm jack · side fingerprint · Bluetooth 5.1 / Wi-Fi 5</dd>
</div>
</dl> </dl>
</li> </li>
</ul> </ul>
@ -165,6 +457,6 @@
</div> </div>
</footer> </footer>
<script src="script.js" defer></script> <script src="script.js?v=20261022" defer></script>
</body> </body>
</html> </html>

View file

@ -34,7 +34,7 @@
} }
})(); })();
</script> </script>
<link rel="stylesheet" href="style.css?v=20261018"> <link rel="stylesheet" href="style.css?v=20261022">
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@context": "https://schema.org", "@context": "https://schema.org",
@ -180,6 +180,65 @@
<a class="btn btn-ghost" href="projects.html">See projects</a> <a class="btn btn-ghost" href="projects.html">See projects</a>
</div> </div>
</div> </div>
<div class="hero-radar-wrap">
<div class="hero-radar" data-radar aria-label="Homelab services radar">
<div class="hero-radar-rings" aria-hidden="true">
<span></span>
<span></span>
</div>
<a class="radar-node radar-node-core" style="--x: 50%; --y: 50%;" href="https://10.0.0.200:8006" target="_blank" rel="noopener noreferrer"
data-name="Proxmox" data-role="Virtual machines · 10.0.0.200">
<span class="radar-dot"></span>
<span class="radar-label">Proxmox</span>
</a>
<a class="radar-node" style="--x: 64.6%; --y: 37.8%;" href="https://jelly.skopec.dev" target="_blank" rel="noopener noreferrer"
data-name="Jellyfin" data-role="Movies &amp; TV · jelly.skopec.dev">
<span class="radar-dot"></span>
<span class="radar-label">Jellyfin</span>
</a>
<a class="radar-node" style="--x: 31.1%; --y: 51.7%;" href="http://10.0.0.106:3000" target="_blank" rel="noopener noreferrer"
data-name="Ollama" data-role="Local AI · 10.0.0.106:3000">
<span class="radar-dot"></span>
<span class="radar-label">Ollama</span>
</a>
<a class="radar-node" style="--x: 54.9%; --y: 68.4%;" href="http://10.0.0.100/admin" target="_blank" rel="noopener noreferrer"
data-name="Pi-hole" data-role="DNS &amp; ad blocking · 10.0.0.100">
<span class="radar-dot"></span>
<span class="radar-label">Pi-hole</span>
</a>
<a class="radar-node" style="--x: 34%; --y: 20%;" href="http://10.0.0.111:22300" target="_blank" rel="noopener noreferrer"
data-name="Joplin" data-role="Notes · 10.0.0.111:22300">
<span class="radar-dot"></span>
<span class="radar-label">Joplin</span>
</a>
<a class="radar-node" style="--x: 83%; --y: 41.8%;" href="http://10.0.0.109:2283" target="_blank" rel="noopener noreferrer"
data-name="Immich" data-role="Photos · 10.0.0.109:2283">
<span class="radar-dot"></span>
<span class="radar-label">Immich</span>
</a>
<a class="radar-node" style="--x: 76%; --y: 71.9%;" href="http://10.0.0.103:4533" target="_blank" rel="noopener noreferrer"
data-name="Navidrome" data-role="Music · 10.0.0.103:4533">
<span class="radar-dot"></span>
<span class="radar-label">Navidrome</span>
</a>
<a class="radar-node" style="--x: 30.5%; --y: 77.9%;" href="http://10.0.0.104:8080" target="_blank" rel="noopener noreferrer"
data-name="SearXNG" data-role="Search · 10.0.0.104:8080">
<span class="radar-dot"></span>
<span class="radar-label">SearXNG</span>
</a>
<a class="radar-node" style="--x: 20.6%; --y: 33%;" href="http://10.0.0.107:3000" target="_blank" rel="noopener noreferrer"
data-name="ConvertX" data-role="File conversion · 10.0.0.107:3000">
<span class="radar-dot"></span>
<span class="radar-label">ConvertX</span>
</a>
</div>
<div class="radar-hud" data-radar-hud aria-live="polite">
<span class="radar-hud-dot" aria-hidden="true"></span>
<span class="radar-hud-name" data-radar-name>Homelab</span>
<span class="radar-hud-sep" aria-hidden="true">·</span>
<span class="radar-hud-role" data-radar-role>Proxmox VE — 9 nodes online</span>
</div>
</div>
</section> </section>
<section class="section feature" id="graduately" aria-labelledby="graduately-title"> <section class="section feature" id="graduately" aria-labelledby="graduately-title">
@ -448,6 +507,6 @@
</div> </div>
</footer> </footer>
<script src="script.js" defer></script> <script src="script.js?v=20261022" defer></script>
</body> </body>
</html> </html>

View file

@ -33,7 +33,7 @@
} }
})(); })();
</script> </script>
<link rel="stylesheet" href="style.css?v=20261018"> <link rel="stylesheet" href="style.css?v=20261022">
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@context": "https://schema.org", "@context": "https://schema.org",

View file

@ -34,7 +34,7 @@
} }
})(); })();
</script> </script>
<link rel="stylesheet" href="style.css?v=20261018"> <link rel="stylesheet" href="style.css?v=20261022">
<script type="application/ld+json"> <script type="application/ld+json">
{ {
"@context": "https://schema.org", "@context": "https://schema.org",
@ -82,63 +82,266 @@
<li><span aria-current="page">Projects</span></li> <li><span aria-current="page">Projects</span></li>
</ol> </ol>
</nav> </nav>
<div class="projects-hero-top">
<div>
<h1>Projects</h1> <h1>Projects</h1>
<p>Websites, the homelab, games, and school projects.</p> <p>Websites, the homelab, games, and school projects.</p>
</div>
<div class="project-filters" role="toolbar" aria-label="Filter projects">
<button class="filter-chip is-active" type="button" data-filter="all">All <span>6</span></button>
<button class="filter-chip" type="button" data-filter="apps">Apps &amp; Web</button>
<button class="filter-chip" type="button" data-filter="infra">Homelab &amp; Program</button>
<button class="filter-chip" type="button" data-filter="games">Games</button>
</div>
</div>
</header> </header>
<section class="section" aria-labelledby="projects-list-title"> <section class="section projects-section" aria-labelledby="projects-list-title">
<div class="wrap reveal"> <div class="wrap reveal">
<p class="section-label">Selected</p> <p class="section-label">Selected</p>
<h2 id="projects-list-title">Work</h2> <h2 id="projects-list-title">Work</h2>
<p class="section-lead">Things I’ve built or run — open a project for more.</p> <p class="section-lead">Things I’ve built or run — open a project for more.</p>
<ul class="project-list">
<li class="project-item"> <ul class="project-grid">
<span class="tag">HTML</span> <!-- Graduately (Featured full-width card) -->
<h3> <li class="project-card project-card-featured" data-category="apps">
<a href="https://github.com/pepaskopec-blip/skopec.dev" target="_blank" rel="noopener noreferrer">skopec.dev</a> <div class="project-card-body">
</h3> <div class="project-card-meta">
<p>This site, written in html, hosted on my Homelab/Proxmox server. It has caching enabled on Cloudflared so it does not have to load everything from the origin server.</p>
<a class="project-link" href="https://github.com/pepaskopec-blip/skopec.dev" target="_blank" rel="noopener noreferrer">View on GitHub →</a>
</li>
<li class="project-item">
<span class="tag">Proxmox</span>
<h3>
<a href="index.html#services">Homelab</a>
</h3>
<p>I just wanted a NAS server, but now I host almost everything. It uses Proxmox as the operating system and virtualization. I have more than ten LXC containers and VMs for my services. I also set up automatic restarts for maximum safety and updates. </p>
<a class="project-link" href="index.html#services">See services →</a>
</li>
<li class="project-item">
<span class="tag">Python</span>
<h3>
<a href="https://github.com/pepaskopec-blip/Snake-Pygame-" target="_blank" rel="noopener noreferrer">Snake-Pygame-</a>
</h3>
<p>My first game in python, it is like 90% ai code, when I made it for the first time, I thought that it looks good, but now it looks like ai slop. Still it is very solid game packed with a lot of features, so it is pretty fun to play or try :D </p>
<a class="project-link" href="https://github.com/pepaskopec-blip/Snake-Pygame-" target="_blank" rel="noopener noreferrer">View on GitHub →</a>
</li>
<li class="project-item">
<span class="tag">Batchfile</span>
<h3>
<a href="https://github.com/pepaskopec-blip/BatchGym" target="_blank" rel="noopener noreferrer">BatchGym</a>
</h3>
<p>First game ever — a freshman school project for console application, written in bash, it can natively run only on Windows. Text-based RPG that simulates a gym visit: manage money, buy supplements, and complete sets.</p>
<a class="project-link" href="https://github.com/pepaskopec-blip/BatchGym" target="_blank" rel="noopener noreferrer">View on GitHub →</a>
</li>
<li class="project-item">
<span class="tag">Swift, Kotlin</span> <span class="tag">Swift, Kotlin</span>
<span class="project-badge">Featured project</span>
</div>
<h3> <h3>
<a href="https://github.com/pepaskopec-blip/Graduately" target="_blank" rel="noopener noreferrer">Graduately</a> <a href="https://github.com/pepaskopec-blip/Graduately" target="_blank" rel="noopener noreferrer">Graduately</a>
</h3> </h3>
<p>My biggest project yet. It is an educational SwiftUI/ Kotlin app for maturita/graduation preparation — The app is crossplatform and it is available on Mac, iPhone, Android, iPad, Windows and Linux. I am really proud of it.</p> <p>My biggest project yet. It is an educational SwiftUI/ Kotlin app for maturita/graduation preparation — The app is crossplatform and it is available on Mac, iPhone, Android, iPad, Windows and Linux. I am really proud of it.</p>
<a class="project-link" href="https://github.com/pepaskopec-blip/Graduately" target="_blank" rel="noopener noreferrer">View on GitHub →</a> <ul class="project-pills" aria-label="Platforms and technologies">
<li>SwiftUI</li>
<li>Kotlin + Compose</li>
<li>macOS &amp; iOS</li>
<li>Android</li>
<li>Windows &amp; Linux</li>
<li>13 subjects</li>
</ul>
<div class="project-card-actions">
<a class="btn btn-primary" href="https://github.com/pepaskopec-blip/Graduately" target="_blank" rel="noopener noreferrer">View on GitHub →</a>
<a class="btn btn-ghost" href="https://github.com/pepaskopec-blip/Graduately#install" target="_blank" rel="noopener noreferrer">Install guide</a>
</div>
</div>
<a class="project-preview project-preview-graduately" href="https://github.com/pepaskopec-blip/Graduately" target="_blank" rel="noopener noreferrer" aria-label="Open Graduately on GitHub">
<img class="preview-graduately-shot" src="graduately-home.webp" width="1600" height="1199" loading="lazy" decoding="async" alt="Graduately app dashboard screenshot">
<img class="preview-graduately-icon" src="graduately-icon.webp" width="256" height="256" loading="lazy" decoding="async" alt="">
</a>
</li> </li>
<li class="project-item">
<!-- Homelab -->
<li class="project-card" data-category="infra">
<a class="project-preview project-preview-homelab" href="index.html#services" aria-hidden="true" tabindex="-1">
<div class="mock-rack">
<div class="mock-rack-header">
<span class="mock-status-dot"></span>
<span class="mock-rack-title">pve-node · 10.0.0.200</span>
<span class="mock-rack-uptime">10+ LXC / VMs</span>
</div>
<div class="mock-rack-bars">
<div class="mock-bar-row">
<span>CPU · Xeon 10C/20T</span>
<div class="mock-bar"><i style="width: 34%"></i></div>
</div>
<div class="mock-bar-row">
<span>RAM · 40 GB ECC</span>
<div class="mock-bar"><i style="width: 62%"></i></div>
</div>
</div>
<div class="mock-service-chips">
<span>Jellyfin</span>
<span>Ollama</span>
<span>Navidrome</span>
<span>Immich</span>
<span>Pi-hole</span>
<span>SearXNG</span>
<span>Joplin</span>
</div>
</div>
</a>
<div class="project-card-body">
<div class="project-card-meta">
<span class="tag">Proxmox</span>
<span class="project-status">Self-hosted · 24/7</span>
</div>
<h3>
<a href="index.html#services">Homelab</a>
</h3>
<p>I just wanted a NAS server, but now I host almost everything. It uses Proxmox as the operating system and virtualization. I have more than ten LXC containers and VMs for my services. I also set up automatic restarts for maximum safety and updates.</p>
<ul class="project-pills" aria-label="Technologies">
<li>Proxmox VE</li>
<li>LXC &amp; QEMU</li>
<li>Tailscale</li>
<li>Cloudflare Tunnels</li>
</ul>
<a class="project-link" href="index.html#services">See services →</a>
</div>
</li>
<!-- skopec.dev -->
<li class="project-card" data-category="apps">
<a class="project-preview project-preview-site" href="https://github.com/pepaskopec-blip/skopec.dev" target="_blank" rel="noopener noreferrer" aria-hidden="true" tabindex="-1">
<div class="mock-browser">
<div class="mock-browser-bar">
<span class="mock-dots"><i></i><i></i><i></i></span>
<span class="mock-url">https://skopec.dev</span>
<span class="mock-cache-tag">CF HIT</span>
</div>
<div class="mock-browser-canvas">
<div class="mock-site-hero">
<div class="mock-site-lines">
<span class="mock-line-title">Josef<i>.</i></span>
<span class="mock-line-sub"></span>
<span class="mock-line-sub short"></span>
</div>
<div class="mock-site-orb"></div>
</div>
<div class="mock-pipeline">
<span>Proxmox Origin</span>
<span class="mock-arrow">→</span>
<span>Cloudflared</span>
<span class="mock-arrow">→</span>
<span class="mock-edge">Edge Cache</span>
</div>
</div>
</div>
</a>
<div class="project-card-body">
<div class="project-card-meta">
<span class="tag">HTML</span>
<span class="project-status">Live · Cached</span>
</div>
<h3>
<a href="https://github.com/pepaskopec-blip/skopec.dev" target="_blank" rel="noopener noreferrer">skopec.dev</a>
</h3>
<p>This site, written in html, hosted on my Homelab/Proxmox server. It has caching enabled on Cloudflared so it does not have to load everything from the origin server.</p>
<ul class="project-pills" aria-label="Technologies">
<li>HTML5 &amp; CSS3</li>
<li>Vanilla JS</li>
<li>Cloudflared</li>
<li>Zero dependencies</li>
</ul>
<a class="project-link" href="https://github.com/pepaskopec-blip/skopec.dev" target="_blank" rel="noopener noreferrer">View on GitHub →</a>
</div>
</li>
<!-- Snake-Pygame- -->
<li class="project-card" data-category="games">
<div class="project-preview project-preview-snake">
<div class="mock-arcade">
<div class="mock-arcade-hud">
<span>SNAKE.PY</span>
<span data-snake-score>SCORE 0140</span>
</div>
<canvas class="snake-canvas" data-snake-canvas width="280" height="126" aria-label="Animated Snake game preview"></canvas>
</div>
</div>
<div class="project-card-body">
<div class="project-card-meta">
<span class="tag">Python</span>
<span class="project-status">Arcade game</span>
</div>
<h3>
<a href="https://github.com/pepaskopec-blip/Snake-Pygame-" target="_blank" rel="noopener noreferrer">Snake-Pygame-</a>
</h3>
<p>My first game in python, it is like 90% ai code, when I made it for the first time, I thought that it looks good, but now it looks like ai slop. Still it is very solid game packed with a lot of features, so it is pretty fun to play or try :D</p>
<ul class="project-pills" aria-label="Technologies">
<li>Python</li>
<li>Pygame</li>
<li>Power-ups</li>
<li>Arcade modes</li>
</ul>
<a class="project-link" href="https://github.com/pepaskopec-blip/Snake-Pygame-" target="_blank" rel="noopener noreferrer">View on GitHub →</a>
</div>
</li>
<!-- BatchGym -->
<li class="project-card" data-category="games">
<a class="project-preview project-preview-batch" href="https://github.com/pepaskopec-blip/BatchGym" target="_blank" rel="noopener noreferrer" aria-hidden="true" tabindex="-1">
<div class="mock-terminal">
<div class="mock-terminal-bar">
<span>C:\WINDOWS\system32\cmd.exe — BatchGym.bat</span>
<span>×</span>
</div>
<div class="mock-terminal-body">
<div class="term-header">[ BATCH GYM RPG · DAY 12 · CASH: $85 ]</div>
<div class="term-stats">
<span>STR ████████░░ 80%</span>
<span>NRG ███████░░░ 70%</span>
</div>
<div class="term-log">&gt; Stack: Creatine + Whey Isolate</div>
<div class="term-prompt">&gt; [1] Bench Press [2] Shop [3] Rest<i class="term-cursor"></i></div>
</div>
</div>
</a>
<div class="project-card-body">
<div class="project-card-meta">
<span class="tag">Batchfile</span>
<span class="project-status">Console RPG</span>
</div>
<h3>
<a href="https://github.com/pepaskopec-blip/BatchGym" target="_blank" rel="noopener noreferrer">BatchGym</a>
</h3>
<p>First game ever — a freshman school project for console application, written in bash, it can natively run only on Windows. Text-based RPG that simulates a gym visit: manage money, buy supplements, and complete sets.</p>
<ul class="project-pills" aria-label="Technologies">
<li>Windows Batch</li>
<li>CLI RPG</li>
<li>School project</li>
</ul>
<a class="project-link" href="https://github.com/pepaskopec-blip/BatchGym" target="_blank" rel="noopener noreferrer">View on GitHub →</a>
</div>
</li>
<!-- Terra -->
<li class="project-card" data-category="infra">
<a class="project-preview project-preview-terra" href="https://terra.hackclub.com/" target="_blank" rel="noopener noreferrer" aria-hidden="true" tabindex="-1">
<div class="mock-terra">
<div class="mock-terra-top">
<span class="mock-terra-badge">HACK CLUB · YSWS</span>
<span class="mock-terra-duration">3 MONTHS</span>
</div>
<div class="mock-terra- tracks">
<div class="mock-terra-track">
<div class="mock-terra-label">
<span>Human coding</span>
<strong>10h / week</strong>
</div>
<div class="mock-bar"><i style="width: 78%"></i></div>
</div>
<div class="mock-terra-track">
<div class="mock-terra-label">
<span>AI coding</span>
<strong>30h / week</strong>
</div>
<div class="mock-bar"><i style="width: 65%"></i></div>
</div>
</div>
<div class="mock-terra-reward">
<span>Final reward</span>
<strong>Framework Laptop / iPad</strong>
</div>
</div>
</a>
<div class="project-card-body">
<div class="project-card-meta">
<span class="tag">Terra</span> <span class="tag">Terra</span>
<span class="project-status">Hack Club · Active</span>
</div>
<h3> <h3>
<a href="https://terra.hackclub.com" target="_blank" rel="noopener noreferrer">Terra</a> <a href="https://terra.hackclub.com" target="_blank" rel="noopener noreferrer">Terra</a>
</h3> </h3>
<p>Terra is You Ship, We Ship program rub by Hack Club. It combines gamified farming mechanics with real-world software developement, where coding translates into in-game resources to earn a free modular laptop or tablet.</p> <p>Terra is You Ship, We Ship program rub by Hack Club. It combines gamified farming mechanics with real-world software developement, where coding translates into in-game resources to earn a free modular laptop or tablet.</p>
<a class="project-link" href="https://terra.hackclub.com/" target="_blank" rel="noopener noreferrer">View their site</a> <ul class="project-pills" aria-label="Program details">
<li>Hack Club</li>
<li>10h / 30h weekly</li>
<li>Framework / iPad</li>
</ul>
<a class="project-link" href="https://terra.hackclub.com/" target="_blank" rel="noopener noreferrer">View their site →</a>
</div>
</li> </li>
</ul> </ul>
</div> </div>
@ -157,6 +360,6 @@
</div> </div>
</footer> </footer>
<script src="script.js" defer></script> <script src="script.js?v=20261022" defer></script>
</body> </body>
</html> </html>

239
script.js
View file

@ -59,6 +59,245 @@
}); });
} }
const radar = document.querySelector("[data-radar]");
if (radar) {
const hudName = document.querySelector("[data-radar-name]");
const hudRole = document.querySelector("[data-radar-role]");
const nodes = Array.from(radar.querySelectorAll(".radar-node"));
const defaultName = hudName ? hudName.textContent : "Homelab";
const defaultRole = hudRole ? hudRole.textContent : "";
const nodeData = nodes.map((el) => {
const x = parseFloat(el.style.getPropertyValue("--x")) || 50;
const y = parseFloat(el.style.getPropertyValue("--y")) || 50;
const dx = x - 50;
const dy = y - 50;
const dist = Math.hypot(dx, dy);
const angle = ((Math.atan2(dx, -dy) * 180) / Math.PI + 360) % 360;
return { el, dist, angle };
});
let currentBeam = 102;
let targetBeam = null;
let hoveredNode = null;
const reduceMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const shortestDelta = (from, to) => ((((to - from) % 360) + 540) % 360) - 180;
const updateSweep = (beamDeg) => {
const startDeg = (beamDeg - 252 + 360) % 360;
radar.style.setProperty("--radar-angle", `${startDeg.toFixed(1)}deg`);
nodeData.forEach(({ el, dist, angle }) => {
if (dist < 4) return;
const diff = Math.abs(shortestDelta(angle, beamDeg));
el.classList.toggle("is-swept", diff < 28);
});
};
const setHud = (name, role) => {
if (hudName) hudName.textContent = name;
if (hudRole) hudRole.textContent = role;
};
nodes.forEach((el, idx) => {
const activate = () => {
hoveredNode = nodeData[idx];
if (hoveredNode.dist >= 4) {
targetBeam = hoveredNode.angle;
}
setHud(el.dataset.name || defaultName, el.dataset.role || defaultRole);
};
const deactivate = () => {
hoveredNode = null;
setHud(defaultName, defaultRole);
};
el.addEventListener("mouseenter", activate);
el.addEventListener("focus", activate);
el.addEventListener("mouseleave", deactivate);
el.addEventListener("blur", deactivate);
});
radar.addEventListener("pointermove", (event) => {
if (hoveredNode && hoveredNode.dist >= 4) return;
const rect = radar.getBoundingClientRect();
const cx = rect.left + rect.width / 2;
const cy = rect.top + rect.height / 2;
const dx = event.clientX - cx;
const dy = event.clientY - cy;
if (Math.hypot(dx, dy) > 8) {
targetBeam = ((Math.atan2(dx, -dy) * 180) / Math.PI + 360) % 360;
}
});
radar.addEventListener("pointerleave", () => {
if (!hoveredNode) targetBeam = null;
});
if (!reduceMotion) {
let lastTime = performance.now();
const tick = (now) => {
const dt = Math.min((now - lastTime) / 1000, 0.1);
lastTime = now;
if (targetBeam !== null) {
const delta = shortestDelta(currentBeam, targetBeam);
currentBeam = (currentBeam + delta * Math.min(1, dt * 8) + 360) % 360;
} else {
currentBeam = (currentBeam + dt * 18) % 360;
}
updateSweep(currentBeam);
requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
} else {
updateSweep(currentBeam);
}
}
const filterChips = document.querySelectorAll("[data-filter]");
const projectCards = document.querySelectorAll(".project-card[data-category]");
if (filterChips.length && projectCards.length) {
filterChips.forEach((chip) => {
chip.addEventListener("click", () => {
const filter = chip.dataset.filter;
filterChips.forEach((c) => c.classList.toggle("is-active", c === chip));
projectCards.forEach((card) => {
const match = filter === "all" || card.dataset.category === filter;
card.hidden = !match;
});
});
});
}
const hwChips = document.querySelectorAll("[data-hw-filter]");
const hwCards = document.querySelectorAll(".hw-card[data-hw-category]");
if (hwChips.length && hwCards.length) {
hwChips.forEach((chip) => {
chip.addEventListener("click", () => {
const filter = chip.dataset.hwFilter;
hwChips.forEach((c) => c.classList.toggle("is-active", c === chip));
hwCards.forEach((card) => {
const match = filter === "all" || card.dataset.hwCategory === filter;
card.hidden = !match;
});
});
});
}
const snakeCanvas = document.querySelector("[data-snake-canvas]");
if (snakeCanvas instanceof HTMLCanvasElement) {
const ctx = snakeCanvas.getContext("2d");
const scoreNode = document.querySelector("[data-snake-score]");
const cols = 20;
const rows = 9;
const cell = 14;
let snake = [
{ x: 6, y: 4 },
{ x: 5, y: 4 },
{ x: 4, y: 4 },
{ x: 3, y: 4 },
{ x: 2, y: 4 },
];
let food = { x: 14, y: 4 };
let score = 140;
const placeFood = (tx, ty) => {
if (typeof tx === "number" && typeof ty === "number") {
food = {
x: Math.max(1, Math.min(cols - 2, tx)),
y: Math.max(1, Math.min(rows - 2, ty)),
};
return;
}
for (let i = 0; i < 30; i++) {
const fx = 1 + Math.floor(Math.random() * (cols - 2));
const fy = 1 + Math.floor(Math.random() * (rows - 2));
if (!snake.some((s) => s.x === fx && s.y === fy)) {
food = { x: fx, y: fy };
return;
}
}
};
const drawSnake = () => {
if (!ctx) return;
ctx.clearRect(0, 0, snakeCanvas.width, snakeCanvas.height);
ctx.strokeStyle = "rgba(238, 242, 246, 0.04)";
ctx.lineWidth = 1;
for (let x = 0; x <= cols; x++) {
ctx.beginPath();
ctx.moveTo(x * cell + 0.5, 0);
ctx.lineTo(x * cell + 0.5, rows * cell);
ctx.stroke();
}
for (let y = 0; y <= rows; y++) {
ctx.beginPath();
ctx.moveTo(0, y * cell + 0.5);
ctx.lineTo(cols * cell, y * cell + 0.5);
ctx.stroke();
}
ctx.fillStyle = "#ff4a1c";
ctx.beginPath();
ctx.arc(food.x * cell + cell / 2, food.y * cell + cell / 2, 4.5, 0, Math.PI * 2);
ctx.fill();
snake.forEach((seg, idx) => {
const alpha = Math.max(0.28, 1 - idx * 0.09);
ctx.fillStyle = idx === 0 ? "#eef2f6" : `rgba(255, 122, 82, ${alpha})`;
ctx.fillRect(seg.x * cell + 2, seg.y * cell + 2, cell - 4, cell - 4);
});
};
const stepSnake = () => {
const head = snake[0];
const dirs = [
{ x: 1, y: 0 },
{ x: -1, y: 0 },
{ x: 0, y: 1 },
{ x: 0, y: -1 },
];
const valid = dirs
.map((d) => ({
x: (head.x + d.x + cols) % cols,
y: (head.y + d.y + rows) % rows,
}))
.filter((p) => !snake.slice(0, -1).some((s) => s.x === p.x && s.y === p.y));
const next = valid.length
? valid.sort(
(a, b) =>
Math.hypot(a.x - food.x, a.y - food.y) -
Math.hypot(b.x - food.x, b.y - food.y)
)[0]
: { x: (head.x + 1) % cols, y: head.y };
snake.unshift(next);
if (next.x === food.x && next.y === food.y) {
score += 10;
if (scoreNode) scoreNode.textContent = `SCORE ${String(score).padStart(4, "0")}`;
if (snake.length > 9) snake.pop();
placeFood();
} else {
snake.pop();
}
drawSnake();
};
snakeCanvas.addEventListener("pointerdown", (e) => {
const rect = snakeCanvas.getBoundingClientRect();
const gx = Math.floor(((e.clientX - rect.left) / rect.width) * cols);
const gy = Math.floor(((e.clientY - rect.top) / rect.height) * rows);
placeFood(gx, gy);
drawSnake();
});
drawSnake();
if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
setInterval(stepSnake, 180);
}
}
const revealItems = document.querySelectorAll(".reveal"); const revealItems = document.querySelectorAll(".reveal");
if (!revealItems.length) return; if (!revealItems.length) return;

1415
style.css

File diff suppressed because it is too large Load diff