openvibers/afresume Commits

Drop GameServerStats, mark GameServerLists coming soon, list planned OpenVibe domains, fix reveal overlap

OP OpenVibers committed 20 Sept 2026 · be968e061b · 64 files, +111 −42

- GameServerStats case study removed; the experience entry stays generic
  ("Own company, LLC") and its rebuild is described as launching soon.
- Projects gain a status field; GameServerLists is "coming-soon" with a
  badge on the home card, portfolio grid and its page (no "Open live").
- Work > OpenVibe group ends with a "Coming soon" row linking the 13
  placeholder domains (OpenRe.Stream, Chat, Tips, VIP, Codes, Host, Wiki,
  Blog, News, Reviews, Deals, Coupons, Trade); previews cover them.
- Reveal animations use small offsets and no longer stack on nested
  wrappers: with 26px translations, cards sat on top of the next card
  while animating when someone scrolled fast.

Changes

ddiff --git a/public/assets/img/previews/alexfrison.net.jpg b/public/assets/img/previews/alexfrison.net.jpg
iindex 02f0e7f..39db12f 100644
BBinary files a/public/assets/img/previews/alexfrison.net.jpg and b/public/assets/img/previews/alexfrison.net.jpg differ
ddiff --git a/public/assets/img/previews/local_home.jpg b/public/assets/img/previews/local_home.jpg
iindex db19f30..f09efc8 100644
BBinary files a/public/assets/img/previews/local_home.jpg and b/public/assets/img/previews/local_home.jpg differ
ddiff --git a/public/assets/img/previews/local_home__about.jpg b/public/assets/img/previews/local_home__about.jpg
iindex 89dddba..4b155f2 100644
BBinary files a/public/assets/img/previews/local_home__about.jpg and b/public/assets/img/previews/local_home__about.jpg differ
ddiff --git a/public/assets/img/previews/local_home__contact.jpg b/public/assets/img/previews/local_home__contact.jpg
iindex 4510143..7774191 100644
BBinary files a/public/assets/img/previews/local_home__contact.jpg and b/public/assets/img/previews/local_home__contact.jpg differ
ddiff --git a/public/assets/img/previews/local_home__experience.jpg b/public/assets/img/previews/local_home__experience.jpg
iindex 863f615..5882fbd 100644
BBinary files a/public/assets/img/previews/local_home__experience.jpg and b/public/assets/img/previews/local_home__experience.jpg differ
ddiff --git a/public/assets/img/previews/local_home__metrics.jpg b/public/assets/img/previews/local_home__metrics.jpg
iindex 8c0ece0..49b80c6 100644
BBinary files a/public/assets/img/previews/local_home__metrics.jpg and b/public/assets/img/previews/local_home__metrics.jpg differ
ddiff --git a/public/assets/img/previews/local_home__network.jpg b/public/assets/img/previews/local_home__network.jpg
iindex 45d1f27..b20850a 100644
BBinary files a/public/assets/img/previews/local_home__network.jpg and b/public/assets/img/previews/local_home__network.jpg differ
ddiff --git a/public/assets/img/previews/local_home__skills.jpg b/public/assets/img/previews/local_home__skills.jpg
iindex 4c28936..9db4c8e 100644
BBinary files a/public/assets/img/previews/local_home__skills.jpg and b/public/assets/img/previews/local_home__skills.jpg differ
ddiff --git a/public/assets/img/previews/local_home__work.jpg b/public/assets/img/previews/local_home__work.jpg
iindex 6138ef1..8d80150 100644
BBinary files a/public/assets/img/previews/local_home__work.jpg and b/public/assets/img/previews/local_home__work.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio.jpg b/public/assets/img/previews/local_portfolio.jpg
iindex a2f896f..f0388d6 100644
BBinary files a/public/assets/img/previews/local_portfolio.jpg and b/public/assets/img/previews/local_portfolio.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_amazon-sql-copilot.jpg b/public/assets/img/previews/local_portfolio_amazon-sql-copilot.jpg
iindex 273e04d..86c4d2e 100644
BBinary files a/public/assets/img/previews/local_portfolio_amazon-sql-copilot.jpg and b/public/assets/img/previews/local_portfolio_amazon-sql-copilot.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_amazon-userscripts.jpg b/public/assets/img/previews/local_portfolio_amazon-userscripts.jpg
iindex b495390..a70b960 100644
BBinary files a/public/assets/img/previews/local_portfolio_amazon-userscripts.jpg and b/public/assets/img/previews/local_portfolio_amazon-userscripts.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_gameserverlists.jpg b/public/assets/img/previews/local_portfolio_gameserverlists.jpg
iindex 1347486..eeac8d3 100644
BBinary files a/public/assets/img/previews/local_portfolio_gameserverlists.jpg and b/public/assets/img/previews/local_portfolio_gameserverlists.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_gameserverstats.jpg b/public/assets/img/previews/local_portfolio_gameserverstats.jpg
ddeleted file mode 100644
iindex 07359fb..0000000
BBinary files a/public/assets/img/previews/local_portfolio_gameserverstats.jpg and /dev/null differ
ddiff --git a/public/assets/img/previews/local_portfolio_hartools.jpg b/public/assets/img/previews/local_portfolio_hartools.jpg
iindex 7426a1d..722ec2c 100644
BBinary files a/public/assets/img/previews/local_portfolio_hartools.jpg and b/public/assets/img/previews/local_portfolio_hartools.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_inbound-pull-tool.jpg b/public/assets/img/previews/local_portfolio_inbound-pull-tool.jpg
iindex 7fe2714..49b052b 100644
BBinary files a/public/assets/img/previews/local_portfolio_inbound-pull-tool.jpg and b/public/assets/img/previews/local_portfolio_inbound-pull-tool.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_irdr-autonomous-bot.jpg b/public/assets/img/previews/local_portfolio_irdr-autonomous-bot.jpg
iindex 3502749..95d26e7 100644
BBinary files a/public/assets/img/previews/local_portfolio_irdr-autonomous-bot.jpg and b/public/assets/img/previews/local_portfolio_irdr-autonomous-bot.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_irdr-quality-recap.jpg b/public/assets/img/previews/local_portfolio_irdr-quality-recap.jpg
iindex 6a18ce0..90fa210 100644
BBinary files a/public/assets/img/previews/local_portfolio_irdr-quality-recap.jpg and b/public/assets/img/previews/local_portfolio_irdr-quality-recap.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_live-translation.jpg b/public/assets/img/previews/local_portfolio_live-translation.jpg
iindex 45049a0..7eec15b 100644
BBinary files a/public/assets/img/previews/local_portfolio_live-translation.jpg and b/public/assets/img/previews/local_portfolio_live-translation.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_lyric-video-toolkit.jpg b/public/assets/img/previews/local_portfolio_lyric-video-toolkit.jpg
iindex f32b23b..7c022f5 100644
BBinary files a/public/assets/img/previews/local_portfolio_lyric-video-toolkit.jpg and b/public/assets/img/previews/local_portfolio_lyric-video-toolkit.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_mp-select-mini.jpg b/public/assets/img/previews/local_portfolio_mp-select-mini.jpg
iindex ed8e129..b9b7a00 100644
BBinary files a/public/assets/img/previews/local_portfolio_mp-select-mini.jpg and b/public/assets/img/previews/local_portfolio_mp-select-mini.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_openvibe-community.jpg b/public/assets/img/previews/local_portfolio_openvibe-community.jpg
iindex dabf2bb..347ec1b 100644
BBinary files a/public/assets/img/previews/local_portfolio_openvibe-community.jpg and b/public/assets/img/previews/local_portfolio_openvibe-community.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_openvibe-cutover.jpg b/public/assets/img/previews/local_portfolio_openvibe-cutover.jpg
iindex f625359..b1c6824 100644
BBinary files a/public/assets/img/previews/local_portfolio_openvibe-cutover.jpg and b/public/assets/img/previews/local_portfolio_openvibe-cutover.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_openvibe-live.jpg b/public/assets/img/previews/local_portfolio_openvibe-live.jpg
iindex 843ea32..8af4a26 100644
BBinary files a/public/assets/img/previews/local_portfolio_openvibe-live.jpg and b/public/assets/img/previews/local_portfolio_openvibe-live.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_openvibe-media.jpg b/public/assets/img/previews/local_portfolio_openvibe-media.jpg
iindex 1297efe..1ee16c7 100644
BBinary files a/public/assets/img/previews/local_portfolio_openvibe-media.jpg and b/public/assets/img/previews/local_portfolio_openvibe-media.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_openvibe-sites.jpg b/public/assets/img/previews/local_portfolio_openvibe-sites.jpg
iindex f857cd4..e70bcec 100644
BBinary files a/public/assets/img/previews/local_portfolio_openvibe-sites.jpg and b/public/assets/img/previews/local_portfolio_openvibe-sites.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_perf-harness.jpg b/public/assets/img/previews/local_portfolio_perf-harness.jpg
iindex 5b166d5..9c1608e 100644
BBinary files a/public/assets/img/previews/local_portfolio_perf-harness.jpg and b/public/assets/img/previews/local_portfolio_perf-harness.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_powerchat-developer-api.jpg b/public/assets/img/previews/local_portfolio_powerchat-developer-api.jpg
iindex b11b413..b420b1f 100644
BBinary files a/public/assets/img/previews/local_portfolio_powerchat-developer-api.jpg and b/public/assets/img/previews/local_portfolio_powerchat-developer-api.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_powerchat-payments.jpg b/public/assets/img/previews/local_portfolio_powerchat-payments.jpg
iindex 57a42cc..6795ee5 100644
BBinary files a/public/assets/img/previews/local_portfolio_powerchat-payments.jpg and b/public/assets/img/previews/local_portfolio_powerchat-payments.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_pytools-workshop.jpg b/public/assets/img/previews/local_portfolio_pytools-workshop.jpg
iindex 48b9adc..d3a085e 100644
BBinary files a/public/assets/img/previews/local_portfolio_pytools-workshop.jpg and b/public/assets/img/previews/local_portfolio_pytools-workshop.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_robotstreamer-toolkit.jpg b/public/assets/img/previews/local_portfolio_robotstreamer-toolkit.jpg
iindex 71e679e..0cae2cf 100644
BBinary files a/public/assets/img/previews/local_portfolio_robotstreamer-toolkit.jpg and b/public/assets/img/previews/local_portfolio_robotstreamer-toolkit.jpg differ
ddiff --git a/public/assets/img/previews/local_portfolio_youtube-desktop-client.jpg b/public/assets/img/previews/local_portfolio_youtube-desktop-client.jpg
iindex 34cb67c..54afcf8 100644
BBinary files a/public/assets/img/previews/local_portfolio_youtube-desktop-client.jpg and b/public/assets/img/previews/local_portfolio_youtube-desktop-client.jpg differ
ddiff --git a/public/assets/img/previews/openre.stream.jpg b/public/assets/img/previews/openre.stream.jpg
nnew file mode 100644
iindex 0000000..5f3b142
BBinary files /dev/null and b/public/assets/img/previews/openre.stream.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.blog.jpg b/public/assets/img/previews/openvibe.blog.jpg
nnew file mode 100644
iindex 0000000..900adbd
BBinary files /dev/null and b/public/assets/img/previews/openvibe.blog.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.chat.jpg b/public/assets/img/previews/openvibe.chat.jpg
iindex c51310c..57ee5f6 100644
BBinary files a/public/assets/img/previews/openvibe.chat.jpg and b/public/assets/img/previews/openvibe.chat.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.codes.jpg b/public/assets/img/previews/openvibe.codes.jpg
nnew file mode 100644
iindex 0000000..150417e
BBinary files /dev/null and b/public/assets/img/previews/openvibe.codes.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.community.jpg b/public/assets/img/previews/openvibe.community.jpg
iindex 8088ab6..43f8e6e 100644
BBinary files a/public/assets/img/previews/openvibe.community.jpg and b/public/assets/img/previews/openvibe.community.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.coupons.jpg b/public/assets/img/previews/openvibe.coupons.jpg
nnew file mode 100644
iindex 0000000..c31616e
BBinary files /dev/null and b/public/assets/img/previews/openvibe.coupons.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.deals.jpg b/public/assets/img/previews/openvibe.deals.jpg
nnew file mode 100644
iindex 0000000..743e75f
BBinary files /dev/null and b/public/assets/img/previews/openvibe.deals.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.games.jpg b/public/assets/img/previews/openvibe.games.jpg
iindex a2ff4c7..0c0b2bc 100644
BBinary files a/public/assets/img/previews/openvibe.games.jpg and b/public/assets/img/previews/openvibe.games.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.host.jpg b/public/assets/img/previews/openvibe.host.jpg
nnew file mode 100644
iindex 0000000..12af377
BBinary files /dev/null and b/public/assets/img/previews/openvibe.host.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.live.jpg b/public/assets/img/previews/openvibe.live.jpg
iindex 5801296..c07c7be 100644
BBinary files a/public/assets/img/previews/openvibe.live.jpg and b/public/assets/img/previews/openvibe.live.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.media.jpg b/public/assets/img/previews/openvibe.media.jpg
iindex 7a82c4e..d04d721 100644
BBinary files a/public/assets/img/previews/openvibe.media.jpg and b/public/assets/img/previews/openvibe.media.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.network.jpg b/public/assets/img/previews/openvibe.network.jpg
iindex 2105314..29c3f0b 100644
BBinary files a/public/assets/img/previews/openvibe.network.jpg and b/public/assets/img/previews/openvibe.network.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.news.jpg b/public/assets/img/previews/openvibe.news.jpg
nnew file mode 100644
iindex 0000000..d3787e5
BBinary files /dev/null and b/public/assets/img/previews/openvibe.news.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.reviews.jpg b/public/assets/img/previews/openvibe.reviews.jpg
nnew file mode 100644
iindex 0000000..3e09a41
BBinary files /dev/null and b/public/assets/img/previews/openvibe.reviews.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.tips.jpg b/public/assets/img/previews/openvibe.tips.jpg
nnew file mode 100644
iindex 0000000..7f9e803
BBinary files /dev/null and b/public/assets/img/previews/openvibe.tips.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.tools.jpg b/public/assets/img/previews/openvibe.tools.jpg
iindex a870f28..16d00ad 100644
BBinary files a/public/assets/img/previews/openvibe.tools.jpg and b/public/assets/img/previews/openvibe.tools.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.trade.jpg b/public/assets/img/previews/openvibe.trade.jpg
nnew file mode 100644
iindex 0000000..78695e3
BBinary files /dev/null and b/public/assets/img/previews/openvibe.trade.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.vip.jpg b/public/assets/img/previews/openvibe.vip.jpg
nnew file mode 100644
iindex 0000000..7d3be90
BBinary files /dev/null and b/public/assets/img/previews/openvibe.vip.jpg differ
ddiff --git a/public/assets/img/previews/openvibe.wiki.jpg b/public/assets/img/previews/openvibe.wiki.jpg
nnew file mode 100644
iindex 0000000..dca1c48
BBinary files /dev/null and b/public/assets/img/previews/openvibe.wiki.jpg differ
ddiff --git a/scripts/make-link-previews.mjs b/scripts/make-link-previews.mjs
iindex 9f0c1b9..5311b5d 100644
---- a/scripts/make-link-previews.mjs
++++ b/scripts/make-link-previews.mjs
@@@ -13,6 +13,7 @@ const W = 1280, H = 800, SCALE = 0.5; // 640x400 output
const external = [
...profile.network.map((n) => n.url),
++ ...(profile.networkPlanned || []).map((n) => n.url),
'https://powerchat.live',
'https://github.com/OpenVibers',
'https://alexfrison.net',
ddiff --git a/src/components/Now.astro b/src/components/Now.astro
iindex 955e05b..4c0066d 100644
---- a/src/components/Now.astro
++++ b/src/components/Now.astro
@@@ -5,7 +5,6 @@ import { linkify } from "../lib/linkify";
const LINK_TERMS = {
"OpenVibe": { href: "https://openvibe.network", external: true },
"PowerChat": { href: "https://powerchat.live", external: true },
-- "GameServerStats": { href: "/portfolio/gameserverstats" },
};
const pitchParts = linkify(profile.pitch, LINK_TERMS);
const statusNowParts = linkify(profile.status.now, LINK_TERMS);
ddiff --git a/src/components/ProjectGrid.astro b/src/components/ProjectGrid.astro
iindex dfbadcf..39a19da 100644
---- a/src/components/ProjectGrid.astro
++++ b/src/components/ProjectGrid.astro
@@@ -43,7 +43,7 @@ const cats = Array.from(new Set(projects.map((p) => p.data.category)));
<div class="pcard__top">
<span class="pcard__icon"><i class={p.data.icon ?? "fa-solid fa-cube"}></i></span>
<span class="pcard__cat">{catLabel[p.data.category] ?? p.data.category}</span>
-- {p.data.year && <span class="pcard__year mono">{p.data.year}</span>}
++ {p.data.status === "coming-soon" ? <span class="pcard__soon mono"><i class="fa-solid fa-clock"></i> Coming soon</span> : p.data.year && <span class="pcard__year mono">{p.data.year}</span>}
</div>
<h3 class="pcard__title">{p.data.title}</h3>
<p class="pcard__sum">{p.data.summary}</p>
@@@ -108,6 +108,7 @@ const cats = Array.from(new Set(projects.map((p) => p.data.category)));
.cat-infra .pcard__icon { background: rgba(139, 92, 246, .14); color: var(--violet); border-color: rgba(139, 92, 246, .25); }
.pcard__cat { font-family: var(--font-mono); font-size: .7rem; letter-spacing: .14em; text-transform: uppercase; color: var(--muted); }
.pcard__year { margin-left: auto; font-size: .7rem; color: var(--dim); }
++ .pcard__soon { margin-left: auto; display: inline-flex; align-items: center; gap: 6px; font-size: .66rem; letter-spacing: .08em; text-transform: uppercase; padding: 3px 9px; border-radius: 999px; color: var(--accent-3); background: rgba(var(--accent-3-rgb), .12); border: 1px solid rgba(var(--accent-3-rgb), .35); }
.pcard__title { margin: 0; font-size: 1.15rem; }
.pcard__sum { margin: 0; font-size: .9rem; color: var(--muted); line-height: 1.55; display: -webkit-box; -webkit-line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden; }
.pcard__tech { list-style: none; margin: auto 0 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
ddiff --git a/src/components/WorkShowcase.astro b/src/components/WorkShowcase.astro
iindex 97c2ea0..3fe56c6 100644
---- a/src/components/WorkShowcase.astro
++++ b/src/components/WorkShowcase.astro
@@@ -28,25 +28,25 @@ const groups = [
key: "openvibe", tone: "a", anchor: "network", icon: "fa-solid fa-diagram-project",
title: "The OpenVibe network", blurb: "Seven live services behind one sign-in, all built and run by me since 2026.",
count: profile.network.length, allHref: "/portfolio/?cat=openvibe,infra", allLabel: "All OpenVibe case notes",
-- items: [] as { href: string; external: boolean; image?: string | null; icon: string; title: string; sub: string; text: string; meta: string }[],
++ items: [] as { href: string; external: boolean; image?: string | null; icon: string; title: string; sub: string; text: string; meta: string; soon?: boolean }[],
},
{
key: "powerchat", tone: "b", icon: "fa-solid fa-bolt",
title: "PowerChat", blurb: "Contract platform work for a creator-monetization product: payments and the developer API.",
count: countCats(["powerchat"]), allHref: "/portfolio/?cat=powerchat", allLabel: "All PowerChat work",
-- items: byCats(["powerchat"], 4).map((p) => ({ href: `/portfolio/${p.slug}`, external: false, icon: p.data.icon ?? "fa-solid fa-cube", title: p.data.title, sub: catLabel[p.data.category], text: p.data.summary, meta: p.data.year ?? "" })),
++ items: byCats(["powerchat"], 4).map((p) => ({ href: `/portfolio/${p.slug}`, external: false, icon: p.data.icon ?? "fa-solid fa-cube", title: p.data.title, sub: catLabel[p.data.category], text: p.data.summary, meta: p.data.year ?? "", soon: p.data.status === "coming-soon" })),
},
{
key: "ai", tone: "c", icon: "fa-solid fa-robot",
title: "AI, data & automation", blurb: "Agents, analytics and tooling that did real work at Amazon and inside OpenVibe.",
count: countCats(["ai", "amazon"]), allHref: "/portfolio/?cat=ai,amazon", allLabel: "All AI & data work",
-- items: byCats(["ai", "amazon"], 4).map((p) => ({ href: `/portfolio/${p.slug}`, external: false, icon: p.data.icon ?? "fa-solid fa-cube", title: p.data.title, sub: catLabel[p.data.category], text: p.data.summary, meta: p.data.year ?? "" })),
++ items: byCats(["ai", "amazon"], 4).map((p) => ({ href: `/portfolio/${p.slug}`, external: false, icon: p.data.icon ?? "fa-solid fa-cube", title: p.data.title, sub: catLabel[p.data.category], text: p.data.summary, meta: p.data.year ?? "", soon: p.data.status === "coming-soon" })),
},
{
key: "more", tone: "d", icon: "fa-solid fa-shapes",
title: "Games, tools & side projects", blurb: "The builds that keep the tools sharp: a multiplayer game, desktop apps, hardware and utilities.",
count: countCats(["game", "tools", "python", "hardware", "web"]), allHref: "/portfolio/", allLabel: "The whole archive",
-- items: byCats(["game", "tools", "python", "hardware", "web"], 4).map((p) => ({ href: `/portfolio/${p.slug}`, external: false, icon: p.data.icon ?? "fa-solid fa-cube", title: p.data.title, sub: catLabel[p.data.category], text: p.data.summary, meta: p.data.year ?? "" })),
++ items: byCats(["game", "tools", "python", "hardware", "web"], 4).map((p) => ({ href: `/portfolio/${p.slug}`, external: false, icon: p.data.icon ?? "fa-solid fa-cube", title: p.data.title, sub: catLabel[p.data.category], text: p.data.summary, meta: p.data.year ?? "", soon: p.data.status === "coming-soon" })),
},
];
---
@@@ -63,7 +63,7 @@ const groups = [
<div class="wk">
{groups.map((g, gi) => (
-- <section class={`wk-group wk-group--${g.tone}`} id={g.anchor} aria-labelledby={`wk-${g.key}`} data-reveal style={`--i:${gi}`}>
++ <section class={`wk-group wk-group--${g.tone}`} id={g.anchor} aria-labelledby={`wk-${g.key}`} style={`--i:${gi}`}>
<header class="wk-group__head">
<span class="wk-group__ico"><i class={g.icon} aria-hidden="true"></i></span>
<div class="wk-group__txt">
@@@ -73,21 +73,31 @@ const groups = [
<a class="wk-group__all" href={g.allHref}><i class="fa-solid fa-grip" aria-hidden="true"></i> <span>{g.allLabel}</span> <i class="fa-solid fa-arrow-right wk-group__arrow" aria-hidden="true"></i></a>
</header>
{g.key === "openvibe" && <OpenVibeBento />}
++ {g.key === "openvibe" && (
++ <div class="wk-planned" aria-label="Planned OpenVibe sites">
++ <span class="wk-planned__label"><i class="fa-solid fa-clock" aria-hidden="true"></i> Coming soon</span>
++ <ul class="wk-planned__list">
++ {profile.networkPlanned.map((n) => (
++ <li><a href={n.url} target="_blank" rel="noopener" title={`${n.name}: ${n.role} (placeholder page)`}>{n.name.replace("OpenVibe.", ".").replace("OpenRe.", "OpenRe.")}<small>{n.role}</small></a></li>
++ ))}
++ </ul>
++ </div>
++ )}
<ul class="wk-grid" data-stagger hidden={g.items.length === 0}>
{g.items.map((it) => (
-- <li class={`wk-card ${it.image ? "wk-card--img" : ""}`}>
++ <li class={`wk-card ${it.image ? "wk-card--img" : ""} ${it.soon ? "wk-card--soon" : ""}`}>
<a href={it.href} class="wk-card__link" target={it.external ? "_blank" : undefined} rel={it.external ? "noopener" : undefined}>
{it.image && <span class="wk-card__thumb" aria-hidden="true"><img src={it.image} alt="" loading="lazy" width="640" height="240" /></span>}
<span class="wk-card__body">
<span class="wk-card__top">
<span class="wk-card__ico"><i class={it.icon} aria-hidden="true"></i></span>
{!it.image && <span class="wk-card__sub">{it.sub}</span>}
-- {it.meta && <span class="wk-card__meta mono">{it.meta}</span>}
++ {it.soon ? <span class="wk-card__soon mono"><i class="fa-solid fa-clock" aria-hidden="true"></i> Coming soon</span> : it.meta && <span class="wk-card__meta mono">{it.meta}</span>}
</span>
<span class="wk-card__title">{it.title}</span>
{it.image && <span class="wk-card__role">{it.sub}</span>}
<span class="wk-card__text">{it.text}</span>
-- <span class="wk-card__go"><span>{it.external ? "Open site" : "Case notes"}</span> <i class={it.external ? "fa-solid fa-arrow-up-right-from-square" : "fa-solid fa-arrow-right"} aria-hidden="true"></i></span>
++ <span class="wk-card__go"><span>{it.soon ? "Preview the plan" : it.external ? "Open site" : "Case notes"}</span> <i class={it.external ? "fa-solid fa-arrow-up-right-from-square" : "fa-solid fa-arrow-right"} aria-hidden="true"></i></span>
</span>
</a>
</li>
@@@ -150,6 +160,16 @@ const groups = [
.wk-card__go i { transition: transform .3s var(--ease); font-size: .7rem; }
.wk-card__link:hover .wk-card__go i { transform: translateX(3px); }
.mono { font-family: var(--font-mono); }
++ .wk-card__soon { margin-left: auto; display: inline-flex; align-items: center; gap: 5px; font-size: .6rem; letter-spacing: .08em; text-transform: uppercase; padding: 2px 8px; border-radius: 999px; color: var(--accent-3); background: rgba(var(--accent-3-rgb), .12); border: 1px solid rgba(var(--accent-3-rgb), .35); white-space: nowrap; }
++ .wk-card--soon .wk-card__link { border-style: dashed; }
++ /* Planned domains: every one already has a truthful "coming soon" page, so the chips link there. */
++ .wk-planned { display: flex; flex-wrap: wrap; align-items: center; gap: 10px 14px; margin-top: 14px; padding: 12px 14px; border-radius: 14px; border: 1px dashed var(--border-strong); background: var(--surface); }
++ .wk-planned__label { display: inline-flex; align-items: center; gap: 7px; font: 600 .68rem var(--font-mono); letter-spacing: .14em; text-transform: uppercase; color: var(--accent-3); }
++ .wk-planned__list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: 6px; }
++ .wk-planned__list a { display: inline-flex; align-items: baseline; gap: 6px; padding: 5px 10px; border-radius: 999px; border: 1px solid var(--border); background: var(--surface-2); color: var(--fg); font: 600 .74rem var(--font-body); text-decoration: none; transition: border-color .25s var(--ease), transform .25s var(--ease); }
++ .wk-planned__list a small { font: 500 .62rem var(--font-mono); color: var(--dim); letter-spacing: .04em; }
++ .wk-planned__list a:hover { text-decoration: none; border-color: rgba(var(--accent-3-rgb), .55); transform: translateY(-1px); }
++ @media (max-width: 560px) { .wk-planned__list a small { display: none; } }
.work__more { margin-top: clamp(22px, 3vw, 34px); display: flex; justify-content: center; }
.work__all-btn {
ddiff --git a/src/content/config.ts b/src/content/config.ts
iindex 2725828..7da782e 100644
---- a/src/content/config.ts
++++ b/src/content/config.ts
@@@ -39,6 +39,7 @@ const projects = defineCollection({
video: z.string().url().optional(),
}).default({}),
featured: z.boolean().default(false),
++ status: z.enum(['live', 'coming-soon', 'archived']).default('live'),
order: z.number().default(0),
}),
});
ddiff --git a/src/content/experience/06-gameserverstats.json b/src/content/experience/06-gameserverstats.json
iindex 6ed4bdf..06bb024 100644
---- a/src/content/experience/06-gameserverstats.json
++++ b/src/content/experience/06-gameserverstats.json
@@@ -8,7 +8,7 @@
"bullets": [
"Built analytics platforms tracking 200,000+ live game servers: ingestion, search, ranking, dashboards and licensed partner APIs. Ranked first on Google for several major titles.",
"Self-built and collocated 1U and 2U servers in datacenters; designed for scale and DDoS resilience and ran the whole LLC, from licenses and taxes to partner relations.",
-- "Rebuilt the idea in 2026 as GameServerLists: a 14-service, game-agnostic server-discovery platform with multi-region probes, time-weighted ranking, an anti-fraud integrity engine and a tiered public API."
++ "Rebuilding the idea in 2026 as GameServerLists (launching soon): a 14-service, game-agnostic server-discovery platform with multi-region probes, time-weighted ranking, an anti-fraud integrity engine and a tiered public API."
],
"stack": [
"Node.js",
@@@ -21,4 +21,4 @@
"NGINX",
"Linux"
]
--}
\\ No newline at end of file
++}
ddiff --git a/src/content/projects/gameserverlists.mdx b/src/content/projects/gameserverlists.mdx
iindex 439bed2..1c3db54 100644
---- a/src/content/projects/gameserverlists.mdx
++++ b/src/content/projects/gameserverlists.mdx
@@@ -1,10 +1,11 @@
---
title: "GameServerLists"
category: "web"
--summary: "2026 rebuild of the server-tracking idea as a 14-service, game-agnostic discovery platform: multi-region probes, time-weighted ranking, anti-fraud integrity engine and a tiered public API."
++summary: "Launching soon: a 14-service, game-agnostic server-discovery platform with multi-region probes, time-weighted ranking, an anti-fraud integrity engine and a tiered public API."
tech: ["TypeScript", "pnpm monorepo", "TimescaleDB", "Redis", "A2S / Minecraft SLP / EOS", "WebSockets", "Provider-abstracted AI"]
role: "Architect · Sole Engineer"
--year: "2026"
++year: "Coming soon"
++status: "coming-soon"
icon: "fa-solid fa-list-check"
order: 13
---
ddiff --git a/src/content/projects/gameserverstats.mdx b/src/content/projects/gameserverstats.mdx
ddeleted file mode 100644
iindex 97a9c08..0000000
---- a/src/content/projects/gameserverstats.mdx
++++ /dev/null
@@@ -1,15 +0,0 @@
-----
--title: "GameServerStats"
--category: "web"
--summary: "Analytics platform tracking 200,000+ live game servers — search, ranking, dashboards, and partner APIs."
--tech: ["Node.js", "PHP", "MongoDB", "Redis", "NGINX", "Linux"]
--role: "Founder / CEO / Sole Engineer"
--year: "2015–2026"
--icon: "fa-solid fa-server"
--order: 30
-----
--
--A decade of analytics for the multiplayer-game-server world. Custom
--ingestion fetches and computes stats on hundreds of thousands of servers,
--serves the #1 Google result for several major games, and licensed to
--third-party sites under written agreement.
ddiff --git a/src/data/link-previews.json b/src/data/link-previews.json
iindex be76a76..c7e0e64 100644
---- a/src/data/link-previews.json
++++ b/src/data/link-previews.json
@@@ -27,6 +27,54 @@
"img": "/assets/img/previews/openvibe.chat.jpg",
"label": "openvibe.chat"
},
++ "openre.stream": {
++ "img": "/assets/img/previews/openre.stream.jpg",
++ "label": "openre.stream"
++ },
++ "openvibe.tips": {
++ "img": "/assets/img/previews/openvibe.tips.jpg",
++ "label": "openvibe.tips"
++ },
++ "openvibe.vip": {
++ "img": "/assets/img/previews/openvibe.vip.jpg",
++ "label": "openvibe.vip"
++ },
++ "openvibe.codes": {
++ "img": "/assets/img/previews/openvibe.codes.jpg",
++ "label": "openvibe.codes"
++ },
++ "openvibe.host": {
++ "img": "/assets/img/previews/openvibe.host.jpg",
++ "label": "openvibe.host"
++ },
++ "openvibe.wiki": {
++ "img": "/assets/img/previews/openvibe.wiki.jpg",
++ "label": "openvibe.wiki"
++ },
++ "openvibe.blog": {
++ "img": "/assets/img/previews/openvibe.blog.jpg",
++ "label": "openvibe.blog"
++ },
++ "openvibe.news": {
++ "img": "/assets/img/previews/openvibe.news.jpg",
++ "label": "openvibe.news"
++ },
++ "openvibe.reviews": {
++ "img": "/assets/img/previews/openvibe.reviews.jpg",
++ "label": "openvibe.reviews"
++ },
++ "openvibe.deals": {
++ "img": "/assets/img/previews/openvibe.deals.jpg",
++ "label": "openvibe.deals"
++ },
++ "openvibe.coupons": {
++ "img": "/assets/img/previews/openvibe.coupons.jpg",
++ "label": "openvibe.coupons"
++ },
++ "openvibe.trade": {
++ "img": "/assets/img/previews/openvibe.trade.jpg",
++ "label": "openvibe.trade"
++ },
"powerchat.live": {
"img": "/assets/img/previews/powerchat.live.jpg",
"label": "powerchat.live"
@@@ -63,10 +111,6 @@
"img": "/assets/img/previews/local_portfolio_gameserverlists.jpg",
"label": "alexfrison.net/portfolio/gameserverlists"
},
-- "path:/portfolio/gameserverstats": {
-- "img": "/assets/img/previews/local_portfolio_gameserverstats.jpg",
-- "label": "alexfrison.net/portfolio/gameserverstats"
-- },
"path:/portfolio/hartools": {
"img": "/assets/img/previews/local_portfolio_hartools.jpg",
"label": "alexfrison.net/portfolio/hartools"
ddiff --git a/src/data/profile.json b/src/data/profile.json
iindex 80cc0a7..9e94cde 100644
---- a/src/data/profile.json
++++ b/src/data/profile.json
@@@ -70,6 +70,21 @@
{ "key": "community", "name": "OpenVibe.Community", "url": "https://openvibe.community", "role": "Pastes & posts", "blurb": "Server-rendered paste hub with forks, search, feeds and silent SSO, proxied to Live so identity stays consistent.", "stat": "No DB of its own", "image": "/assets/img/sites/community.webp" },
{ "key": "sites", "name": "OpenVibe.Sites", "url": "https://openvibe.chat", "role": "13 front doors", "blurb": "One catalog and one template generate the pages and nginx configs for openvibe.chat, .codes, .wiki, .news and more.", "stat": "13 domains · 1 build", "image": null }
],
++ "networkPlanned": [
++ { "name": "OpenRe.Stream", "url": "https://openre.stream", "role": "Restreaming" },
++ { "name": "OpenVibe.Chat", "url": "https://openvibe.chat", "role": "Chat & calls" },
++ { "name": "OpenVibe.Tips", "url": "https://openvibe.tips", "role": "Creator tips" },
++ { "name": "OpenVibe.VIP", "url": "https://openvibe.vip", "role": "Memberships" },
++ { "name": "OpenVibe.Codes", "url": "https://openvibe.codes", "role": "Developer portal" },
++ { "name": "OpenVibe.Host", "url": "https://openvibe.host", "role": "Hosting" },
++ { "name": "OpenVibe.Wiki", "url": "https://openvibe.wiki", "role": "Wiki" },
++ { "name": "OpenVibe.Blog", "url": "https://openvibe.blog", "role": "Blogs" },
++ { "name": "OpenVibe.News", "url": "https://openvibe.news", "role": "News" },
++ { "name": "OpenVibe.Reviews", "url": "https://openvibe.reviews", "role": "Reviews" },
++ { "name": "OpenVibe.Deals", "url": "https://openvibe.deals", "role": "Deals" },
++ { "name": "OpenVibe.Coupons", "url": "https://openvibe.coupons", "role": "Coupons" },
++ { "name": "OpenVibe.Trade", "url": "https://openvibe.trade", "role": "Markets" }
++ ],
"education": "Snohomish High School, WA. AP Statistics, Calculus, English, Creative Writing and Spanish. Self-taught engineer since age 12: systems, infrastructure, graphics, hardware and whatever the problem needs.",
"capabilities": ["websites", "platforms", "APIs", "dashboards", "AI features", "automations", "identity systems", "payment systems", "real-time chat", "streaming infra", "whatever you need"]
}
ddiff --git a/src/layouts/Base.astro b/src/layouts/Base.astro
iindex a36e7e3..dd7230c 100644
---- a/src/layouts/Base.astro
++++ b/src/layouts/Base.astro
@@@ -248,12 +248,14 @@ const imageUrl = new URL(image, site).toString();
const el = en.target as HTMLElement;
const kind = el.dataset.reveal || 'up';
const delay = Number(el.dataset.delay || 0);
-- const from: Keyframe = kind === 'left' ? { opacity: 0, transform: 'translateX(-30px)' }
-- : kind === 'right' ? { opacity: 0, transform: 'translateX(30px)' }
-- : kind === 'scale' ? { opacity: 0, transform: 'scale(.94)' }
-- : { opacity: 0, transform: 'translateY(26px)' };
++ // Offsets stay small (and never accumulate on nested [data-reveal] ancestors): larger ones
++ // made cards visibly overlap the next card mid-animation when someone scrolls fast.
++ const from: Keyframe = kind === 'left' ? { opacity: 0, transform: 'translateX(-12px)' }
++ : kind === 'right' ? { opacity: 0, transform: 'translateX(12px)' }
++ : kind === 'scale' ? { opacity: 0, transform: 'scale(.975)' }
++ : { opacity: 0, transform: 'translateY(8px) scale(.99)' };
el.style.opacity = '1'; el.style.transform = 'none';
-- el.animate([from, { opacity: 1, transform: 'none' }], { duration: 750, delay, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'backwards' });
++ el.animate([from, { opacity: 1, transform: 'none' }], { duration: 600, delay, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'backwards' });
io.unobserve(el);
}
}, { rootMargin: '0px 0px -8% 0px', threshold: 0.06 });
@@@ -266,8 +268,8 @@ const imageUrl = new URL(image, site).toString();
const kids = Array.from((en.target as HTMLElement).children) as HTMLElement[];
kids.forEach((k, i) => {
k.style.opacity = '1';
-- k.animate([{ opacity: 0, transform: 'translateY(22px)' }, { opacity: 1, transform: 'none' }],
-- { duration: 650, delay: i * 70, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'backwards' });
++ k.animate([{ opacity: 0, transform: 'translateY(8px) scale(.99)' }, { opacity: 1, transform: 'none' }],
++ { duration: 550, delay: Math.min(i, 8) * 55, easing: 'cubic-bezier(0.22, 1, 0.36, 1)', fill: 'backwards' });
});
sio.unobserve(en.target);
}
ddiff --git a/src/pages/portfolio/[slug].astro b/src/pages/portfolio/[slug].astro
iindex ec2cfde..c81f077 100644
---- a/src/pages/portfolio/[slug].astro
++++ b/src/pages/portfolio/[slug].astro
@@@ -26,7 +26,7 @@ const catLabel: Record<string, string> = {
<header class="pp__head" data-reveal>
<div class="pp__meta-top">
<span class="chip">{catLabel[d.category] ?? d.category}</span>
-- {d.year && <span class="mono pp__year">{d.year}</span>}
++ {d.status === "coming-soon" ? <span class="mono pp__soon"><i class="fa-solid fa-clock"></i> Coming soon</span> : d.year && <span class="mono pp__year">{d.year}</span>}
</div>
<h1 class="pp__title">{d.title}</h1>
<p class="pp__lead">{d.summary}</p>
@@@ -82,6 +82,7 @@ const catLabel: Record<string, string> = {
.pp__back:hover { color: var(--fg); text-decoration: none; }
.pp__meta-top { display: flex; align-items: center; gap: 12px; margin-bottom: 14px; }
.pp__year { color: var(--dim); font-size: .8rem; }
++ .pp__soon { display: inline-flex; align-items: center; gap: 6px; font-size: .7rem; letter-spacing: .08em; text-transform: uppercase; padding: 4px 10px; border-radius: 999px; color: var(--accent-3); background: rgba(var(--accent-3-rgb), .12); border: 1px solid rgba(var(--accent-3-rgb), .35); }
.pp__title { font-size: clamp(2.2rem, 5.5vw, 4rem); margin: 0 0 .3em; }
.pp__lead { font-size: clamp(1.05rem, 1.6vw, 1.3rem); color: var(--fg); max-width: 62ch; line-height: 1.6; }
.pp__meta { display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 14px; margin: 1.4rem 0; }
ddiff --git a/src/pages/resume.astro b/src/pages/resume.astro
iindex 5336766..70e17c3 100644
---- a/src/pages/resume.astro
++++ b/src/pages/resume.astro
@@@ -12,7 +12,6 @@ const projects = (await getCollection("projects")).filter((p) => p.data.featured
const LINK_TERMS = {
"OpenVibe": { href: "https://openvibe.network", external: true },
"PowerChat": { href: "https://powerchat.live", external: true },
-- "GameServerStats": { href: "/portfolio/gameserverstats" },
};
const pitchParts = linkify(profile.pitch, LINK_TERMS);
---