Compare commits
2 commits
main
...
feat/anima
| Author | SHA1 | Date | |
|---|---|---|---|
| 7d86adf6f3 | |||
| 3a4a450fbb |
27 changed files with 995 additions and 1873 deletions
6
.vscode/extensions.json
vendored
6
.vscode/extensions.json
vendored
|
|
@ -1,8 +1,4 @@
|
||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": ["astro-build.astro-vscode"],
|
||||||
"astro-build.astro-vscode",
|
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"esbenp.prettier-vscode"
|
|
||||||
],
|
|
||||||
"unwantedRecommendations": []
|
"unwantedRecommendations": []
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,6 @@ $ pnpm i
|
||||||
| `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 |
|
|
||||||
|
|
||||||
## Deployment
|
## Deployment
|
||||||
|
|
||||||
|
|
@ -47,7 +46,7 @@ If you want to run build before deploying, do `npm run build:deploy`
|
||||||
|
|
||||||
## Auto-mirror to various code forges
|
## Auto-mirror to various code forges
|
||||||
|
|
||||||
I push this project to three different code forges, because I'm paranoid like that. No, I just like to show off. I do this with a post-commit hook that automatically calls `git push <repo> --mirror`, and you can see that in the `.husky/post-commit` file.
|
The power of git lies within its decentralized nature. I push this project to three different code forges, because I'm paranoid like that. No, I just like to show off. I do this with a post-commit hook that automatically calls `git push <repo> --mirror`, and you can see that in the `.husky/post-commit` file.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@
|
||||||
import { defineConfig } from 'astro/config'
|
import { defineConfig } from 'astro/config'
|
||||||
import serviceWorker from '@ayco/astro-sw'
|
import serviceWorker from '@ayco/astro-sw'
|
||||||
import sitemap from '@astrojs/sitemap'
|
import sitemap from '@astrojs/sitemap'
|
||||||
import { viteStaticCopy } from 'vite-plugin-static-copy'
|
|
||||||
|
|
||||||
import * as data from './package.json'
|
import * as data from './package.json'
|
||||||
|
|
||||||
|
|
@ -15,6 +14,7 @@ export default defineConfig({
|
||||||
domains: ['cdn.bsky.app', 'media.ayco.io'],
|
domains: ['cdn.bsky.app', 'media.ayco.io'],
|
||||||
remotePatterns: [{ protocol: 'https' }],
|
remotePatterns: [{ protocol: 'https' }],
|
||||||
},
|
},
|
||||||
|
security: { csp: true },
|
||||||
integrations: [
|
integrations: [
|
||||||
sitemap(),
|
sitemap(),
|
||||||
serviceWorker({
|
serviceWorker({
|
||||||
|
|
@ -22,10 +22,6 @@ export default defineConfig({
|
||||||
assetCachePrefix: 'ayco-personal-site',
|
assetCachePrefix: 'ayco-personal-site',
|
||||||
assetCacheVersionID: data.version,
|
assetCacheVersionID: data.version,
|
||||||
logAssets: true,
|
logAssets: true,
|
||||||
include: [
|
|
||||||
'/wc/node_modules/web-component-base/dist/index.js',
|
|
||||||
'/wc/node_modules/@ayo-run/status-indicator/dist/status-indicator.js',
|
|
||||||
],
|
|
||||||
esbuild: {
|
esbuild: {
|
||||||
minify: true,
|
minify: true,
|
||||||
},
|
},
|
||||||
|
|
@ -43,20 +39,4 @@ export default defineConfig({
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
],
|
],
|
||||||
vite: {
|
|
||||||
plugins: [
|
|
||||||
viteStaticCopy({
|
|
||||||
targets: [
|
|
||||||
{
|
|
||||||
src: './node_modules/web-component-base/dist/index.js',
|
|
||||||
dest: 'wc',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
src: './node_modules/@ayo-run/status-indicator/dist/status-indicator.js',
|
|
||||||
dest: 'wc',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
}),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
})
|
})
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import tseslint from 'typescript-eslint'
|
||||||
import astroSwGlobals from '@ayco/astro-sw/globals'
|
import astroSwGlobals from '@ayco/astro-sw/globals'
|
||||||
import astroParser from 'astro-eslint-parser'
|
import astroParser from 'astro-eslint-parser'
|
||||||
|
|
||||||
import { includeIgnoreFile } from '@eslint/config-helpers'
|
import { includeIgnoreFile } from '@eslint/compat'
|
||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
import { fileURLToPath } from 'node:url'
|
import { fileURLToPath } from 'node:url'
|
||||||
|
|
||||||
|
|
|
||||||
47
package.json
47
package.json
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "@ayco/personal-website",
|
"name": "@ayco/personal-website",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"version": "1.3.78",
|
"version": "1.3.54",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"astro": "astro",
|
"astro": "astro",
|
||||||
|
|
@ -10,54 +10,47 @@
|
||||||
"build": "astro telemetry disable && astro build",
|
"build": "astro telemetry disable && astro build",
|
||||||
"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",
|
|
||||||
"format": "prettier . --write",
|
"format": "prettier . --write",
|
||||||
"check": "npm run format && npm run lint",
|
"check": "npm run format && 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",
|
|
||||||
"build:preview": "npm run build && astro preview",
|
"build:preview": "npm run build && astro preview",
|
||||||
"build:deploy": "npm run build && npm run deploy",
|
"build:deploy": "npm run build && npm run deploy",
|
||||||
"copy:dist": "npm run build && cp -R dist ../ayco.io-flask/",
|
"copy:dist": "npm run build && cp -R dist ../ayco.io-flask/",
|
||||||
"prepare": "husky",
|
"prepare": "husky",
|
||||||
"prep:now": "npx jiti ./commands/prep-now.js",
|
"prep:now": "npx jiti ./commands/prep-now.js"
|
||||||
"test": "vitest run ."
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@astrojs/sitemap": "^3.7.2",
|
"@astrojs/sitemap": "^3.7.2",
|
||||||
"@ayco/astro-sw": "^1.0.0",
|
"@ayco/astro-sw": "^0.9.4",
|
||||||
"@eslint/compat": "^2.1.0",
|
"@eslint/compat": "^2.0.3",
|
||||||
"@eslint/config-helpers": "^0.6.0",
|
|
||||||
"@eslint/js": "^10.0.1",
|
"@eslint/js": "^10.0.1",
|
||||||
"@iconify-json/mdi": "^1.2.3",
|
"@iconify-json/mdi": "^1.2.3",
|
||||||
"@iconify-json/simple-icons": "^1.2.82",
|
"@iconify-json/simple-icons": "^1.2.76",
|
||||||
"@iconify-json/tabler": "^1.2.35",
|
"@iconify-json/tabler": "^1.2.33",
|
||||||
"@typescript-eslint/parser": "^8.59.3",
|
"@typescript-eslint/parser": "^8.58.0",
|
||||||
"astro": "^6.3.3",
|
"astro": "^6.1.2",
|
||||||
"astro-eslint-parser": "^1.4.0",
|
"astro-eslint-parser": "^1.4.0",
|
||||||
"astro-github-stats": "^0.8.0",
|
"astro-github-stats": "^0.8.0",
|
||||||
"astro-icon": "^1.1.5",
|
"astro-icon": "^1.1.5",
|
||||||
"consola": "^3.4.2",
|
"consola": "^3.4.2",
|
||||||
"eslint": "^10.4.0",
|
"eslint": "^10.1.0",
|
||||||
"eslint-plugin-astro": "^1.7.0",
|
"eslint-plugin-astro": "^1.6.0",
|
||||||
"eslint-plugin-jsx-a11y": "^6.10.2",
|
"eslint-plugin-jsx-a11y": "^6.10.2",
|
||||||
"globals": "^17.6.0",
|
"globals": "^17.4.0",
|
||||||
"husky": "^9.1.7",
|
"husky": "^9.1.7",
|
||||||
"jiti": "^2.7.0",
|
"jiti": "^2.6.1",
|
||||||
"lint-staged": "^17.0.4",
|
"lint-staged": "^16.4.0",
|
||||||
"pathe": "^2.0.3",
|
"pathe": "^2.0.3",
|
||||||
"prettier": "^3.8.3",
|
"prettier": "^3.8.1",
|
||||||
"prettier-plugin-astro": "^0.14.1",
|
"prettier-plugin-astro": "^0.14.1",
|
||||||
"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",
|
||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"typescript": "^6.0.3",
|
"typescript": "^6.0.2",
|
||||||
"typescript-eslint": "^8.59.3",
|
"typescript-eslint": "^8.58.0",
|
||||||
"unified": "^11.0.5",
|
"unified": "^11.0.5"
|
||||||
"vite-plugin-static-copy": "^4.1.0",
|
|
||||||
"vitest": "^4.1.7",
|
|
||||||
"vitest-dom": "^0.1.1"
|
|
||||||
},
|
},
|
||||||
"lint-staged": {
|
"lint-staged": {
|
||||||
"*.{js,mjs,astro,ts}": [
|
"*.{js,mjs,astro,ts}": [
|
||||||
|
|
@ -68,9 +61,5 @@
|
||||||
"prettier --write"
|
"prettier --write"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"packageManager": "pnpm@11.2.2",
|
"packageManager": "pnpm@10.14.0"
|
||||||
"dependencies": {
|
|
||||||
"@ayo-run/status-indicator": "^2.1.2",
|
|
||||||
"web-component-base": "^4.1.2"
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1864
pnpm-lock.yaml
1864
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,4 +0,0 @@
|
||||||
allowBuilds:
|
|
||||||
esbuild: false
|
|
||||||
sharp: false
|
|
||||||
web-component-base: false
|
|
||||||
209
public/components/sparkly-text.js
Normal file
209
public/components/sparkly-text.js
Normal file
|
|
@ -0,0 +1,209 @@
|
||||||
|
/**
|
||||||
|
* Thanks to sparkly-text web component by Stefan Judis: https://github.com/stefanjudis/sparkly-text
|
||||||
|
*/
|
||||||
|
|
||||||
|
let sheet
|
||||||
|
let sparkleTemplate
|
||||||
|
|
||||||
|
// https://caniuse.com/mdn-api_cssstylesheet_replacesync
|
||||||
|
const supportsConstructableStylesheets =
|
||||||
|
'replaceSync' in CSSStyleSheet.prototype
|
||||||
|
|
||||||
|
const motionOK = window.matchMedia('(prefers-reduced-motion: no-preference)')
|
||||||
|
|
||||||
|
class SparklyText extends HTMLElement {
|
||||||
|
#numberOfSparkles = 3
|
||||||
|
#sparkleSvg = `<svg width="1200" height="1200" viewBox="0 0 1200 1200" aria-hidden="true">
|
||||||
|
<path fill="red" d="m611.04 866.16c17.418-61.09 50.25-116.68 95.352-161.42 45.098-44.742 100.94-77.133 162.17-94.062l38.641-10.68-38.641-10.68c-61.227-16.93-117.07-49.32-162.17-94.062-45.102-44.738-77.934-100.33-95.352-161.42l-11.039-38.641-11.039 38.641c-17.418 61.09-50.25 116.68-95.352 161.42-45.098 44.742-100.94 77.133-162.17 94.062l-38.641 10.68 38.641 10.68c61.227 16.93 117.07 49.32 162.17 94.062 45.102 44.738 77.934 100.33 95.352 161.42l11.039 38.641z"/>
|
||||||
|
</svg>`
|
||||||
|
|
||||||
|
#css = `
|
||||||
|
:host {
|
||||||
|
--_sparkle-base-size: var(--sparkly-text-size, 1em);
|
||||||
|
--_sparkle-base-animation-length: var(--sparkly-text-animation-length, 1.5s);
|
||||||
|
--_sparkle-base-color: var(--sparkly-text-color, #4ab9f8);
|
||||||
|
|
||||||
|
position: relative;
|
||||||
|
z-index: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
position: absolute;
|
||||||
|
z-index: -1;
|
||||||
|
width: var(--_sparkle-base-size);
|
||||||
|
height: var(--_sparkle-base-size);
|
||||||
|
transform-origin: center;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (prefers-reduced-motion: no-preference) {
|
||||||
|
svg {
|
||||||
|
animation: sparkle-spin var(--_sparkle-base-animation-length) linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg.rainbow path {
|
||||||
|
animation: rainbow-colors calc(var(--_sparkle-base-animation-length) * 2) linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg path {
|
||||||
|
fill: var(--_sparkle-base-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes rainbow-colors {
|
||||||
|
0%, 100% { fill: #ff0000; }
|
||||||
|
14% { fill: #ff8000; }
|
||||||
|
28% { fill: #ffff00; }
|
||||||
|
42% { fill: #00ff00; }
|
||||||
|
56% { fill: #0000ff; }
|
||||||
|
70% { fill: #4b0082; }
|
||||||
|
84% { fill: #8f00ff; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes sparkle-spin {
|
||||||
|
0% {
|
||||||
|
scale: 0;
|
||||||
|
opacity: 0;
|
||||||
|
rotate: 0deg;
|
||||||
|
}
|
||||||
|
|
||||||
|
50% {
|
||||||
|
scale: 1;
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
100% {
|
||||||
|
scale: 0;
|
||||||
|
opacity: 0;
|
||||||
|
rotate: 180deg;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
static register() {
|
||||||
|
if ('customElements' in window) {
|
||||||
|
window.customElements.define('sparkly-text', SparklyText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
generateCss() {
|
||||||
|
if (!sheet) {
|
||||||
|
if (supportsConstructableStylesheets) {
|
||||||
|
sheet = new CSSStyleSheet()
|
||||||
|
sheet.replaceSync(this.#css)
|
||||||
|
} else {
|
||||||
|
sheet = document.createElement('style')
|
||||||
|
sheet.textContent = this.#css
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (supportsConstructableStylesheets) {
|
||||||
|
this.shadowRoot.adoptedStyleSheets = [sheet]
|
||||||
|
} else {
|
||||||
|
this.shadowRoot.append(sheet.cloneNode(true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedCallback() {
|
||||||
|
const needsSparkles = motionOK.matches || !this.shadowRoot
|
||||||
|
|
||||||
|
if (!this.shadowRoot) {
|
||||||
|
this.attachShadow({ mode: 'open' })
|
||||||
|
this.generateCss()
|
||||||
|
this.shadowRoot.append(document.createElement('slot'))
|
||||||
|
}
|
||||||
|
|
||||||
|
if (needsSparkles) {
|
||||||
|
this.#numberOfSparkles = parseInt(
|
||||||
|
this.getAttribute('number-of-sparkles') || `${this.#numberOfSparkles}`,
|
||||||
|
10
|
||||||
|
)
|
||||||
|
|
||||||
|
if (Number.isNaN(this.#numberOfSparkles)) {
|
||||||
|
throw new Error(`Invalid number-of-sparkles value`)
|
||||||
|
}
|
||||||
|
this.cleanupSparkles()
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
|
||||||
|
motionOK.addEventListener('change', this.motionOkChange)
|
||||||
|
window.addEventListener('popstate', this.handleNavigation)
|
||||||
|
window.addEventListener('pageshow', this.handlePageShow)
|
||||||
|
}
|
||||||
|
|
||||||
|
disconnectedCallback() {
|
||||||
|
motionOK.removeEventListener('change', this.motionOkChange)
|
||||||
|
window.removeEventListener('popstate', this.handleNavigation)
|
||||||
|
window.removeEventListener('pageshow', this.handlePageShow)
|
||||||
|
this.cleanupSparkles()
|
||||||
|
}
|
||||||
|
|
||||||
|
handleNavigation = () => {
|
||||||
|
if (motionOK.matches) {
|
||||||
|
this.cleanupSparkles()
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
handlePageShow = (event) => {
|
||||||
|
// If the page is being loaded from the bfcache
|
||||||
|
if (event.persisted && motionOK.matches) {
|
||||||
|
this.cleanupSparkles()
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cleanupSparkles() {
|
||||||
|
// Remove all existing sparkle SVGs
|
||||||
|
const sparkles = this.shadowRoot.querySelectorAll('svg')
|
||||||
|
sparkles.forEach((sparkle) => sparkle.remove())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Declare as an arrow function to get the appropriate 'this'
|
||||||
|
motionOkChange = () => {
|
||||||
|
if (motionOK.matches) {
|
||||||
|
this.addSparkles()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSparkles() {
|
||||||
|
for (let i = 0; i < this.#numberOfSparkles; i++) {
|
||||||
|
setTimeout(() => {
|
||||||
|
this.addSparkle((sparkle) => {
|
||||||
|
sparkle.style.top = `calc(${
|
||||||
|
Math.random() * 110 - 5
|
||||||
|
}% - var(--_sparkle-base-size) / 2)`
|
||||||
|
sparkle.style.left = `calc(${
|
||||||
|
Math.random() * 110 - 5
|
||||||
|
}% - var(--_sparkle-base-size) / 2)`
|
||||||
|
})
|
||||||
|
}, i * 500)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
addSparkle(update) {
|
||||||
|
if (!sparkleTemplate) {
|
||||||
|
const span = document.createElement('span')
|
||||||
|
span.innerHTML = this.#sparkleSvg
|
||||||
|
sparkleTemplate = span.firstElementChild.cloneNode(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
const sparkleWrapper = sparkleTemplate.cloneNode(true)
|
||||||
|
|
||||||
|
// Add rainbow class if --sparkly-text-color is set to 'rainbow'
|
||||||
|
const styles = getComputedStyle(this)
|
||||||
|
if (styles.getPropertyValue('--sparkly-text-color').trim() === 'rainbow') {
|
||||||
|
sparkleWrapper.classList.add('rainbow')
|
||||||
|
}
|
||||||
|
|
||||||
|
update(sparkleWrapper)
|
||||||
|
this.shadowRoot.appendChild(sparkleWrapper)
|
||||||
|
sparkleWrapper.addEventListener('animationiteration', () => {
|
||||||
|
update(sparkleWrapper)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
SparklyText.register()
|
||||||
|
|
||||||
|
export { SparklyText }
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
mDMEahs0NhYJKwYBBAHaRw8BAQdAV2CfblniKxklPgW9eYt2gBl0jMHLwtjrQaY+
|
|
||||||
BQoWdIa0FkF5byBBeWNvIDxheW9AYXljby5pbz6ImQQTFgoAQRYhBBfxPV6P9zcr
|
|
||||||
E1RcOGXmv2QVKTxlBQJqGzQ2AhsDBQkB4TOABQsJCAcCAiICBhUKCQgLAgQWAgMB
|
|
||||||
Ah4HAheAAAoJEGXmv2QVKTxlIeYA/2WLvkDapBbVmGXoACAhqcTN93/CoPyzUbhN
|
|
||||||
hHE6mmAUAQDHsgCHmh3S/Rn4NRE3Gb41kiPTBWHrlYLTj3Kiw0ASB7g4BGobNDYS
|
|
||||||
CisGAQQBl1UBBQEBB0C6OEKEIPLaNDOM6JYhvvq0Q4Mk/B1eyZBKF/b4fQvlGgMB
|
|
||||||
CAeIfgQYFgoAJhYhBBfxPV6P9zcrE1RcOGXmv2QVKTxlBQJqGzQ2AhsMBQkB4TOA
|
|
||||||
AAoJEGXmv2QVKTxlMSQBAP5ta1kUFp3HAYwcun8qmoiVq1dEJSN1LnI7HlX4ucTl
|
|
||||||
AP0YLC768PFTBm9CM5T1BE0xjJ7s4dZSrVoI4n8RSe1nCA==
|
|
||||||
=MTsv
|
|
||||||
-----END PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
@ -45,7 +45,7 @@ const year = new Date().getFullYear()
|
||||||
<p>
|
<p>
|
||||||
Copyright © 2022-{year}
|
Copyright © 2022-{year}
|
||||||
<a href="/">Ayo Ayco</a>. This website <a
|
<a href="/">Ayo Ayco</a>. This website <a
|
||||||
href="https://v1.ayos.blog/stopped-tracking-on-my-sites"
|
href="https://ayos.blog/stopped-tracking-on-my-sites"
|
||||||
>does not track users</a
|
>does not track users</a
|
||||||
>. See the <a href="https://git.ayo.run/ayo/ayco.io-astro">source code.</a>
|
>. See the <a href="https://git.ayo.run/ayo/ayco.io-astro">source code.</a>
|
||||||
</p>
|
</p>
|
||||||
|
|
|
||||||
|
|
@ -55,18 +55,6 @@ const baseURL = Astro.site?.toString().slice(0, -1) // ?? 'https://ayo.ayco.io'
|
||||||
<link rel="mask-icon" href="mask-icon.svg" color="#000000" />
|
<link rel="mask-icon" href="mask-icon.svg" color="#000000" />
|
||||||
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
<link rel="apple-touch-icon" href="apple-touch-icon.png" />
|
||||||
|
|
||||||
<!-- Web Components -->
|
<!-- web components -->
|
||||||
<script type="importmap">
|
<script type="module" src="components/sparkly-text.js"></script>
|
||||||
{
|
|
||||||
"imports": {
|
|
||||||
"web-component-base": "/wc/node_modules/web-component-base/dist/index.js",
|
|
||||||
"status-indicator": "/wc/node_modules/@ayo-run/status-indicator/dist/status-indicator.js"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
|
||||||
<script type="module">
|
|
||||||
// importing is enough to register the elements
|
|
||||||
// eslint-disable-next-line
|
|
||||||
import StatusIndicator from 'status-indicator'
|
|
||||||
</script>
|
|
||||||
</head>
|
</head>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"title": "BACK AT IT",
|
|
||||||
"description": "",
|
|
||||||
"publishDate": "2026-02-22",
|
|
||||||
"publishedOn": "the 22nd day of February 2026",
|
|
||||||
"publishState": ""
|
|
||||||
}
|
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
Hello! Been a while!
|
|
||||||
|
|
||||||
Yes, I'm getting back to some of my shelved side projects like <a href="https://ayco.io/gh/mcfly#readme">McFly</a> and <a href="https://ayco.io/gh/astro-sw#readme">Astro SW</a>.
|
|
||||||
|
|
||||||
Why were they shelved, you ask?
|
|
||||||
|
|
||||||
Well, that's a story for another time.
|
|
||||||
|
|
||||||
You can <a href="https://ayco.io/gh">Follow me on GitHub</a> to see my activities on these projects if that's your thing. :)
|
|
||||||
|
|
@ -1,7 +0,0 @@
|
||||||
{
|
|
||||||
"title": "Joining the Elk team, speaking my mind",
|
|
||||||
"description": "",
|
|
||||||
"publishDate": "2026-04-11",
|
|
||||||
"publishedOn": "a sunny 11th day of April",
|
|
||||||
"publishState": "in my home in Amsterdam"
|
|
||||||
}
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
Aaaaah summer feels today. Bugs all buzzing and
|
|
||||||
|
|
||||||
All the darkness are fading and people who lived most of their lives in the tropics but emigrated to less sunny places become friendlier and nicer.
|
|
||||||
|
|
||||||
BUT NOT ME! 😡
|
|
||||||
|
|
||||||
kidding. 😅
|
|
||||||
|
|
||||||
Anyway... I have [joined the team](https://github.com/elk-zone/elk/pull/3594) behind [Elk](https://elk.zone) where I continue to be the bottle of sunshine that I am.
|
|
||||||
|
|
||||||
Kidding again.
|
|
||||||
|
|
||||||
...where I try to be more annoying than usual and tag @everyone in our Discord server. That's it. That's me now.
|
|
||||||
|
|
||||||
Also I have been smothering my [threads](https://ayo.ayco.io/threads) page with my deepest darkest thoughts -- and people either like it or not. That's how it is really. It's probably gonna be written on my grave: "HIS THOUGHTS SUCK"
|
|
||||||
|
|
||||||
...though honestly I have been getting more responses of validation and new connections that I have a feeling maybe I have been touching on something.... true???
|
|
||||||
|
|
||||||
Only time will tell. As my therapist said: these thoughts will not matter in 5 years.
|
|
||||||
|
|
||||||
...because they will be deleted.
|
|
||||||
|
|
||||||
Kidding!
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"title": "Social Web & Web Components",
|
"title": "BACK AT IT",
|
||||||
"description": "Getting into discussions about improving the social web & building components",
|
"description": "",
|
||||||
"publishDate": "2026-05-12",
|
"publishDate": "2026-02-22",
|
||||||
"publishedOn": "",
|
"publishedOn": "the 22nd day of February 2026",
|
||||||
"publishState": ""
|
"publishState": ""
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +1,9 @@
|
||||||
I bought a pull-up bar, and in addition to a few barbells at home, I am starting to have a mini gym.
|
Hello! Been a while!
|
||||||
|
|
||||||
Super happy that I am getting healthier as I build my workout routines.
|
Yes, I'm getting back to some of my shelved side projects like <a href="https://ayco.io/gh/mcfly#readme">McFly</a> and <a href="https://ayco.io/gh/astro-sw#readme">Astro SW</a>.
|
||||||
|
|
||||||
To keep my mind sharp, I find it helpful to have several hobby things I can get back to from time to time.
|
Why were they shelved, you ask?
|
||||||
|
|
||||||
I am in a few discussions for exploring some things in the open social web. Details can come later as they become more concrete.
|
Well, that's a story for another time.
|
||||||
|
|
||||||
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.
|
You can <a href="https://ayco.io/gh">Follow me on GitHub</a> to see my activities on these projects if that's your thing. :)
|
||||||
|
|
||||||
I'm also back on my webcomponents main arc, after some side quests in self-hosting & machine learning.
|
|
||||||
|
|
||||||
Links to come later. Cheers!
|
|
||||||
|
|
|
||||||
|
|
@ -127,10 +127,10 @@ const ogFileType = 'image/png'
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<!-- Anonymous RUM for web perf by Cloudflare Web Analytics -->
|
<!-- Anonymous RUM for web perf by Cloudflare Web Analytics -->
|
||||||
<!-- <script
|
<script
|
||||||
defer
|
defer
|
||||||
src="https://static.cloudflareinsights.com/beacon.min.js"
|
src="https://static.cloudflareinsights.com/beacon.min.js"
|
||||||
data-cf-beacon='{"token": "a39ad600e67a4db8960c639d2552435c"}'></script> -->
|
data-cf-beacon='{"token": "a39ad600e67a4db8960c639d2552435c"}'></script>
|
||||||
<!-- End Cloudflare Web Analytics -->
|
<!-- End Cloudflare Web Analytics -->
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -19,26 +19,11 @@ import Footer from '../components/Footer.astro'
|
||||||
target="_blank">iSAQB</a
|
target="_blank">iSAQB</a
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
<p>
|
|
||||||
I have contributed software development expertise to
|
|
||||||
<a href="http://infor.com">Infor</a>,
|
|
||||||
<a href="https://www.priva.com/">Priva</a>,
|
|
||||||
<a href="http://itc.uplb.edu.ph">UPLB</a>,
|
|
||||||
<a href="http://dost.gov.ph">DOST</a>, and various government-funded
|
|
||||||
projects such as
|
|
||||||
<a href="http://noah.up.edu.ph">Project NOAH</a> — which consistently
|
|
||||||
<a href="https://www.youtube.com/watch?v=LKrV6vtGZEA">saved lives</a>
|
|
||||||
from natural hazards in
|
|
||||||
<a href="https://www.officialgazette.gov.ph/programs/about-project-noah/">
|
|
||||||
the Philippines
|
|
||||||
</a>.
|
|
||||||
</p>
|
|
||||||
<p>
|
<p>
|
||||||
In my spare time, I find it fun building <a href="/showcase">projects</a>,
|
In my spare time, I find it fun building <a href="/showcase">projects</a>,
|
||||||
running self-hosted services at <a href="https://ayo.run">ayo.run</a>, and
|
running self-hosted services at <a href="https://ayo.run">ayo.run</a>, and
|
||||||
volunteering to <a
|
volunteering to <a href="https://ayos.blog/why-fediverse/" target="_blank"
|
||||||
href="https://v1.ayos.blog/why-fediverse/"
|
>Fediverse</a
|
||||||
target="_blank">Fediverse</a
|
|
||||||
> projects like <a href="https://elk.zone">elk.zone</a>,
|
> projects like <a href="https://elk.zone">elk.zone</a>,
|
||||||
<a href="https://m.webtoo.ls/public/local">webtoo.ls</a>, and <a
|
<a href="https://m.webtoo.ls/public/local">webtoo.ls</a>, and <a
|
||||||
href="https://m.webtoo.ls/@vitest">vitest's fedi presence</a
|
href="https://m.webtoo.ls/@vitest">vitest's fedi presence</a
|
||||||
|
|
@ -53,10 +38,7 @@ import Footer from '../components/Footer.astro'
|
||||||
<h2 id="contact">Contact info</h2>
|
<h2 id="contact">Contact info</h2>
|
||||||
<p>My inbox is open to everyone.</p>
|
<p>My inbox is open to everyone.</p>
|
||||||
<ul>
|
<ul>
|
||||||
<li>
|
<li>✉️ Email: <a href="mailto:hi@ayo.run">hi@ayo.run</a></li>
|
||||||
✉️ Email me: <a href="mailto:ayo@ayco.io">ayo@ayco.io</a> ·
|
|
||||||
<small> (<a href="/pgp">PGP key</a>)</small>
|
|
||||||
</li>
|
|
||||||
<li>
|
<li>
|
||||||
💬 Signal: <a
|
💬 Signal: <a
|
||||||
href="https://signal.me/#eu/mU2KHaMuoumvLaq7P5ZPUU4HMd0SaU9hYHeEPbDIeJzysNL01FVLfbk-kVEncfIz"
|
href="https://signal.me/#eu/mU2KHaMuoumvLaq7P5ZPUU4HMd0SaU9hYHeEPbDIeJzysNL01FVLfbk-kVEncfIz"
|
||||||
|
|
@ -67,11 +49,11 @@ import Footer from '../components/Footer.astro'
|
||||||
</ul>
|
</ul>
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
|
|
||||||
<style>
|
|
||||||
ul {
|
|
||||||
list-style: none;
|
|
||||||
padding-left: 0;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
ul {
|
||||||
|
list-style: none;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -36,8 +36,8 @@ const avatarSize = 150
|
||||||
<h1 title="Ayo Ayco | Software Engineer + Web Developer">
|
<h1 title="Ayo Ayco | Software Engineer + Web Developer">
|
||||||
Hi, I'm <span class="heavy-text">Ayo</span>!
|
Hi, I'm <span class="heavy-text">Ayo</span>!
|
||||||
</h1>
|
</h1>
|
||||||
|
<!--a href="https://forms.ayo.run/form/tnz7FybY" class="now-wrapper"-->
|
||||||
<a href="/now" class="now-wrapper action primary">
|
<a href="/now" class="now-wrapper">
|
||||||
<span class="now-label">now</span>
|
<span class="now-label">now</span>
|
||||||
<span class="status">{now.title}</span>
|
<span class="status">{now.title}</span>
|
||||||
</a>
|
</a>
|
||||||
|
|
@ -47,9 +47,12 @@ const avatarSize = 150
|
||||||
<main>
|
<main>
|
||||||
<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 <sparkly-text
|
||||||
>inspire</em
|
><em>inspire</em></sparkly-text
|
||||||
> and <em>serve</em> others.
|
> and <em>serve</em> others.
|
||||||
|
<!--
|
||||||
|
<a href="/about">More?</a>
|
||||||
|
-->
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
<section class="cards-section">
|
<section class="cards-section">
|
||||||
|
|
@ -68,7 +71,6 @@ const avatarSize = 150
|
||||||
</ul>
|
</ul>
|
||||||
</section>
|
</section>
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
<Footer />
|
<Footer />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
||||||
|
|
@ -83,30 +85,14 @@ const avatarSize = 150
|
||||||
color: white;
|
color: white;
|
||||||
}
|
}
|
||||||
|
|
||||||
.action {
|
.now-wrapper {
|
||||||
margin-right: 0.5rem;
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
border-radius: 40px;
|
||||||
padding: 8px 4px;
|
padding: 8px 4px;
|
||||||
|
font-weight: normal;
|
||||||
color: white;
|
color: white;
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: normal;
|
|
||||||
|
|
||||||
&.primary {
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
||||||
border-radius: 40px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.now-wrapper {
|
|
||||||
display: inline-block;
|
|
||||||
max-width: 390px;
|
|
||||||
white-space: nowrap;
|
|
||||||
overflow: clip;
|
|
||||||
text-overflow: ellipsis;
|
|
||||||
|
|
||||||
.status {
|
|
||||||
max-width: 300px;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.now-label {
|
.now-label {
|
||||||
|
|
@ -206,7 +192,7 @@ const avatarSize = 150
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (max-device-width: 360px) {
|
@media only screen and (max-device-width: 360px) {
|
||||||
.action.primary {
|
.now-wrapper {
|
||||||
border: 0px;
|
border: 0px;
|
||||||
border-radius: 0;
|
border-radius: 0;
|
||||||
font-size: var(--font-size-sm);
|
font-size: var(--font-size-sm);
|
||||||
|
|
@ -237,12 +223,6 @@ const avatarSize = 150
|
||||||
}
|
}
|
||||||
|
|
||||||
@media only screen and (min-device-width: 280px) and (max-device-width: 653px) {
|
@media only screen and (min-device-width: 280px) and (max-device-width: 653px) {
|
||||||
.action {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
.now-wrapper {
|
|
||||||
max-width: 600px;
|
|
||||||
}
|
|
||||||
.highlighted-section__content {
|
.highlighted-section__content {
|
||||||
padding: 1rem;
|
padding: 1rem;
|
||||||
}
|
}
|
||||||
|
|
@ -265,4 +245,8 @@ const avatarSize = 150
|
||||||
font-size: var(--font-size-base);
|
font-size: var(--font-size-base);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
sparkly-text {
|
||||||
|
--sparkly-text-color: orange;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
|
||||||
|
|
@ -1,70 +0,0 @@
|
||||||
---
|
|
||||||
import Layout from '../../../../layouts/Layout.astro'
|
|
||||||
import Footer from '../../../../components/Footer.astro'
|
|
||||||
|
|
||||||
const title = `BACK AT IT`
|
|
||||||
const description = ``
|
|
||||||
let publishedOn = `the 22nd day of February 2026`
|
|
||||||
const publishDate = `2026-02-22`
|
|
||||||
const publishState = ``
|
|
||||||
const content = `<p>Hello! Been a while!</p>
|
|
||||||
<p>Yes, I'm getting back to some of my shelved side projects like McFly and Astro SW.</p>
|
|
||||||
<p>Why were they shelved, you ask?</p>
|
|
||||||
<p>Well, that's a story for another time.</p>
|
|
||||||
<p>You can Follow me on GitHub to see my activities on these projects if that's your thing. :)</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>
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
---
|
|
||||||
import Layout from '../../../../layouts/Layout.astro'
|
|
||||||
import Footer from '../../../../components/Footer.astro'
|
|
||||||
|
|
||||||
const title = `Joining the Elk team, speaking my mind`
|
|
||||||
const description = ``
|
|
||||||
let publishedOn = `a sunny 11th day of April`
|
|
||||||
const publishDate = `2026-04-11`
|
|
||||||
const publishState = `in my home in Amsterdam`
|
|
||||||
const content = `<p>Aaaaah summer feels today. Bugs all buzzing and</p>
|
|
||||||
<p>All the darkness are fading and people who lived most of their lives in the tropics but emigrated to less sunny places become friendlier and nicer.</p>
|
|
||||||
<p>BUT NOT ME! 😡</p>
|
|
||||||
<p>kidding. 😅</p>
|
|
||||||
<p>Anyway... I have <a href="https://github.com/elk-zone/elk/pull/3594">joined the team</a> behind <a href="https://elk.zone">Elk</a> where I continue to be the bottle of sunshine that I am.</p>
|
|
||||||
<p>Kidding again.</p>
|
|
||||||
<p>...where I try to be more annoying than usual and tag @everyone in our Discord server. That's it. That's me now.</p>
|
|
||||||
<p>Also I have been smothering my <a href="https://ayo.ayco.io/threads">threads</a> page with my deepest darkest thoughts -- and people either like it or not. That's how it is really. It's probably gonna be written on my grave: "HIS THOUGHTS SUCK"</p>
|
|
||||||
<p>...though honestly I have been getting more responses of validation and new connections that I have a feeling maybe I have been touching on something.... true???</p>
|
|
||||||
<p>Only time will tell. As my therapist said: these thoughts will not matter in 5 years.</p>
|
|
||||||
<p>...because they will be deleted.</p>
|
|
||||||
<p>Kidding!</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>
|
|
||||||
|
|
@ -1,124 +0,0 @@
|
||||||
---
|
|
||||||
import Layout from '../layouts/Layout.astro'
|
|
||||||
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.'
|
|
||||||
---
|
|
||||||
|
|
||||||
<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>
|
|
||||||
<pre
|
|
||||||
id="public-key"><code>
|
|
||||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
|
||||||
|
|
||||||
mDMEahs0NhYJKwYBBAHaRw8BAQdAV2CfblniKxklPgW9eYt2gBl0jMHLwtjrQaY+
|
|
||||||
BQoWdIa0FkF5byBBeWNvIDxheW9AYXljby5pbz6ImQQTFgoAQRYhBBfxPV6P9zcr
|
|
||||||
E1RcOGXmv2QVKTxlBQJqGzQ2AhsDBQkB4TOABQsJCAcCAiICBhUKCQgLAgQWAgMB
|
|
||||||
Ah4HAheAAAoJEGXmv2QVKTxlIeYA/2WLvkDapBbVmGXoACAhqcTN93/CoPyzUbhN
|
|
||||||
hHE6mmAUAQDHsgCHmh3S/Rn4NRE3Gb41kiPTBWHrlYLTj3Kiw0ASB7g4BGobNDYS
|
|
||||||
CisGAQQBl1UBBQEBB0C6OEKEIPLaNDOM6JYhvvq0Q4Mk/B1eyZBKF/b4fQvlGgMB
|
|
||||||
CAeIfgQYFgoAJhYhBBfxPV6P9zcrE1RcOGXmv2QVKTxlBQJqGzQ2AhsMBQkB4TOA
|
|
||||||
AAoJEGXmv2QVKTxlMSQBAP5ta1kUFp3HAYwcun8qmoiVq1dEJSN1LnI7HlX4ucTl
|
|
||||||
AP0YLC768PFTBm9CM5T1BE0xjJ7s4dZSrVoI4n8RSe1nCA==
|
|
||||||
=MTsv
|
|
||||||
-----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>
|
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</Layout>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
/* Clipboard copy logic */
|
|
||||||
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>
|
|
||||||
code {
|
|
||||||
font-size: var(--font-size-sm);
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-block {
|
|
||||||
position: relative;
|
|
||||||
background: #272822;
|
|
||||||
color: #f8f8f2;
|
|
||||||
font-family: monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
line-height: 1.4;
|
|
||||||
padding: 1rem 1rem 1rem 1.5rem;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow-x: auto;
|
|
||||||
white-space: pre-wrap;
|
|
||||||
word-wrap: break-word;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.key-block code {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* buttons */
|
|
||||||
.btn-wrapper {
|
|
||||||
position: absolute;
|
|
||||||
top: 0.5rem;
|
|
||||||
right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
background: var(--color-brand-blue-1);
|
|
||||||
color: #fff;
|
|
||||||
border: none;
|
|
||||||
border-radius: 4px;
|
|
||||||
padding: 0.3rem 0.6rem;
|
|
||||||
font-size: 0.8rem;
|
|
||||||
cursor: pointer;
|
|
||||||
opacity: 0.9;
|
|
||||||
transition: opacity 0.2s;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:hover,
|
|
||||||
.btn:focus {
|
|
||||||
opacity: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn:focus {
|
|
||||||
outline: 2px solid #0056b3;
|
|
||||||
outline-offset: 2px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
|
|
@ -11,17 +11,11 @@ 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
|
See more of my previous projects at <a href="https://ayos.blog/projects"
|
||||||
href="https://v1.ayos.blog/projects">my blog</a
|
>my blog</a
|
||||||
>.
|
>.
|
||||||
</p>
|
</p>
|
||||||
<ul>
|
<ul>
|
||||||
<Card
|
|
||||||
newTab
|
|
||||||
href="https://status-indicator.webcomponent.io/"
|
|
||||||
title="<status-indicator>"
|
|
||||||
body="Colored circles that can pulse"
|
|
||||||
/>
|
|
||||||
<Card
|
<Card
|
||||||
newTab
|
newTab
|
||||||
href="https://git.ayo.run/ayo/simple-tts#readme"
|
href="https://git.ayo.run/ayo/simple-tts#readme"
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,4 @@
|
||||||
---
|
---
|
||||||
/**
|
|
||||||
* The code for /threads in production is in: https://git.ayo.run/ayo/threads
|
|
||||||
*/
|
|
||||||
import Layout from '../layouts/Layout.astro'
|
import Layout from '../layouts/Layout.astro'
|
||||||
import Footer from '../components/Footer.astro'
|
import Footer from '../components/Footer.astro'
|
||||||
|
|
||||||
|
|
@ -9,10 +6,6 @@ const page = {
|
||||||
title: "Ayo's Threads",
|
title: "Ayo's Threads",
|
||||||
description: 'Incubator for thoughts before the become a blog',
|
description: 'Incubator for thoughts before the become a blog',
|
||||||
}
|
}
|
||||||
|
|
||||||
console.warn(
|
|
||||||
'The code for /threads in production is in: https://git.ayo.run/ayo/threads'
|
|
||||||
)
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title={page.title} description={page.description}>
|
<Layout title={page.title} description={page.description}>
|
||||||
|
|
@ -21,28 +14,6 @@ console.warn(
|
||||||
|
|
||||||
<p>{page.description}</p>
|
<p>{page.description}</p>
|
||||||
<p>Visit the <a href="https://ayco.io/threads">page</a></p>
|
<p>Visit the <a href="https://ayco.io/threads">page</a></p>
|
||||||
<p>You're probably wondering how it works.</p>
|
|
||||||
<p>
|
|
||||||
What you are viewing now, is but a placeholder in the <a
|
|
||||||
href="https://git.ayo.run/ayo/ayco.io-astro#readme"
|
|
||||||
>static parts of my personal website.</a
|
|
||||||
> This is not the full picture. There is a <a
|
|
||||||
href="https://git.ayo.run/ayo/ayco.io-flask#readme">Flask server</a
|
|
||||||
> serving the static assets who is also responsible for attaching dynamic parts
|
|
||||||
/ blueprints like my <a href="https://git.ayo.run/ayo/threads#readme"
|
|
||||||
>Threads</a
|
|
||||||
> project.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
I built it that way, because Python serves as a good "glue" to put
|
|
||||||
different parts together. Especially now with <em>LLMs</em>... where
|
|
||||||
Python is probably their greatest strength.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Anyway, for questions don't hesitate to <a
|
|
||||||
href="https://ayo.ayco.io/about#contact">contact me</a
|
|
||||||
>
|
|
||||||
</p>
|
|
||||||
</main>
|
</main>
|
||||||
<Footer />
|
<Footer />
|
||||||
</Layout>
|
</Layout>
|
||||||
|
|
|
||||||
|
|
@ -1,121 +0,0 @@
|
||||||
import { readFileSync, writeFileSync, copyFileSync } from 'node:fs'
|
|
||||||
import { fileURLToPath } from 'node:url'
|
|
||||||
import { dirname, resolve } from 'node:path'
|
|
||||||
|
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
||||||
import { consola } from 'consola'
|
|
||||||
|
|
||||||
import newNow from '../commands/prep-now'
|
|
||||||
|
|
||||||
// Mock file system operations
|
|
||||||
vi.mock('path', () => ({
|
|
||||||
resolve: vi.fn(),
|
|
||||||
readFileSync: vi.fn(),
|
|
||||||
writeFileSync: vi.fn(),
|
|
||||||
copyFileSync: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock consola
|
|
||||||
vi.mock('consola', () => ({
|
|
||||||
consola: {
|
|
||||||
box: vi.fn(),
|
|
||||||
start: vi.fn(),
|
|
||||||
success: vi.fn(),
|
|
||||||
fail: vi.fn(),
|
|
||||||
error: vi.fn(),
|
|
||||||
},
|
|
||||||
}))
|
|
||||||
|
|
||||||
// Mock mdToHTML
|
|
||||||
vi.mock('../command/md-to-html', () => ({
|
|
||||||
mdToHTML: vi.fn(),
|
|
||||||
}))
|
|
||||||
|
|
||||||
describe('prep-now', () => {
|
|
||||||
// const mockNow = {
|
|
||||||
// title: 'Test Title',
|
|
||||||
// description: 'Test Description',
|
|
||||||
// publishedOn: '2023-01-01',
|
|
||||||
// publishDate: '2023-01-01',
|
|
||||||
// publishState: 'draft',
|
|
||||||
// content: '# Test Content',
|
|
||||||
// }
|
|
||||||
|
|
||||||
beforeEach(() => {
|
|
||||||
vi.clearAllMocks()
|
|
||||||
vi.mocked(resolve).mockImplementation((...paths) => paths.join('/'))
|
|
||||||
vi.mocked(readFileSync).mockReturnValue('template content')
|
|
||||||
vi.mocked(writeFileSync).mockImplementation(() => {})
|
|
||||||
vi.mocked(copyFileSync).mockImplementation(() => {})
|
|
||||||
vi.mocked(consola.box).mockImplementation(() => {})
|
|
||||||
vi.mocked(consola.start).mockImplementation(() => {})
|
|
||||||
vi.mocked(consola.success).mockImplementation(() => {})
|
|
||||||
vi.mocked(consola.fail).mockImplementation(() => {})
|
|
||||||
vi.mocked(consola.error).mockImplementation(() => {})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should create a new now page and clear the original files', async () => {
|
|
||||||
// Mock the mdToHTML function
|
|
||||||
const { mdToHTML } = await import('../utils/md-to-html')
|
|
||||||
vi.mocked(mdToHTML).mockResolvedValue('<h1>Test Content</h1>')
|
|
||||||
|
|
||||||
// Mock file paths
|
|
||||||
vi.mocked(resolve)
|
|
||||||
.mockReturnValueOnce('/src/constants/now.md')
|
|
||||||
.mockReturnValueOnce('/src/constants/now.json')
|
|
||||||
.mockReturnValueOnce('/src/pages/now/and-then/posts/2023-01-01.astro')
|
|
||||||
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.md')
|
|
||||||
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.json')
|
|
||||||
|
|
||||||
// Mock file content
|
|
||||||
vi.mocked(readFileSync)
|
|
||||||
.mockReturnValueOnce('template content')
|
|
||||||
.mockReturnValueOnce('# Test Content')
|
|
||||||
|
|
||||||
// Mock file paths
|
|
||||||
const __filename = '/src/commands/prep-now.ts'
|
|
||||||
const __dirname = '/src/commands'
|
|
||||||
vi.mocked(fileURLToPath).mockReturnValue(__filename)
|
|
||||||
vi.mocked(dirname).mockReturnValue(__dirname)
|
|
||||||
|
|
||||||
// Execute the function
|
|
||||||
await newNow()
|
|
||||||
|
|
||||||
// Verify file operations
|
|
||||||
expect(writeFileSync).toHaveBeenCalledTimes(2)
|
|
||||||
expect(copyFileSync).toHaveBeenCalledTimes(2)
|
|
||||||
expect(consola.success).toHaveBeenCalledWith('now.md cleared')
|
|
||||||
expect(consola.success).toHaveBeenCalledWith(
|
|
||||||
'You may now update your Now content and props.\n'
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should handle errors gracefully', async () => {
|
|
||||||
// Mock an error
|
|
||||||
vi.mocked(writeFileSync).mockImplementation(() => {
|
|
||||||
throw new Error('Write failed')
|
|
||||||
})
|
|
||||||
|
|
||||||
// Mock file paths
|
|
||||||
vi.mocked(resolve)
|
|
||||||
.mockReturnValueOnce('/src/constants/now.md')
|
|
||||||
.mockReturnValueOnce('/src/constants/now.json')
|
|
||||||
.mockReturnValueOnce('/src/pages/now/and-then/posts/2023-01-01.astro')
|
|
||||||
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.md')
|
|
||||||
.mockReturnValueOnce('/src/constants/bkup/2023-01-01.json')
|
|
||||||
|
|
||||||
// Mock file content
|
|
||||||
vi.mocked(readFileSync)
|
|
||||||
.mockReturnValueOnce('template content')
|
|
||||||
.mockReturnValueOnce('# Test Content')
|
|
||||||
|
|
||||||
// Execute the function
|
|
||||||
await newNow()
|
|
||||||
|
|
||||||
// Verify error handling
|
|
||||||
expect(consola.fail).toHaveBeenCalledWith(
|
|
||||||
'Failed to create a new post from Now'
|
|
||||||
)
|
|
||||||
expect(consola.error).toHaveBeenCalled()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
@ -1,91 +0,0 @@
|
||||||
import { describe, it, expect, vi, beforeEach } from 'vitest'
|
|
||||||
import { install } from 'vitest-dom'
|
|
||||||
|
|
||||||
// Install DOM environment for vitest
|
|
||||||
install()
|
|
||||||
|
|
||||||
// Mock the service worker globals
|
|
||||||
global.caches = {
|
|
||||||
keys: vi.fn(),
|
|
||||||
delete: vi.fn(),
|
|
||||||
open: vi.fn(),
|
|
||||||
match: vi.fn(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock the global self object
|
|
||||||
global.self = {
|
|
||||||
skipWaiting: vi.fn(),
|
|
||||||
addEventListener: vi.fn(),
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mock the fetch function
|
|
||||||
global.fetch = vi.fn()
|
|
||||||
|
|
||||||
// Import the service worker module
|
|
||||||
// Note: We need to use dynamic import to avoid module loading issues
|
|
||||||
let swModule
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
|
||||||
// Clear all mocks
|
|
||||||
vi.clearAllMocks()
|
|
||||||
|
|
||||||
// Mock the module
|
|
||||||
swModule = await import('../src/sw.mjs')
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('Service Worker', () => {
|
|
||||||
describe('cleanOldCaches', () => {
|
|
||||||
it('should delete old caches', async () => {
|
|
||||||
const cacheName = 'app-v000'
|
|
||||||
const oldCacheName = 'app-v001'
|
|
||||||
|
|
||||||
global.caches.keys.mockResolvedValue([cacheName, oldCacheName])
|
|
||||||
global.caches.delete.mockResolvedValue(true)
|
|
||||||
|
|
||||||
await swModule.cleanOldCaches()
|
|
||||||
|
|
||||||
expect(global.caches.delete).toHaveBeenCalledWith(oldCacheName)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('addResourcesToCache', () => {
|
|
||||||
it('should add resources to cache', async () => {
|
|
||||||
const resources = ['/index.html', '/style.css']
|
|
||||||
const cache = { addAll: vi.fn() }
|
|
||||||
|
|
||||||
global.caches.open.mockResolvedValue(cache)
|
|
||||||
|
|
||||||
await swModule.addResourcesToCache(resources)
|
|
||||||
|
|
||||||
expect(cache.addAll).toHaveBeenCalledWith(resources)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('networkFirst', () => {
|
|
||||||
it('should return network response when available', async () => {
|
|
||||||
const request = new Request('/test')
|
|
||||||
const networkResponse = new Response('network content')
|
|
||||||
|
|
||||||
global.fetch.mockResolvedValue(networkResponse)
|
|
||||||
global.caches.open.mockResolvedValue({ put: vi.fn() })
|
|
||||||
|
|
||||||
const result = await swModule.networkFirst({ request })
|
|
||||||
|
|
||||||
expect(result).toEqual(networkResponse)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return cached response when network fails', async () => {
|
|
||||||
const request = new Request('/test')
|
|
||||||
const cachedResponse = new Response('cached content')
|
|
||||||
|
|
||||||
global.fetch.mockRejectedValue(new Error('Network error'))
|
|
||||||
global.caches.open.mockResolvedValue({
|
|
||||||
match: vi.fn().mockResolvedValue(cachedResponse),
|
|
||||||
})
|
|
||||||
|
|
||||||
const result = await swModule.networkFirst({ request })
|
|
||||||
|
|
||||||
expect(result).toEqual(cachedResponse)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
Loading…
Reference in a new issue