default to dark mode, refresh Right now text, CSS barbell gym icons
This commit is contained in:
parent
6827ca6ed1
commit
4a8cac689b
10 changed files with 101 additions and 66 deletions
|
|
@ -8,7 +8,7 @@ from the saved theme, all from the same content:
|
||||||
- **`web`** (default) — a light, frosted-glass **website**: a sticky full-height sidebar
|
- **`web`** (default) — a light, frosted-glass **website**: a sticky full-height sidebar
|
||||||
(section nav, light/dark switch, desktop switch) beside a column of glass cards — hero with
|
(section nav, light/dark switch, desktop switch) beside a column of glass cards — hero with
|
||||||
a socials row, quick tiles, live services, activity heatmap, projects and hardware
|
a socials row, quick tiles, live services, activity heatmap, projects and hardware
|
||||||
(a masonry gallery), features and friends. Light by default with a persisted dark mode,
|
(a masonry gallery), features and friends. Dark by default with a persisted light mode,
|
||||||
and fully responsive, so phones and desktops get the same shell.
|
and fully responsive, so phones and desktops get the same shell.
|
||||||
- **`skeuo`** — an alternate KDE Plasma / Windows-style **desktop simulation** (desktop
|
- **`skeuo`** — an alternate KDE Plasma / Windows-style **desktop simulation** (desktop
|
||||||
only): draggable snap-to-grid icons, resizable/floating windows, a Start menu, taskbar and
|
only): draggable snap-to-grid icons, resizable/floating windows, a Start menu, taskbar and
|
||||||
|
|
@ -26,7 +26,7 @@ more.
|
||||||
- **Two themes / shells** — a default frosted-glass **web** shell and an alternate KDE-style
|
- **Two themes / shells** — a default frosted-glass **web** shell and an alternate KDE-style
|
||||||
**desktop** shell, toggled from either UI and persisted in `localStorage` (applied pre-paint
|
**desktop** shell, toggled from either UI and persisted in `localStorage` (applied pre-paint
|
||||||
to avoid a flash). Phones always get the web shell.
|
to avoid a flash). Phones always get the web shell.
|
||||||
- **Light / dark mode** — the web shell has a light/dark switch (light by default, remembered
|
- **Light / dark mode** — the web shell has a light/dark switch (dark by default, remembered
|
||||||
in `localStorage`, applied pre-paint). The desktop shell keeps its own Breeze Dark look.
|
in `localStorage`, applied pre-paint). The desktop shell keeps its own Breeze Dark look.
|
||||||
- **Glassmorphic web layout** — a sticky sidebar with section navigation beside a column of
|
- **Glassmorphic web layout** — a sticky sidebar with section navigation beside a column of
|
||||||
frosted-glass panels (subtle gradients + `backdrop-filter` blur), reorganised into
|
frosted-glass panels (subtle gradients + `backdrop-filter` blur), reorganised into
|
||||||
|
|
|
||||||
11
agents.md
11
agents.md
|
|
@ -109,7 +109,8 @@ scripts/
|
||||||
1. `ThemeScript` runs before paint. It reads `localStorage.theme` and sets
|
1. `ThemeScript` runs before paint. It reads `localStorage.theme` and sets
|
||||||
`data-theme` (`web` | `skeuo`) on `<html>`, defaulting to `web`; it also reads
|
`data-theme` (`web` | `skeuo`) on `<html>`, defaulting to `web`; it also reads
|
||||||
`localStorage.scheme` and sets `data-scheme` (`light` | `dark`), defaulting to
|
`localStorage.scheme` and sets `data-scheme` (`light` | `dark`), defaulting to
|
||||||
`light`. There is no device sniff anymore.
|
`dark` (only an explicit stored `light` overrides it). There is no device
|
||||||
|
sniff anymore.
|
||||||
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via `useSyncExternalStore`
|
2. `useTheme()` (in `useTheme.ts`) reads `data-theme` via `useSyncExternalStore`
|
||||||
and dispatches a `theme-change` event on toggle. `useColorScheme()` does the
|
and dispatches a `theme-change` event on toggle. `useColorScheme()` does the
|
||||||
same for `data-scheme` via a `scheme-change` event.
|
same for `data-scheme` via a `scheme-change` event.
|
||||||
|
|
@ -192,6 +193,14 @@ Themes: `web` = default frosted-glass website (sidebar + content, light/dark),
|
||||||
`.web-tile-note` is centered. Every other panel title is centered too:
|
`.web-tile-note` is centered. Every other panel title is centered too:
|
||||||
`.web-section-head` (the eyebrow + `.web-section-title`) and `.web-tile-title`
|
`.web-section-head` (the eyebrow + `.web-section-title`) and `.web-tile-title`
|
||||||
("Right now" / "Gym PRs"). Desktop keeps the left-aligned headings.
|
("Right now" / "Gym PRs"). Desktop keeps the left-aligned headings.
|
||||||
|
- The gym PR icons (`.gym-icon--squat` / `--bench` / `--deadlift`) are **pure
|
||||||
|
CSS barbells**, not SVGs: a bar (`::before`) plus plates drawn as layered
|
||||||
|
`linear-gradient` backgrounds on `::after`, all in `currentColor` so they
|
||||||
|
adapt to light/dark and the desktop app surface. More plates per side = a
|
||||||
|
heavier lift (bench 1, squat 2, deadlift 3). Don't swap them back to images.
|
||||||
|
- `.web-quick` is top-aligned (`align-items: start`) so the shorter "Right now"
|
||||||
|
tile isn't stretched to the Gym tile's height (it used to leave a big empty
|
||||||
|
gap under the text).
|
||||||
- Under `max-width: 640px` the year-grid heatmap (`.heatmap-container`) and its
|
- Under `max-width: 640px` the year-grid heatmap (`.heatmap-container`) and its
|
||||||
legend/hover row (`.heatmap-status-row`) are `display: none` — the grid is
|
legend/hover row (`.heatmap-status-row`) are `display: none` — the grid is
|
||||||
unreadable at phone widths. The contribution total and the commit history
|
unreadable at phone widths. The contribution total and the commit history
|
||||||
|
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="bench-press-6" transform="translate(-2 -2)">
|
|
||||||
<path id="primary" d="M6,21V15H18v6" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
|
||||||
<path id="primary-2" data-name="primary" d="M4,3V9M3,4V8M20,3V9m1-5V8M4,6H20M16,4V15M8,15V4m9,17h2M5,21H7" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 625 B |
|
|
@ -1,9 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg width="800px" height="800px" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<g id="deadlift" transform="translate(-2 -2)">
|
|
||||||
<circle id="secondary" fill="#2ca9bc" cx="2" cy="2" r="2" transform="translate(10 3)"/>
|
|
||||||
<path id="primary" d="M8,15V10a2,2,0,0,1,2-2h4a2,2,0,0,1,2,2v5" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
|
||||||
<path id="primary-2" data-name="primary" d="M14,5a2,2,0,1,1-2-2A2,2,0,0,1,14,5Zm0,9v7m-4,0V14M3,14H20m0-2v4m1-3v2M4,12v4M3,13v2" fill="none" stroke="#000000" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 762 B |
|
|
@ -1,15 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Uploaded to: SVG Repo, www.svgrepo.com, Generator: SVG Repo Mixer Tools -->
|
|
||||||
<svg fill="#000000" height="800px" width="800px" version="1.1" id="Layer_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"
|
|
||||||
viewBox="0 0 256 256" xml:space="preserve">
|
|
||||||
<g>
|
|
||||||
<circle cx="127" cy="28.4" r="26.6"/>
|
|
||||||
<path d="M213.4,171.2h-49.8V97.3c0-1.8,1.5-3.3,3.3-3.3c0.6,0,1.2,0.2,1.7,0.5l30.1,17.3l-20.8,36c-3.2,5.6-1.3,12.7,4.3,15.9
|
|
||||||
c5.6,3.2,12.7,1.3,15.9-4.3l26.6-46c3.2-5.6,1.3-12.7-4.3-15.9l-51.6-29.8c-4.7-3.3-10-5.4-15.5-6L127,61.6h0h0l-26.3,0.1
|
|
||||||
c-5.5,0.6-10.8,2.7-15.5,6L33.6,97.5c-5.6,3.2-7.5,10.3-4.3,15.9l26.6,46c3.2,5.6,10.3,7.5,15.9,4.3c5.6-3.2,7.5-10.3,4.3-15.9
|
|
||||||
l-20.8-36l30.1-17.3c0.5-0.3,1.1-0.5,1.7-0.5c1.8,0,3.3,1.5,3.3,3.3v24.1v49.8H40.6c-9.2,0-16.6,7.4-16.6,16.6
|
|
||||||
c0,4.6,1.8,8.7,4.8,11.7l49.7,49.7c6.5,6.5,17,6.5,23.5,0c6.5-6.5,6.5-17,0-23.5l-21.3-21.3l46.2,0l0-13.3c0,0,0,0,0,0s0,0,0,0
|
|
||||||
l0,13.3l46.2,0l-21.3,21.3c-6.5,6.5-6.5,17,0,23.5c6.5,6.5,17,6.5,23.5,0l49.7-49.7c3-3,4.8-7.1,4.8-11.7
|
|
||||||
C230,178.7,222.6,171.2,213.4,171.2z"/>
|
|
||||||
</g>
|
|
||||||
</svg>
|
|
||||||
|
Before Width: | Height: | Size: 1.1 KiB |
|
|
@ -7,8 +7,9 @@ import { useServerInsertedHTML } from "next/navigation";
|
||||||
// are applied without a flash. It is injected with useServerInsertedHTML so the
|
// are applied without a flash. It is injected with useServerInsertedHTML so the
|
||||||
// <script> lives in the SSR HTML stream instead of inside the React tree —
|
// <script> lives in the SSR HTML stream instead of inside the React tree —
|
||||||
// React 19 logs an error for script tags rendered by components on the client.
|
// React 19 logs an error for script tags rendered by components on the client.
|
||||||
// Anything unrecognised falls back to the defaults: web + light.
|
// Anything unrecognised falls back to the defaults: web + dark. Dark is the
|
||||||
const INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('theme');document.documentElement.setAttribute('data-theme',t==='skeuo'?'skeuo':'web');var s=localStorage.getItem('scheme');document.documentElement.setAttribute('data-scheme',s==='dark'?'dark':'light');}catch(e){}})();`;
|
// default scheme; only an explicit stored "light" switches it off.
|
||||||
|
const INIT_SCRIPT = `(function(){try{var t=localStorage.getItem('theme');document.documentElement.setAttribute('data-theme',t==='skeuo'?'skeuo':'web');var s=localStorage.getItem('scheme');document.documentElement.setAttribute('data-scheme',s==='light'?'light':'dark');}catch(e){}})();`;
|
||||||
|
|
||||||
export default function ThemeScript() {
|
export default function ThemeScript() {
|
||||||
// useServerInsertedHTML can fire several times during a single render pass;
|
// useServerInsertedHTML can fire several times during a single render pass;
|
||||||
|
|
|
||||||
|
|
@ -121,15 +121,15 @@ function Services() {
|
||||||
|
|
||||||
function Gym() {
|
function Gym() {
|
||||||
const lifts = [
|
const lifts = [
|
||||||
{ icon: "/squat.svg", label: "SQUAT", weight: "125kg", date: "5.1.2025" },
|
{ icon: "squat", label: "SQUAT", weight: "125kg", date: "5.1.2025" },
|
||||||
{ icon: "/bench.svg", label: "BENCH", weight: "111kg", date: "29.5.2026" },
|
{ icon: "bench", label: "BENCH", weight: "111kg", date: "29.5.2026" },
|
||||||
{ icon: "/deadlift.svg", label: "DEADLIFT", weight: "165kg", date: "17.12.2024" },
|
{ icon: "deadlift", label: "DEADLIFT", weight: "165kg", date: "17.12.2024" },
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
<div className="gym-grid">
|
<div className="gym-grid">
|
||||||
{lifts.map((l) => (
|
{lifts.map((l) => (
|
||||||
<div key={l.label} className="gym-item">
|
<div key={l.label} className="gym-item">
|
||||||
<Image src={l.icon} alt={l.label} width={48} height={48} className="gym-icon" />
|
<span className={`gym-icon gym-icon--${l.icon}`} aria-hidden="true" />
|
||||||
<div className="gym-label">{l.label}</div>
|
<div className="gym-label">{l.label}</div>
|
||||||
<div className="gym-weight">{l.weight}</div>
|
<div className="gym-weight">{l.weight}</div>
|
||||||
<div className="gym-date">{l.date}</div>
|
<div className="gym-date">{l.date}</div>
|
||||||
|
|
@ -238,11 +238,9 @@ export default function WebLayout() {
|
||||||
<article id="now" className="web-tile glass">
|
<article id="now" className="web-tile glass">
|
||||||
<h3 className="web-tile-title">Right now</h3>
|
<h3 className="web-tile-title">Right now</h3>
|
||||||
<p className="web-tile-text">
|
<p className="web-tile-text">
|
||||||
I'm currently hosting an SMP (dm me on discord). Also I'm pretty much
|
I left the maturita.c project to focus on my proxmox server (just popped in
|
||||||
cooking daily, because im bulking through out the non-summer seasons. And me
|
another 1.7TB hdd upgrade, 3 small hdds from ewaste laptops and I just
|
||||||
and my pal <a href="https://skopec.dev">Pepa</a> are working on an education
|
ziplocked it into place and connected it lmao)
|
||||||
app so we can study more efficiently, It's called{" "}
|
|
||||||
<a href="https://git.dvorakmortonxd.dev/edu/maturita.c">maturita.c</a>!
|
|
||||||
</p>
|
</p>
|
||||||
</article>
|
</article>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -301,25 +301,19 @@ function GymApp() {
|
||||||
<div className="app-pad">
|
<div className="app-pad">
|
||||||
<div className="gym-grid">
|
<div className="gym-grid">
|
||||||
<div className="gym-item">
|
<div className="gym-item">
|
||||||
<Image src="/squat.svg" alt="Squat" width={48} height={48} className="gym-icon" />
|
<span className="gym-icon gym-icon--squat" aria-hidden="true" />
|
||||||
<div className="gym-label">SQUAT</div>
|
<div className="gym-label">SQUAT</div>
|
||||||
<div className="gym-weight">125kg</div>
|
<div className="gym-weight">125kg</div>
|
||||||
<div className="gym-date">5.1.2025</div>
|
<div className="gym-date">5.1.2025</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="gym-item">
|
<div className="gym-item">
|
||||||
<Image src="/bench.svg" alt="Bench" width={48} height={48} className="gym-icon" />
|
<span className="gym-icon gym-icon--bench" aria-hidden="true" />
|
||||||
<div className="gym-label">BENCH</div>
|
<div className="gym-label">BENCH</div>
|
||||||
<div className="gym-weight">111kg</div>
|
<div className="gym-weight">111kg</div>
|
||||||
<div className="gym-date">29.5.2026</div>
|
<div className="gym-date">29.5.2026</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="gym-item">
|
<div className="gym-item">
|
||||||
<Image
|
<span className="gym-icon gym-icon--deadlift" aria-hidden="true" />
|
||||||
src="/deadlift.svg"
|
|
||||||
alt="Deadlift"
|
|
||||||
width={48}
|
|
||||||
height={48}
|
|
||||||
className="gym-icon"
|
|
||||||
/>
|
|
||||||
<div className="gym-label">DEADLIFT</div>
|
<div className="gym-label">DEADLIFT</div>
|
||||||
<div className="gym-weight">165kg</div>
|
<div className="gym-weight">165kg</div>
|
||||||
<div className="gym-date">17.12.2024</div>
|
<div className="gym-date">17.12.2024</div>
|
||||||
|
|
|
||||||
|
|
@ -4,13 +4,13 @@ import { useSyncExternalStore } from "react";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Light/dark appearance for the web theme. Persisted in `localStorage.scheme`
|
* Light/dark appearance for the web theme. Persisted in `localStorage.scheme`
|
||||||
* and applied pre-paint as `<html data-scheme>` so there is no flash. Light is
|
* and applied pre-paint as `<html data-scheme>` so there is no flash. Dark is
|
||||||
* the default.
|
* the default; the user's choice is remembered once they toggle.
|
||||||
*/
|
*/
|
||||||
export type ColorScheme = "light" | "dark";
|
export type ColorScheme = "light" | "dark";
|
||||||
|
|
||||||
const EVENT = "scheme-change";
|
const EVENT = "scheme-change";
|
||||||
export const DEFAULT_SCHEME: ColorScheme = "light";
|
export const DEFAULT_SCHEME: ColorScheme = "dark";
|
||||||
|
|
||||||
function subscribe(callback: () => void) {
|
function subscribe(callback: () => void) {
|
||||||
window.addEventListener(EVENT, callback);
|
window.addEventListener(EVENT, callback);
|
||||||
|
|
@ -22,9 +22,9 @@ function subscribe(callback: () => void) {
|
||||||
}
|
}
|
||||||
|
|
||||||
function getSnapshot(): ColorScheme {
|
function getSnapshot(): ColorScheme {
|
||||||
return document.documentElement.getAttribute("data-scheme") === "dark"
|
return document.documentElement.getAttribute("data-scheme") === "light"
|
||||||
? "dark"
|
? "light"
|
||||||
: "light";
|
: "dark";
|
||||||
}
|
}
|
||||||
|
|
||||||
function getServerSnapshot(): ColorScheme {
|
function getServerSnapshot(): ColorScheme {
|
||||||
|
|
|
||||||
|
|
@ -357,7 +357,9 @@ html[data-theme="web"] body {
|
||||||
display: grid;
|
display: grid;
|
||||||
grid-template-columns: 1.5fr 1fr;
|
grid-template-columns: 1.5fr 1fr;
|
||||||
gap: 20px;
|
gap: 20px;
|
||||||
align-items: stretch;
|
/* Don't stretch the shorter "Right now" tile to the taller Gym tile's
|
||||||
|
height — that left a big empty gap under its text. */
|
||||||
|
align-items: start;
|
||||||
}
|
}
|
||||||
.web-tile {
|
.web-tile {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
@ -373,7 +375,8 @@ html[data-theme="web"] body {
|
||||||
color: var(--accent);
|
color: var(--accent);
|
||||||
}
|
}
|
||||||
.web-tile-text {
|
.web-tile-text {
|
||||||
font-size: 0.9rem;
|
font-size: 1.12rem;
|
||||||
|
line-height: 1.7;
|
||||||
color: var(--web-muted);
|
color: var(--web-muted);
|
||||||
}
|
}
|
||||||
.web-tile-note {
|
.web-tile-note {
|
||||||
|
|
@ -728,10 +731,72 @@ html[data-theme="web"][data-scheme="dark"] .social-icon {
|
||||||
border-color: var(--accent-line);
|
border-color: var(--accent-line);
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
/* Custom CSS barbell icons (the squat/bench/deadlift SVGs are gone). Same
|
||||||
|
barbell, drawn from currentColor so it adapts to the theme; more plates per
|
||||||
|
side = heavier lift (bench 1, squat 2, deadlift 3). */
|
||||||
.gym-icon {
|
.gym-icon {
|
||||||
width: 100%;
|
position: relative;
|
||||||
max-width: 42px;
|
display: block;
|
||||||
height: auto;
|
width: 54px;
|
||||||
|
height: 30px;
|
||||||
|
}
|
||||||
|
.gym-icon::before {
|
||||||
|
/* the bar */
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
top: 50%;
|
||||||
|
height: 3px;
|
||||||
|
border-radius: 2px;
|
||||||
|
background: currentColor;
|
||||||
|
opacity: 0.55;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
}
|
||||||
|
.gym-icon::after {
|
||||||
|
/* the weight plates */
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: 0;
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.gym-icon--bench::after {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor);
|
||||||
|
background-size: 7px 24px, 7px 24px;
|
||||||
|
background-position: 9px center, calc(100% - 16px) center;
|
||||||
|
}
|
||||||
|
.gym-icon--squat::after {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor);
|
||||||
|
background-size: 6px 18px, 7px 26px, 6px 18px, 7px 26px;
|
||||||
|
background-position:
|
||||||
|
5px center,
|
||||||
|
12px center,
|
||||||
|
calc(100% - 11px) center,
|
||||||
|
calc(100% - 19px) center;
|
||||||
|
}
|
||||||
|
.gym-icon--deadlift::after {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor),
|
||||||
|
linear-gradient(currentColor, currentColor);
|
||||||
|
background-size: 6px 16px, 6px 22px, 7px 28px, 6px 16px, 6px 22px, 7px 28px;
|
||||||
|
background-position:
|
||||||
|
3px center,
|
||||||
|
9px center,
|
||||||
|
16px center,
|
||||||
|
calc(100% - 9px) center,
|
||||||
|
calc(100% - 15px) center,
|
||||||
|
calc(100% - 23px) center;
|
||||||
}
|
}
|
||||||
.gym-label {
|
.gym-label {
|
||||||
font-size: 0.66rem;
|
font-size: 0.66rem;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue