Compare commits

..

No commits in common. "main" and "v1.3.71" have entirely different histories.

16 changed files with 79 additions and 241 deletions

View file

@ -22,30 +22,13 @@ $ pnpm i
## Commands
| Command | Action |
| ----------------------------- | ---------------------------------------------- |
| `pnpm run dev` | start dev server |
| `pnpm run build` | generate static files to `dist` directory |
| `pnpm run deploy` | upload to my server |
| `pnpm run prep:now` | back up and clear current `now page` constants |
| `pnpm run patch:build:deploy` | increment version, build, then upload |
## Testing
This project uses Vitest for unit tests. The test script is defined in `package.json` as `vitest run .`.
Run tests locally with pnpm (recommended):
```bash
# install dependencies (if you haven't already)
pnpm i
# run tests once
pnpm test
# run Vitest in interactive/watch mode
pnpm exec vitest
```
| Command | Action |
| ------------------- | ---------------------------------------------- |
| `pnpm run dev` | start dev server |
| `pnpm run build` | generate static files to `dist` directory |
| `pnpm run deploy` | upload to my server |
| `pnpm run prep:now` | back up and clear current `now page` constants |
| `pnpm run patch:build:deploy` | increment version, build, then upload |
## Deployment

View file

@ -1,7 +1,7 @@
{
"name": "@ayco/personal-website",
"type": "module",
"version": "1.3.95",
"version": "1.3.71",
"private": true,
"scripts": {
"astro": "astro",
@ -11,12 +11,10 @@
"preview": "astro preview",
"lint": "eslint . --config eslint.config.mjs --cache",
"lint:fix": "eslint . --config eslint.config.mjs --fix",
"fmt": "prettier . --config prettier.config.mjs --check",
"fmt:fix": "prettier . --config prettier.config.mjs --write",
"check": "npm run fmt && npm run lint",
"format": "prettier . --write",
"check": "npm run format && npm run lint",
"deploy": "eval $(grep '^HOST' .env) && scp -r dist ayo@$HOST:~/ayco.io-flask",
"patch:build:deploy": "npm version patch && npm run build && npm run deploy",
"pbd": "npm run patch:build:deploy",
"build:preview": "npm run build && astro preview",
"build:deploy": "npm run build && npm run deploy",
"copy:dist": "npm run build && cp -R dist ../ayco.io-flask/",
@ -52,7 +50,6 @@
"rehype-stringify": "^10.0.1",
"remark-parse": "^11.0.0",
"remark-rehype": "^11.1.2",
"sharp": "^0.34.5",
"tslib": "^2.8.1",
"typescript": "^6.0.3",
"typescript-eslint": "^8.59.3",
@ -63,14 +60,14 @@
},
"lint-staged": {
"*.{js,mjs,astro,ts}": [
"prettier . --config prettier.config.mjs --write",
"eslint . --config eslint.config.mjs --fix"
"prettier --write",
"eslint --fix"
],
"*.json": [
"prettier . --config prettier.config.mjs --write"
"prettier --write"
]
},
"packageManager": "pnpm@11.5.2+sha512.71c631e382066efc25625d5cf029075de07b61b37f6e27350fbd84b1bda5864c8c1967adc280776b45c30a715c0359a3be08fef42d5bb09e2b99029979692916",
"packageManager": "pnpm@11.2.2",
"dependencies": {
"@ayo-run/status-indicator": "^2.1.2",
"web-component-base": "^4.1.2"

View file

@ -96,9 +96,6 @@ importers:
remark-rehype:
specifier: ^11.1.2
version: 11.1.2
sharp:
specifier: ^0.34.5
version: 0.34.5
tslib:
specifier: ^2.8.1
version: 2.8.1
@ -3258,7 +3255,8 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@img/colour@1.1.0': {}
'@img/colour@1.1.0':
optional: true
'@img/sharp-darwin-arm64@0.34.5':
optionalDependencies:
@ -4124,7 +4122,8 @@ snapshots:
destr@2.0.5: {}
detect-libc@2.1.2: {}
detect-libc@2.1.2:
optional: true
devalue@5.8.1: {}
@ -5776,6 +5775,7 @@ snapshots:
'@img/sharp-win32-arm64': 0.34.5
'@img/sharp-win32-ia32': 0.34.5
'@img/sharp-win32-x64': 0.34.5
optional: true
shebang-command@2.0.0:
dependencies:

View file

@ -1,7 +0,0 @@
{
"title": "Social Web & Web Components",
"description": "Getting into discussions about improving the social web & building components",
"publishDate": "2026-05-12",
"publishedOn": "",
"publishState": ""
}

View file

@ -1,13 +0,0 @@
I bought a pull-up bar, and in addition to a few barbells at home, I am starting to have a mini gym.
Super happy that I am getting healthier as I build my workout routines.
To keep my mind sharp, I find it helpful to have several hobby things I can get back to from time to time.
I am in a few discussions for exploring some things in the open social web. Details can come later as they become more concrete.
Soft launch of my own newsletter is up on the canary version of my blog: https://main.ayos.blog/subscribe -- because email is the first decentralized social platform. Let's explore that idea more in the coming months.
I'm also back on my webcomponents main arc, after some side quests in self-hosting & machine learning.
Links to come later. Cheers!

View file

@ -57,7 +57,7 @@ export const footerLinks: Link[] = [
},
{
text: 'Mastodon',
url: 'https://elk.zone/m.webtoo.ls/@ayo',
url: 'https://main.elk.zone/social.ayco.io/@ayo',
icon: 'mastodon',
},
{

View file

@ -1,7 +1,7 @@
{
"title": "Fam++, Crypto, ~AI, OSS",
"description": "Navigating changes in the family and the tech industry",
"publishDate": "2026-06-18",
"publishedOn": "the 18th of June 2026",
"publishState": "in Amsterdam"
"title": "Social Web & Web Components",
"description": "Getting into discussions about improving the social web & building components",
"publishDate": "2026-05-12",
"publishedOn": "",
"publishState": ""
}

View file

@ -1,21 +1,13 @@
## 2 vs 2 in the house
I bought a pull-up bar, and in addition to a few barbells at home, I am starting to have a mini gym.
A new challenger is entering the game. It is safe to share now that we're expecting a new addition to the family: a baby girl is joining us later this year. I have been enjoying having a mini-me in the form of my lil kiddo, Kahel, and now it's time for Jen to have hers.
Super happy that I am getting healthier as I build my workout routines.
## Cryptography
To keep my mind sharp, I find it helpful to have several hobby things I can get back to from time to time.
The latest rabbit hole for my tech adventures is on [cryptography](https://elk.zone/m.webtoo.ls/@ayo@ayco.io/116667802537651710); particularly OpenPGP stuff and the implementation on signing & verifying emails. Check [my public key](/pgp) if you want to exchange encrypted messages :)
I am in a few discussions for exploring some things in the open social web. Details can come later as they become more concrete.
Technically this key-pair cryptography can be applied to any data exchange, so I naturally have been ruminating on how we can bring this to the decentralized social web.
Soft launch of my own newsletter is up on the canary version of my blog: https://main.ayos.blog/subscribe -- because email is the first decentralized social platform. Let's explore that idea more in the coming months.
## "AI"
I'm also back on my webcomponents main arc, after some side quests in self-hosting & machine learning.
I disassembled the eGPU setup I have for experimenting with local LLM inference and built a new PC. It is now a dedicated 24/7 "AI" computer at home that powers my [Open WebUI](https://ai.ayo.run) service. Additionally it runs [hermes](https://hermes-agent.nousresearch.com/) which is a "totally safe private local agentic AI" architecture -- and I got to say, it is the most successful one I've tried yet with local AI stuff.
Where am I using this? I don't know yet. It's purely for understanding the "agentic" stuff
## Open source
- [Elk](https://elk.zone) - I try to help out reviewing PRs and will pick up coding work again
- [Webtoo.ls](https://webtoo.ls) - I picked up administration work for this and have BIG dreams on bringing more Web ecosystem open source presence to the Fediverse
- [others](https://git.ayo.run/ayo) - A lot of my other explorations are out in the open
Links to come later. Cheers!

View file

@ -15,7 +15,7 @@ import { getImage } from 'astro:assets'
// fetch mastodon account
const response = await fetch(
'https://m.webtoo.ls/api/v1/accounts/lookup?acct=ayo'
'https://social.ayco.io/api/v1/accounts/lookup?acct=ayo'
)
const data = await response.json()
const { avatar } = data
@ -51,28 +51,13 @@ const ogFileType = 'image/png'
font-size: var(--font-size-base);
color: var(--text-color-dark);
background-color: var(--text-color-light);
background-image: radial-gradient(
circle,
hsl(var(--dot-grid-light)) 1px,
transparent 1px
);
background-size: 24px 24px;
background-position: -24px -24px;
}
@media (prefers-color-scheme: dark) {
html,
body,
* {
background-color: var(--bg-darker);
background: var(--bg-darker);
color: var(--text-color-light);
background-image: radial-gradient(
circle,
hsl(var(--dot-grid-dark)) 1px,
transparent 1px
);
}
}

View file

@ -26,7 +26,4 @@
--bg-dark: #343a40;
--bg-darker: #212529;
--bg-darkest: #000;
--dot-grid-light: 214 32% 82%;
--dot-grid-dark: 215 25% 25%;
}

View file

@ -56,6 +56,11 @@ import Footer from '../components/Footer.astro'
✉️ Email me: <a href="mailto:ayo@ayco.io">ayo@ayco.io</a> &middot
<small> (<a href="/pgp">PGP key</a>)</small>
</li>
<li>
💬 Let's chat: <a href="https://chat.ayo.run/join/7IKG-h3nW-pD1H"
>chat.ayo.run</a
>
</li>
<li>
💬 Signal: <a
href="https://signal.me/#eu/mU2KHaMuoumvLaq7P5ZPUU4HMd0SaU9hYHeEPbDIeJzysNL01FVLfbk-kVEncfIz"

View file

@ -8,7 +8,7 @@ import { Picture } from 'astro:assets'
// fetch mastodon account
const response = await fetch(
'https://m.webtoo.ls/api/v1/accounts/lookup?acct=ayo'
'https://social.ayco.io/api/v1/accounts/lookup?acct=ayo'
)
const data = await response.json()
const { avatar, note } = data
@ -38,15 +38,22 @@ const avatarSize = 150
</h1>
<a href="/now" class="now-wrapper action primary">
<span class="now-label">
<status-indicator status="active" pulse> now </status-indicator>
</span>
<span class="now-label">now</span>
<span class="status">{now.title}</span>
</a>
<a
href="https://chat.ayo.run/join/7IKG-h3nW-pD1H"
class="chat-link action"
>
<status-indicator id="chat-link" pulse status="positive">
Chat
</status-indicator>
</a>
</div>
</div>
</section>
<main class=".dot-grid">
<main>
<section class="introduction-section">
<p>
I care about the <em>Web</em>, and I love to <em>create</em> stuff to <em

View file

@ -1,72 +0,0 @@
---
import Layout from '../../../../layouts/Layout.astro'
import Footer from '../../../../components/Footer.astro'
const title = `Social Web & Web Components`
const description = `Getting into discussions about improving the social web & building components`
let publishedOn = ``
const publishDate = `2026-05-12`
const publishState = ``
const content = `<p>I bought a pull-up bar, and in addition to a few barbells at home, I am starting to have a mini gym.</p>
<p>Super happy that I am getting healthier as I build my workout routines.</p>
<p>To keep my mind sharp, I find it helpful to have several hobby things I can get back to from time to time.</p>
<p>I am in a few discussions for exploring some things in the open social web. Details can come later as they become more concrete.</p>
<p>Soft launch of my own newsletter is up on the canary version of my blog: https://main.ayos.blog/subscribe -- because email is the first decentralized social platform. Let's explore that idea more in the coming months.</p>
<p>I'm also back on my webcomponents main arc, after some side quests in self-hosting &#x26; machine learning.</p>
<p>Links to come later. Cheers!</p>`
publishedOn = publishedOn === '' ? publishDate : publishedOn
---
<Layout title={title} description={description}>
<main>
<h1><span class="text-gradient">{title}</span></h1>
<p>
<small>
Published on
<time datetime={publishDate}>
{publishedOn}
</time>
{publishState}
</small>
</p>
<Fragment set:html={content} />
</main>
<Footer />
</Layout>
<style>
.text-gradient {
font-weight: 900;
background-image: var(--ayo-gradient);
animation: pulse 4s ease-in-out infinite;
background-size: 500% 500%;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-size: 100% 200%;
background-position-y: 100%;
border-radius: 0.4rem;
}
.highlighted-content {
margin: 1rem 0;
background: #4f39fa;
padding: 1rem;
border-radius: 0.4rem;
color: var(--color-bg);
}
.highlighted-content code {
font-size: var(--font-size-base);
border: 0.1em solid var(--color-border);
border-radius: 4px;
padding: 0.15em 0.25em;
}
.link-card-grid {
display: grid;
gap: 1rem;
padding: 0;
}
</style>

View file

@ -4,21 +4,16 @@ import Footer from '../components/Footer.astro'
const title = 'PGP public key'
const description =
'Use to verify my digital signature or to encrypt messages intended only for me.'
'Use this to verify digitally signed content like emails and files'
---
<Layout title={"Ayo's " + title} description={description}>
<Layout title="Ayo's {title}" description={description}>
<main>
<h1>My {title}</h1>
<p>{description}</p>
<div class="key-block" role="region" aria-labelledby="public-key">
<div class="btn-wrapper">
<button class="copy-btn btn" aria-label="Copy key to clipboard"
>Copy</button
>
<a href="/publickey.asc" class="btn">Download</a>
</div>
<button class="copy-btn" aria-label="Copy key to clipboard">Copy</button>
<pre
id="public-key"><code>
-----BEGIN PGP PUBLIC KEY BLOCK-----
@ -36,42 +31,32 @@ AP0YLC768PFTBm9CM5T1BE0xjJ7s4dZSrVoI4n8RSe1nCA==
-----END PGP PUBLIC KEY BLOCK-----
</code></pre>
</div>
<p>Expiry Date: 2027-05-30</p>
<p>
Fingerprint: <code>17F1 3D5E 8FF7 372B 1354 5C38 65E6 BF64 1529 3C65</code
>
</p>
<p></p>
<p>
<a href="/publickey.asc" class="download-link"> Download key (asc) </a>
</p>
<p>This key will expire on: 2027-05-30</p>
</main>
<Footer />
</Layout>
<script>
/* Clipboard copy logic */
const copyBtn = document.querySelector('.copy-btn')
if (copyBtn) {
copyBtn.addEventListener('click', async () => {
const keyText = (
document.querySelector('#public-key code') as HTMLElement
)?.innerText.trim()
if (!keyText) return
try {
await navigator.clipboard.writeText(keyText)
const btn = document.querySelector('.copy-btn')
if (btn) {
const original = btn.textContent
btn.textContent = 'Copied!'
setTimeout(() => {
btn.textContent = original
}, 2000)
}
} catch (err) {
console.error('Copy failed', err)
alert('Unable to copy the key. Please copy it manually.')
}
})
}
document.querySelector('.copy-btn').addEventListener('click', async () => {
const keyText = document.querySelector('#public-key code').innerText.trim()
try {
await navigator.clipboard.writeText(keyText)
const btn = document.querySelector('.copy-btn')
const original = btn.textContent
btn.textContent = 'Copied!'
setTimeout(() => {
btn.textContent = original
}, 2000)
} catch (err) {
console.error('Copy failed', err)
alert('Unable to copy the key. Please copy it manually.')
}
})
</script>
<style>
@ -98,16 +83,11 @@ AP0YLC768PFTBm9CM5T1BE0xjJ7s4dZSrVoI4n8RSe1nCA==
display: block;
}
/* buttons */
.btn-wrapper {
/* Copy button */
.copy-btn {
position: absolute;
top: 0.5rem;
right: 0.5rem;
}
.btn {
display: inline-block;
position: relative;
background: var(--color-brand-blue-1);
color: #fff;
border: none;
@ -117,15 +97,14 @@ AP0YLC768PFTBm9CM5T1BE0xjJ7s4dZSrVoI4n8RSe1nCA==
cursor: pointer;
opacity: 0.9;
transition: opacity 0.2s;
text-decoration: none;
}
.btn:hover,
.btn:focus {
.copy-btn:hover,
.copy-btn:focus {
opacity: 1;
}
.btn:focus {
.copy-btn:focus {
outline: 2px solid #0056b3;
outline-offset: 2px;
}

View file

@ -11,8 +11,7 @@ import Card from '../components/Card.astro'
<main>
<h1><span class="text-gradient">Hobby Projects</span></h1>
<p>
See more of my previous projects at <a href="https://ayos.blog/projects"
>my blog</a
See more of my previous projects at <a href="https://ayos.blog">my blog</a
>.
</p>
<ul>

View file

@ -1,14 +0,0 @@
import { defineConfig } from 'vitest/config'
// Exclude generated files and heavy folders from the watcher to avoid
// continuous re-runs when build output or other tools touch files.
export default defineConfig({
test: {
globals: true,
environment: 'node',
exclude: ['dist/**', 'public/**', 'node_modules/**'],
},
watch: {
exclude: ['dist/**', 'public/**', 'node_modules/**', '.git/**'],
},
})