diff --git a/404.html b/404.html index 26b5e37..6072f53 100644 --- a/404.html +++ b/404.html @@ -28,7 +28,7 @@ } })(); - + @@ -82,6 +82,6 @@ - + diff --git a/hardware.html b/hardware.html index af98765..9ae26f8 100644 --- a/hardware.html +++ b/hardware.html @@ -34,7 +34,7 @@ } })(); - + + diff --git a/index.html b/index.html index 7bc27aa..e552f0b 100644 --- a/index.html +++ b/index.html @@ -1,5 +1,5 @@ - + @@ -34,7 +34,7 @@ } })(); - + + diff --git a/llms.txt b/llms.txt index 5015f65..3a9894a 100644 --- a/llms.txt +++ b/llms.txt @@ -30,6 +30,7 @@ Local 10.0.0.x addresses work only on the home network or with Josef’s Tailsca - BatchGym - Graduately — https://github.com/pepaskopec-blip/Graduately - Terra +- Jellyfin, Navidrome, Ollama, SearXNG, ConvertX, Pi-hole, Joplin, Immich — services from the homelab, also listed on the Projects page ## Friends - Martin Dvořák — https://dvorakmortonxd.dev diff --git a/privacy.html b/privacy.html index 72ab9b6..6735614 100644 --- a/privacy.html +++ b/privacy.html @@ -33,7 +33,7 @@ } })(); - + + diff --git a/projects.html b/projects.html index 8de43cf..a448ebd 100644 --- a/projects.html +++ b/projects.html @@ -3,11 +3,11 @@ - - Projects — websites & games · Josef + + Projects — websites, services & games · Josef - + @@ -15,7 +15,7 @@ - + @@ -34,7 +34,7 @@ } })(); - + + diff --git a/script.js b/script.js index baa064c..e36dedb 100644 --- a/script.js +++ b/script.js @@ -57,6 +57,12 @@ document.addEventListener("keydown", (event) => { if (event.key === "Escape") setOpen(false); }); + + document.addEventListener("click", (event) => { + if (!nav.classList.contains("is-open")) return; + if (nav.contains(event.target) || toggle.contains(event.target)) return; + setOpen(false); + }); } const radar = document.querySelector("[data-radar]"); @@ -157,9 +163,14 @@ const projectCards = document.querySelectorAll(".project-card[data-category]"); if (filterChips.length && projectCards.length) { filterChips.forEach((chip) => { + chip.setAttribute("aria-pressed", chip.classList.contains("is-active") ? "true" : "false"); chip.addEventListener("click", () => { const filter = chip.dataset.filter; - filterChips.forEach((c) => c.classList.toggle("is-active", c === chip)); + filterChips.forEach((c) => { + const on = c === chip; + c.classList.toggle("is-active", on); + c.setAttribute("aria-pressed", on ? "true" : "false"); + }); projectCards.forEach((card) => { const match = filter === "all" || card.dataset.category === filter; card.hidden = !match; @@ -172,9 +183,14 @@ const hwCards = document.querySelectorAll(".hw-card[data-hw-category]"); if (hwChips.length && hwCards.length) { hwChips.forEach((chip) => { + chip.setAttribute("aria-pressed", chip.classList.contains("is-active") ? "true" : "false"); chip.addEventListener("click", () => { const filter = chip.dataset.hwFilter; - hwChips.forEach((c) => c.classList.toggle("is-active", c === chip)); + hwChips.forEach((c) => { + const on = c === chip; + c.classList.toggle("is-active", on); + c.setAttribute("aria-pressed", on ? "true" : "false"); + }); hwCards.forEach((card) => { const match = filter === "all" || card.dataset.hwCategory === filter; card.hidden = !match; @@ -218,11 +234,29 @@ } }; + const snakePalette = () => { + const light = document.documentElement.dataset.theme === "light"; + return light + ? { + grid: "rgba(17, 21, 28, 0.08)", + food: "#dc3c11", + head: "#11151c", + body: "179, 47, 8", + } + : { + grid: "rgba(238, 242, 246, 0.04)", + food: "#ff4a1c", + head: "#eef2f6", + body: "255, 122, 82", + }; + }; + const drawSnake = () => { if (!ctx) return; + const palette = snakePalette(); ctx.clearRect(0, 0, snakeCanvas.width, snakeCanvas.height); - ctx.strokeStyle = "rgba(238, 242, 246, 0.04)"; + ctx.strokeStyle = palette.grid; ctx.lineWidth = 1; for (let x = 0; x <= cols; x++) { ctx.beginPath(); @@ -237,14 +271,14 @@ ctx.stroke(); } - ctx.fillStyle = "#ff4a1c"; + ctx.fillStyle = palette.food; 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.fillStyle = idx === 0 ? palette.head : `rgba(${palette.body}, ${alpha})`; ctx.fillRect(seg.x * cell + 2, seg.y * cell + 2, cell - 4, cell - 4); }); }; @@ -296,6 +330,10 @@ if (!window.matchMedia("(prefers-reduced-motion: reduce)").matches) { setInterval(stepSnake, 180); } + new MutationObserver(drawSnake).observe(document.documentElement, { + attributes: true, + attributeFilter: ["data-theme"], + }); } const revealItems = document.querySelectorAll(".reveal"); diff --git a/sitemap.xml b/sitemap.xml index d2fa02f..3cac36b 100644 --- a/sitemap.xml +++ b/sitemap.xml @@ -2,19 +2,19 @@ https://skopec.dev/ - 2026-10-12 + 2026-10-18 monthly 1.0 https://skopec.dev/projects.html - 2026-10-12 + 2026-10-18 monthly 0.8 https://skopec.dev/hardware.html - 2026-10-12 + 2026-10-18 monthly 0.8 diff --git a/style.css b/style.css index bfb0854..43c6ce7 100644 --- a/style.css +++ b/style.css @@ -1590,6 +1590,17 @@ a:hover { color: var(--accent-ink); } +:root[data-theme="light"] .mock-arcade { + background: #f4f6f8; + box-shadow: 0 14px 30px rgba(17, 21, 28, 0.08); +} + +:root[data-theme="light"] .mock-arcade-hud { + color: var(--ink-soft); + background: rgba(17, 21, 28, 0.04); + border-bottom-color: var(--line); +} + .snake-canvas { display: block; width: 100%; @@ -1658,6 +1669,30 @@ a:hover { background: var(--accent); } +:root[data-theme="light"] .mock-terminal { + background: #f7f8fa; + box-shadow: 0 14px 30px rgba(17, 21, 28, 0.08); +} + +:root[data-theme="light"] .mock-terminal-bar { + color: var(--muted); + background: #eef1f4; + border-bottom-color: var(--line); +} + +:root[data-theme="light"] .mock-terminal-body { + color: var(--ink-soft); +} + +:root[data-theme="light"] .term-stats, +:root[data-theme="light"] .term-log { + color: var(--muted); +} + +:root[data-theme="light"] .term-prompt { + color: var(--ink); +} + /* Terra challenge preview */ .mock-terra { width: 100%; @@ -1722,6 +1757,280 @@ a:hover { font-weight: 650; } +/* Self-hosted service previews */ +.mock-svc { + width: 100%; + max-width: 24rem; + padding: 0.9rem 1rem; + background: var(--surface-solid); + border: 1px solid var(--line-strong); + border-radius: 4px; + box-shadow: 0 14px 30px rgba(0, 0, 0, 0.28); +} + +.mock-svc-top { + display: flex; + align-items: center; + gap: 0.45rem; + margin-bottom: 0.75rem; + font-size: 0.72rem; + font-weight: 650; + letter-spacing: 0.04em; + color: var(--ink); +} + +.mock-svc-host { + margin-left: auto; + color: var(--muted); + font-weight: 600; + letter-spacing: 0; + font-variant-numeric: tabular-nums; +} + +.mock-posters { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0.4rem; +} + +.mock-posters i { + display: block; + aspect-ratio: 2 / 3; + border-radius: 2px; + background: linear-gradient(165deg, var(--accent) 0%, #24140f 78%); +} + +.mock-posters i:nth-child(2) { + background: linear-gradient(200deg, #3d5a80 0%, #121820 80%); +} + +.mock-posters i:nth-child(3) { + background: linear-gradient(150deg, #c45c26 0%, #1a120e 78%); +} + +.mock-posters i:nth-child(4) { + background: linear-gradient(185deg, #6b4c7a 0%, #141820 80%); +} + +.mock-now { + margin-top: 0.65rem; + font-size: 0.72rem; + color: var(--muted); +} + +.mock-now strong { + color: var(--ink); + font-weight: 650; +} + +.mock-track { + display: grid; + grid-template-columns: 3.1rem minmax(0, 1fr); + gap: 0.7rem; + align-items: center; +} + +.mock-album { + width: 3.1rem; + height: 3.1rem; + border-radius: 2px; + background: + linear-gradient(145deg, transparent 42%, rgba(0, 0, 0, 0.28) 43% 57%, transparent 58%), + linear-gradient(145deg, var(--accent) 0%, #1c2430 72%); +} + +.mock-track-title { + font-weight: 700; + font-size: 0.86rem; + color: var(--ink); + line-height: 1.2; +} + +.mock-track-by { + margin: 0.12rem 0 0.45rem; + font-size: 0.72rem; + color: var(--muted); +} + +.mock-chat { + display: grid; + gap: 0.4rem; +} + +.mock-bubble { + max-width: 92%; + padding: 0.42rem 0.6rem; + font-size: 0.75rem; + line-height: 1.4; + color: var(--muted); + background: var(--surface); + border: 1px solid var(--line); +} + +.mock-bubble.ask { + margin-left: auto; + color: var(--ink); + border-color: rgba(255, 74, 28, 0.35); + background: rgba(255, 74, 28, 0.1); +} + +.mock-search { + display: flex; + align-items: center; + gap: 0.5rem; + padding: 0.55rem 0.7rem; + border: 1px solid var(--line-strong); + background: var(--bg); + color: var(--ink-soft); + font-size: 0.82rem; +} + +.mock-search-icon { + color: var(--accent); + font-weight: 700; + line-height: 1; +} + +.mock-search-meta { + display: flex; + gap: 0.75rem; + margin-top: 0.65rem; + font-size: 0.7rem; + color: var(--muted); +} + +.mock-search-meta span:first-child { + color: var(--accent-ink); + font-weight: 650; +} + +.mock-jobs { + display: grid; + gap: 0.45rem; + font-size: 0.76rem; +} + +.mock-jobs div { + display: grid; + grid-template-columns: 5.2rem auto auto 1fr; + gap: 0.45rem; + align-items: center; + color: var(--ink-soft); +} + +.mock-jobs em { + font-style: normal; + color: var(--accent); +} + +.mock-jobs b { + justify-self: end; + font-weight: 650; + font-size: 0.68rem; + letter-spacing: 0.04em; + text-transform: uppercase; + color: var(--muted); +} + +.mock-stat { + display: flex; + justify-content: space-between; + align-items: baseline; + gap: 0.75rem; + margin-bottom: 0.5rem; +} + +.mock-stat strong { + font-family: var(--font-display); + font-size: 1.35rem; + font-weight: 750; + letter-spacing: -0.03em; + line-height: 1; +} + +.mock-stat span { + font-size: 0.72rem; + color: var(--muted); +} + +.mock-stat-rows { + display: grid; + gap: 0.35rem; + margin-top: 0.7rem; + font-size: 0.72rem; + color: var(--muted); +} + +.mock-stat-rows div { + display: flex; + justify-content: space-between; + gap: 0.75rem; +} + +.mock-stat-rows div span:last-child { + color: var(--ink-soft); + font-weight: 650; +} + +.mock-notes { + display: grid; +} + +.mock-notes div { + display: flex; + align-items: center; + gap: 0.55rem; + padding: 0.42rem 0; + border-bottom: 1px solid var(--line); + font-size: 0.8rem; + color: var(--ink-soft); +} + +.mock-notes div:first-child { + color: var(--ink); + font-weight: 650; +} + +.mock-notes div:last-child { + border-bottom: 0; +} + +.mock-notes i { + width: 0.4rem; + height: 0.4rem; + border-radius: 50%; + background: var(--line-strong); + flex-shrink: 0; +} + +.mock-notes div:first-child i { + background: var(--accent); + box-shadow: 0 0 8px var(--glow); +} + +.mock-photos { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 0.35rem; +} + +.mock-photos i { + display: block; + aspect-ratio: 3 / 2; + border-radius: 2px; + background: linear-gradient(160deg, var(--line-strong), var(--glow-cool)); +} + +.mock-photos i:nth-child(2), +.mock-photos i:nth-child(6) { + background: linear-gradient(200deg, rgba(255, 74, 28, 0.55), #1a140f); +} + +.mock-photos i:nth-child(3), +.mock-photos i:nth-child(4) { + background: linear-gradient(145deg, #3d5a80, #121820); +} + .tag { display: inline-block; margin-bottom: 0.5rem; @@ -1997,6 +2306,25 @@ a:hover { .fetch-palette i:nth-child(5) { background: #818cf8; } .fetch-palette i:nth-child(6) { background: #e2e8f0; } +:root[data-theme="light"] .mock-fetch { + background: #f7f8fa; + box-shadow: 0 14px 30px rgba(17, 21, 28, 0.08); +} + +:root[data-theme="light"] .mock-fetch-bar { + color: var(--muted); + background: #eef1f4; + border-bottom-color: var(--line); +} + +:root[data-theme="light"] .mock-fetch-body { + color: var(--ink-soft); +} + +:root[data-theme="light"] .fetch-palette i:nth-child(6) { + box-shadow: inset 0 0 0 1px rgba(17, 21, 28, 0.28); +} + /* Laptop Apple M5 SoC widget */ .mock-soc { width: 100%; @@ -2123,11 +2451,17 @@ a:hover { .server-bays { display: grid; - grid-template-columns: repeat(3, minmax(0, 1fr)); + grid-template-columns: 1fr; gap: 0.45rem; margin-bottom: 0.6rem; } +@media (min-width: 640px) { + .server-bays { + grid-template-columns: repeat(3, minmax(0, 1fr)); + } +} + .server-bay { display: flex; flex-direction: column;