Compare commits

...

30 commits

Author SHA1 Message Date
Ayo
d8a38b60e3 1.3.88 2026-06-10 12:00:34 +02:00
Ayo
1ae9325ace feat: link to blog prod 2026-06-10 11:46:22 +02:00
Ayo
73ecdbe4d8 1.3.87 2026-06-08 11:15:40 +02:00
Ayo
bd65162c70 style: dotted bg for whole page 2026-06-08 11:15:21 +02:00
Ayo
fb93eb7574 1.3.86 2026-06-07 22:26:02 +02:00
Ayo
ed4b091cf1 refactor: remove unneeded style 2026-06-07 22:25:54 +02:00
Ayo
778328d984 1.3.85 2026-06-07 22:25:30 +02:00
Ayo
44e6fe6722 style: dotted bg everywhere 2026-06-07 22:25:03 +02:00
Ayo
271a92c7b3 1.3.84 2026-06-07 22:21:55 +02:00
Ayo
d79e51f1ce style: dotted background 2026-06-07 22:21:38 +02:00
Ayo
cdc9d091e3 1.3.83 2026-06-05 19:27:59 +02:00
Ayo
ef5c186345 feat: use elk.zone 2026-06-05 19:27:43 +02:00
Ayo
8c2e355bfb 1.3.82 2026-06-05 17:16:55 +02:00
Ayo
a9dc107176 feat: status-indicator on now-label 2026-06-05 17:16:29 +02:00
Ayo
ec7442da30 1.3.81 2026-06-05 12:24:39 +02:00
Ayo
7a674694a7 1.3.80 2026-06-05 12:16:50 +02:00
Ayo
d8768998e2 chore(deps): add sharp 2026-06-05 12:16:28 +02:00
Ayo
2f39f3d62d 1.3.79 2026-06-05 12:14:36 +02:00
Ayo
28a57aca9a chore: update packageManager 2026-06-05 12:14:22 +02:00
Ayo
e77351e5dd refactor: trying copilot edits 2026-06-05 12:13:13 +02:00
Ayo
fd1d015a18 chore(pgp): format 2026-06-05 12:13:13 +02:00
Ayo
f2bbfbf1b4 chore: add test config and instructions 2026-06-05 12:13:13 +02:00
Ayo
4860085aef chore: update lint and format scripts 2026-06-05 12:13:13 +02:00
Ayo
e3be0e92cb feat: update mastodon to webtoo.ls profile 2026-06-05 12:12:29 +02:00
Ayo
1dbd3d662c 1.3.78 2026-05-31 18:48:18 +02:00
Ayo
199f3d1429 fix: links to legacy blog 2026-05-31 18:47:57 +02:00
Ayo
74a3a71090 1.3.77 2026-05-31 18:41:01 +02:00
Ayo
7966746711 feat(pgp): copy & download buttons 2026-05-31 18:40:50 +02:00
Ayo
7e0380a324 1.3.76 2026-05-31 17:04:22 +02:00
Ayo
7921721425 feat(pgp): concise description 2026-05-31 17:04:11 +02:00
10 changed files with 127 additions and 51 deletions

View file

@ -22,13 +22,30 @@ $ pnpm i
## Commands ## Commands
| Command | Action | | Command | Action |
| ------------------- | ---------------------------------------------- | | ----------------------------- | ---------------------------------------------- |
| `pnpm run dev` | start dev server | | `pnpm run dev` | start dev server |
| `pnpm run build` | generate static files to `dist` directory | | `pnpm run build` | generate static files to `dist` directory |
| `pnpm run deploy` | upload to my server | | `pnpm run deploy` | upload to my server |
| `pnpm run prep:now` | back up and clear current `now page` constants | | `pnpm run prep:now` | back up and clear current `now page` constants |
| `pnpm run patch:build:deploy` | increment version, build, then upload | | `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
```
## Deployment ## Deployment

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -8,7 +8,7 @@ import { Picture } from 'astro:assets'
// fetch mastodon account // fetch mastodon account
const response = await fetch( const response = await fetch(
'https://social.ayco.io/api/v1/accounts/lookup?acct=ayo' 'https://m.webtoo.ls/api/v1/accounts/lookup?acct=ayo'
) )
const data = await response.json() const data = await response.json()
const { avatar, note } = data const { avatar, note } = data
@ -38,13 +38,15 @@ const avatarSize = 150
</h1> </h1>
<a href="/now" class="now-wrapper action primary"> <a href="/now" class="now-wrapper action primary">
<span class="now-label">now</span> <span class="now-label">
<status-indicator status="active" pulse> now </status-indicator>
</span>
<span class="status">{now.title}</span> <span class="status">{now.title}</span>
</a> </a>
</div> </div>
</div> </div>
</section> </section>
<main> <main class=".dot-grid">
<section class="introduction-section"> <section class="introduction-section">
<p> <p>
I care about the <em>Web</em>, and I love to <em>create</em> stuff to <em I care about the <em>Web</em>, and I love to <em>create</em> stuff to <em
@ -93,6 +95,7 @@ const avatarSize = 150
&.primary { &.primary {
border: 1px solid rgba(255, 255, 255, 0.2); border: 1px solid rgba(255, 255, 255, 0.2);
border-style: dotted;
border-radius: 40px; border-radius: 40px;
} }
} }

View file

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

View file

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

14
vitest.config.ts Normal file
View file

@ -0,0 +1,14 @@
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/**'],
},
})