Compare commits
7 Commits
523980ba13
...
0.11.0
Author | SHA1 | Date | |
---|---|---|---|
fcae7676c6 | |||
cc16b5435a | |||
27b5e6a36b | |||
bcb91972a1 | |||
b11666decb
|
|||
a947a05041 | |||
297c573281
|
@@ -24,7 +24,7 @@ jobs:
|
||||
- name: Set up Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: 22.17.x
|
||||
node-version: 22.17.1
|
||||
cache: pnpm
|
||||
|
||||
- name: Install Dependencies
|
||||
|
@@ -1,7 +1,7 @@
|
||||
ARG REGISTRY=docker.io
|
||||
FROM ${REGISTRY}/node:22.17.0-alpine3.22 AS base
|
||||
FROM ${REGISTRY}/node:22.17.1-alpine3.22 AS base
|
||||
|
||||
LABEL version="0.10.0"
|
||||
LABEL version="0.11.0"
|
||||
LABEL description="Astro based personal website"
|
||||
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
|
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "site-profile",
|
||||
"type": "module",
|
||||
"version": "0.10.0",
|
||||
"version": "0.11.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
@@ -33,7 +33,7 @@
|
||||
"@tailwindcss/typography": "^0.5.16",
|
||||
"@typescript-eslint/parser": "8.37.0",
|
||||
"eslint": "9.31.0",
|
||||
"eslint-config-prettier": "10.1.5",
|
||||
"eslint-config-prettier": "10.1.8",
|
||||
"eslint-plugin-astro": "1.3.1",
|
||||
"prettier": "^3.5.3",
|
||||
"prettier-plugin-astro": "^0.14.1",
|
||||
|
327
pnpm-lock.yaml
generated
327
pnpm-lock.yaml
generated
@@ -10,13 +10,13 @@ importers:
|
||||
dependencies:
|
||||
'@astrojs/mdx':
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(astro@5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))
|
||||
version: 4.3.1(astro@5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))
|
||||
'@astrojs/node':
|
||||
specifier: ^9.2.2
|
||||
version: 9.3.0(astro@5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))
|
||||
version: 9.3.0(astro@5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))
|
||||
'@astrojs/react':
|
||||
specifier: ^4.3.0
|
||||
version: 4.3.0(@types/node@24.0.14)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yaml@2.8.0)
|
||||
version: 4.3.0(@types/node@24.0.15)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yaml@2.8.0)
|
||||
'@astrojs/rss':
|
||||
specifier: ^4.0.12
|
||||
version: 4.0.12
|
||||
@@ -28,10 +28,10 @@ importers:
|
||||
version: 4.1.11
|
||||
'@tailwindcss/vite':
|
||||
specifier: ^4.1.8
|
||||
version: 4.1.11(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||
version: 4.1.11(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||
astro:
|
||||
specifier: ^5.10.1
|
||||
version: 5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0)
|
||||
version: 5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0)
|
||||
framer-motion:
|
||||
specifier: ^12.16.0
|
||||
version: 12.23.6(react-dom@19.1.0(react@19.1.0))(react@19.1.0)
|
||||
@@ -64,8 +64,8 @@ importers:
|
||||
specifier: 9.31.0
|
||||
version: 9.31.0(jiti@2.4.2)
|
||||
eslint-config-prettier:
|
||||
specifier: 10.1.5
|
||||
version: 10.1.5(eslint@9.31.0(jiti@2.4.2))
|
||||
specifier: 10.1.8
|
||||
version: 10.1.8(eslint@9.31.0(jiti@2.4.2))
|
||||
eslint-plugin-astro:
|
||||
specifier: 1.3.1
|
||||
version: 1.3.1(eslint@9.31.0(jiti@2.4.2))
|
||||
@@ -98,11 +98,11 @@ packages:
|
||||
'@astrojs/internal-helpers@0.6.1':
|
||||
resolution: {integrity: sha512-l5Pqf6uZu31aG+3Lv8nl/3s4DbUzdlxTWDof4pEpto6GUJNhhCbelVi9dEyurOVyqaelwmS9oSyOWOENSfgo9A==}
|
||||
|
||||
'@astrojs/markdown-remark@6.3.2':
|
||||
resolution: {integrity: sha512-bO35JbWpVvyKRl7cmSJD822e8YA8ThR/YbUsciWNA7yTcqpIAL2hJDToWP5KcZBWxGT6IOdOkHSXARSNZc4l/Q==}
|
||||
'@astrojs/markdown-remark@6.3.3':
|
||||
resolution: {integrity: sha512-DDRtD1sPvAuA7ms2btc9A7/7DApKqgLMNrE6kh5tmkfy8utD0Z738gqd3p5aViYYdUtHIyEJ1X4mCMxfCfu15w==}
|
||||
|
||||
'@astrojs/mdx@4.3.0':
|
||||
resolution: {integrity: sha512-OGX2KvPeBzjSSKhkCqrUoDMyzFcjKt5nTE5SFw3RdoLf0nrhyCXBQcCyclzWy1+P+XpOamn+p+hm1EhpCRyPxw==}
|
||||
'@astrojs/mdx@4.3.1':
|
||||
resolution: {integrity: sha512-0ynzkFd5p2IFDLPAfAcGizg44WyS0qUr43nP2vQkvrPlpoPEMeeoi1xWiWsVqQNaZ0FOmNqfUviUn52nm9mLag==}
|
||||
engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0}
|
||||
peerDependencies:
|
||||
astro: ^5.0.0
|
||||
@@ -218,161 +218,161 @@ packages:
|
||||
resolution: {integrity: sha512-DoKggMNaVWiKC2bU5BY+Nf6ia2Yz7hrKte0ZtSPXQVkl2mxwDtM0gLfOI6xxVY1CfnvyxJDu+d+21VjI4GYdyw==}
|
||||
engines: {node: '>=22'}
|
||||
|
||||
'@emnapi/runtime@1.4.4':
|
||||
resolution: {integrity: sha512-hHyapA4A3gPaDCNfiqyZUStTMqIkKRshqPIuDOXv1hcBnD4U3l8cP0T1HMCfGRxQ6V64TGCcoswChANyOAwbQg==}
|
||||
'@emnapi/runtime@1.4.5':
|
||||
resolution: {integrity: sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==}
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.6':
|
||||
resolution: {integrity: sha512-ShbM/3XxwuxjFiuVBHA+d3j5dyac0aEVVq1oluIDf71hUw0aRF59dV/efUsIwFnR6m8JNM2FjZOzmaZ8yG61kw==}
|
||||
'@esbuild/aix-ppc64@0.25.8':
|
||||
resolution: {integrity: sha512-urAvrUedIqEiFR3FYSLTWQgLu5tb+m0qZw0NBEasUeo6wuqatkMDaRT+1uABiGXEu5vqgPd7FGE1BhsAIy9QVA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [aix]
|
||||
|
||||
'@esbuild/android-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-hd5zdUarsK6strW+3Wxi5qWws+rJhCCbMiC9QZyzoxfk5uHRIE8T287giQxzVpEvCwuJ9Qjg6bEjcRJcgfLqoA==}
|
||||
'@esbuild/android-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-OD3p7LYzWpLhZEyATcTSJ67qB5D+20vbtr6vHlHWSQYhKtzUYrETuWThmzFpZtFsBIxRvhO07+UgVA9m0i/O1w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-arm@0.25.6':
|
||||
resolution: {integrity: sha512-S8ToEOVfg++AU/bHwdksHNnyLyVM+eMVAOf6yRKFitnwnbwwPNqKr3srzFRe7nzV69RQKb5DgchIX5pt3L53xg==}
|
||||
'@esbuild/android-arm@0.25.8':
|
||||
resolution: {integrity: sha512-RONsAvGCz5oWyePVnLdZY/HHwA++nxYWIX1atInlaW6SEkwq6XkP3+cb825EUcRs5Vss/lGh/2YxAb5xqc07Uw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/android-x64@0.25.6':
|
||||
resolution: {integrity: sha512-0Z7KpHSr3VBIO9A/1wcT3NTy7EB4oNC4upJ5ye3R7taCc2GUdeynSLArnon5G8scPwaU866d3H4BCrE5xLW25A==}
|
||||
'@esbuild/android-x64@0.25.8':
|
||||
resolution: {integrity: sha512-yJAVPklM5+4+9dTeKwHOaA+LQkmrKFX96BM0A/2zQrbS6ENCmxc4OVoBs5dPkCCak2roAD+jKCdnmOqKszPkjA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [android]
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-FFCssz3XBavjxcFxKsGy2DYK5VSvJqa6y5HXljKzhRZ87LvEi13brPrf/wdyl/BbpbMKJNOr1Sd0jtW4Ge1pAA==}
|
||||
'@esbuild/darwin-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-Jw0mxgIaYX6R8ODrdkLLPwBqHTtYHJSmzzd+QeytSugzQ0Vg4c5rDky5VgkoowbZQahCbsv1rT1KW72MPIkevw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/darwin-x64@0.25.6':
|
||||
resolution: {integrity: sha512-GfXs5kry/TkGM2vKqK2oyiLFygJRqKVhawu3+DOCk7OxLy/6jYkWXhlHwOoTb0WqGnWGAS7sooxbZowy+pK9Yg==}
|
||||
'@esbuild/darwin-x64@0.25.8':
|
||||
resolution: {integrity: sha512-Vh2gLxxHnuoQ+GjPNvDSDRpoBCUzY4Pu0kBqMBDlK4fuWbKgGtmDIeEC081xi26PPjn+1tct+Bh8FjyLlw1Zlg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [darwin]
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-aoLF2c3OvDn2XDTRvn8hN6DRzVVpDlj2B/F66clWd/FHLiHaG3aVZjxQX2DYphA5y/evbdGvC6Us13tvyt4pWg==}
|
||||
'@esbuild/freebsd-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-YPJ7hDQ9DnNe5vxOm6jaie9QsTwcKedPvizTVlqWG9GBSq+BuyWEDazlGaDTC5NGU4QJd666V0yqCBL2oWKPfA==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.6':
|
||||
resolution: {integrity: sha512-2SkqTjTSo2dYi/jzFbU9Plt1vk0+nNg8YC8rOXXea+iA3hfNJWebKYPs3xnOUf9+ZWhKAaxnQNUf2X9LOpeiMQ==}
|
||||
'@esbuild/freebsd-x64@0.25.8':
|
||||
resolution: {integrity: sha512-MmaEXxQRdXNFsRN/KcIimLnSJrk2r5H8v+WVafRWz5xdSVmWLoITZQXcgehI2ZE6gioE6HirAEToM/RvFBeuhw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [freebsd]
|
||||
|
||||
'@esbuild/linux-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-b967hU0gqKd9Drsh/UuAm21Khpoh6mPBSgz8mKRq4P5mVK8bpA+hQzmm/ZwGVULSNBzKdZPQBRT3+WuVavcWsQ==}
|
||||
'@esbuild/linux-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-WIgg00ARWv/uYLU7lsuDK00d/hHSfES5BzdWAdAig1ioV5kaFNrtK8EqGcUBJhYqotlUByUKz5Qo6u8tt7iD/w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-arm@0.25.6':
|
||||
resolution: {integrity: sha512-SZHQlzvqv4Du5PrKE2faN0qlbsaW/3QQfUUc6yO2EjFcA83xnwm91UbEEVx4ApZ9Z5oG8Bxz4qPE+HFwtVcfyw==}
|
||||
'@esbuild/linux-arm@0.25.8':
|
||||
resolution: {integrity: sha512-FuzEP9BixzZohl1kLf76KEVOsxtIBFwCaLupVuk4eFVnOZfU+Wsn+x5Ryam7nILV2pkq2TqQM9EZPsOBuMC+kg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ia32@0.25.6':
|
||||
resolution: {integrity: sha512-aHWdQ2AAltRkLPOsKdi3xv0mZ8fUGPdlKEjIEhxCPm5yKEThcUjHpWB1idN74lfXGnZ5SULQSgtr5Qos5B0bPw==}
|
||||
'@esbuild/linux-ia32@0.25.8':
|
||||
resolution: {integrity: sha512-A1D9YzRX1i+1AJZuFFUMP1E9fMaYY+GnSQil9Tlw05utlE86EKTUA7RjwHDkEitmLYiFsRd9HwKBPEftNdBfjg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-loong64@0.25.6':
|
||||
resolution: {integrity: sha512-VgKCsHdXRSQ7E1+QXGdRPlQ/e08bN6WMQb27/TMfV+vPjjTImuT9PmLXupRlC90S1JeNNW5lzkAEO/McKeJ2yg==}
|
||||
'@esbuild/linux-loong64@0.25.8':
|
||||
resolution: {integrity: sha512-O7k1J/dwHkY1RMVvglFHl1HzutGEFFZ3kNiDMSOyUrB7WcoHGf96Sh+64nTRT26l3GMbCW01Ekh/ThKM5iI7hQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [loong64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.6':
|
||||
resolution: {integrity: sha512-WViNlpivRKT9/py3kCmkHnn44GkGXVdXfdc4drNmRl15zVQ2+D2uFwdlGh6IuK5AAnGTo2qPB1Djppj+t78rzw==}
|
||||
'@esbuild/linux-mips64el@0.25.8':
|
||||
resolution: {integrity: sha512-uv+dqfRazte3BzfMp8PAQXmdGHQt2oC/y2ovwpTteqrMx2lwaksiFZ/bdkXJC19ttTvNXBuWH53zy/aTj1FgGw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [mips64el]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.6':
|
||||
resolution: {integrity: sha512-wyYKZ9NTdmAMb5730I38lBqVu6cKl4ZfYXIs31Baf8aoOtB4xSGi3THmDYt4BTFHk7/EcVixkOV2uZfwU3Q2Jw==}
|
||||
'@esbuild/linux-ppc64@0.25.8':
|
||||
resolution: {integrity: sha512-GyG0KcMi1GBavP5JgAkkstMGyMholMDybAf8wF5A70CALlDM2p/f7YFE7H92eDeH/VBtFJA5MT4nRPDGg4JuzQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ppc64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.6':
|
||||
resolution: {integrity: sha512-KZh7bAGGcrinEj4qzilJ4hqTY3Dg2U82c8bv+e1xqNqZCrCyc+TL9AUEn5WGKDzm3CfC5RODE/qc96OcbIe33w==}
|
||||
'@esbuild/linux-riscv64@0.25.8':
|
||||
resolution: {integrity: sha512-rAqDYFv3yzMrq7GIcen3XP7TUEG/4LK86LUPMIz6RT8A6pRIDn0sDcvjudVZBiiTcZCY9y2SgYX2lgK3AF+1eg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [riscv64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-s390x@0.25.6':
|
||||
resolution: {integrity: sha512-9N1LsTwAuE9oj6lHMyyAM+ucxGiVnEqUdp4v7IaMmrwb06ZTEVCIs3oPPplVsnjPfyjmxwHxHMF8b6vzUVAUGw==}
|
||||
'@esbuild/linux-s390x@0.25.8':
|
||||
resolution: {integrity: sha512-Xutvh6VjlbcHpsIIbwY8GVRbwoviWT19tFhgdA7DlenLGC/mbc3lBoVb7jxj9Z+eyGqvcnSyIltYUrkKzWqSvg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [s390x]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/linux-x64@0.25.6':
|
||||
resolution: {integrity: sha512-A6bJB41b4lKFWRKNrWoP2LHsjVzNiaurf7wyj/XtFNTsnPuxwEBWHLty+ZE0dWBKuSK1fvKgrKaNjBS7qbFKig==}
|
||||
'@esbuild/linux-x64@0.25.8':
|
||||
resolution: {integrity: sha512-ASFQhgY4ElXh3nDcOMTkQero4b1lgubskNlhIfJrsH5OKZXDpUAKBlNS0Kx81jwOBp+HCeZqmoJuihTv57/jvQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [linux]
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-IjA+DcwoVpjEvyxZddDqBY+uJ2Snc6duLpjmkXm/v4xuS3H+3FkLZlDm9ZsAbF9rsfP3zeA0/ArNDORZgrxR/Q==}
|
||||
'@esbuild/netbsd-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-d1KfruIeohqAi6SA+gENMuObDbEjn22olAR7egqnkCD9DGBG0wsEARotkLgXDu6c4ncgWTZJtN5vcgxzWRMzcw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.6':
|
||||
resolution: {integrity: sha512-dUXuZr5WenIDlMHdMkvDc1FAu4xdWixTCRgP7RQLBOkkGgwuuzaGSYcOpW4jFxzpzL1ejb8yF620UxAqnBrR9g==}
|
||||
'@esbuild/netbsd-x64@0.25.8':
|
||||
resolution: {integrity: sha512-nVDCkrvx2ua+XQNyfrujIG38+YGyuy2Ru9kKVNyh5jAys6n+l44tTtToqHjino2My8VAY6Lw9H7RI73XFi66Cg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [netbsd]
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-l8ZCvXP0tbTJ3iaqdNf3pjaOSd5ex/e6/omLIQCVBLmHTlfXW3zAxQ4fnDmPLOB1x9xrcSi/xtCWFwCZRIaEwg==}
|
||||
'@esbuild/openbsd-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-j8HgrDuSJFAujkivSMSfPQSAa5Fxbvk4rgNAS5i3K+r8s1X0p1uOO2Hl2xNsGFppOeHOLAVgYwDVlmxhq5h+SQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.6':
|
||||
resolution: {integrity: sha512-hKrmDa0aOFOr71KQ/19JC7az1P0GWtCN1t2ahYAf4O007DHZt/dW8ym5+CUdJhQ/qkZmI1HAF8KkJbEFtCL7gw==}
|
||||
'@esbuild/openbsd-x64@0.25.8':
|
||||
resolution: {integrity: sha512-1h8MUAwa0VhNCDp6Af0HToI2TJFAn1uqT9Al6DJVzdIBAd21m/G0Yfc77KDM3uF3T/YaOgQq3qTJHPbTOInaIQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [openbsd]
|
||||
|
||||
'@esbuild/openharmony-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-+SqBcAWoB1fYKmpWoQP4pGtx+pUUC//RNYhFdbcSA16617cchuryuhOCRpPsjCblKukAckWsV+aQ3UKT/RMPcA==}
|
||||
'@esbuild/openharmony-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-r2nVa5SIK9tSWd0kJd9HCffnDHKchTGikb//9c7HX+r+wHYCpQrSgxhlY6KWV1nFo1l4KFbsMlHk+L6fekLsUg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [openharmony]
|
||||
|
||||
'@esbuild/sunos-x64@0.25.6':
|
||||
resolution: {integrity: sha512-dyCGxv1/Br7MiSC42qinGL8KkG4kX0pEsdb0+TKhmJZgCUDBGmyo1/ArCjNGiOLiIAgdbWgmWgib4HoCi5t7kA==}
|
||||
'@esbuild/sunos-x64@0.25.8':
|
||||
resolution: {integrity: sha512-zUlaP2S12YhQ2UzUfcCuMDHQFJyKABkAjvO5YSndMiIkMimPmxA+BYSBikWgsRpvyxuRnow4nS5NPnf9fpv41w==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [sunos]
|
||||
|
||||
'@esbuild/win32-arm64@0.25.6':
|
||||
resolution: {integrity: sha512-42QOgcZeZOvXfsCBJF5Afw73t4veOId//XD3i+/9gSkhSV6Gk3VPlWncctI+JcOyERv85FUo7RxuxGy+z8A43Q==}
|
||||
'@esbuild/win32-arm64@0.25.8':
|
||||
resolution: {integrity: sha512-YEGFFWESlPva8hGL+zvj2z/SaK+pH0SwOM0Nc/d+rVnW7GSTFlLBGzZkuSU9kFIGIo8q9X3ucpZhu8PDN5A2sQ==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [arm64]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-ia32@0.25.6':
|
||||
resolution: {integrity: sha512-4AWhgXmDuYN7rJI6ORB+uU9DHLq/erBbuMoAuB4VWJTu5KtCgcKYPynF0YI1VkBNuEfjNlLrFr9KZPJzrtLkrQ==}
|
||||
'@esbuild/win32-ia32@0.25.8':
|
||||
resolution: {integrity: sha512-hiGgGC6KZ5LZz58OL/+qVVoZiuZlUYlYHNAmczOm7bs2oE1XriPFi5ZHHrS8ACpV5EjySrnoCKmcbQMN+ojnHg==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [ia32]
|
||||
os: [win32]
|
||||
|
||||
'@esbuild/win32-x64@0.25.6':
|
||||
resolution: {integrity: sha512-NgJPHHbEpLQgDH2MjQu90pzW/5vvXIZ7KOnPyNBm92A6WgZ/7b6fJyUBjoumLqeOQQGqY2QjQxRo97ah4Sj0cA==}
|
||||
'@esbuild/win32-x64@0.25.8':
|
||||
resolution: {integrity: sha512-cn3Yr7+OaaZq1c+2pe+8yxC8E144SReCQjN6/2ynubzYjvyqZjTXfQJpAcQpsdJq3My7XADANiYGHoFC69pLQw==}
|
||||
engines: {node: '>=18'}
|
||||
cpu: [x64]
|
||||
os: [win32]
|
||||
@@ -867,8 +867,8 @@ packages:
|
||||
'@types/nlcst@2.0.3':
|
||||
resolution: {integrity: sha512-vSYNSDe6Ix3q+6Z7ri9lyWqgGhJTmzRjZRqyq15N0Z/1/UnVsno9G/N40NBijoYx2seFDIl0+B2mgAb9mezUCA==}
|
||||
|
||||
'@types/node@24.0.14':
|
||||
resolution: {integrity: sha512-4zXMWD91vBLGRtHK3YbIoFMia+1nqEz72coM42C5ETjnNCa/heoj7NT1G67iAfOqMmcfhuCZ4uNpyz8EjlAejw==}
|
||||
'@types/node@24.0.15':
|
||||
resolution: {integrity: sha512-oaeTSbCef7U/z7rDeJA138xpG3NuKc64/rZ2qmUFkFJmnMsAPaluIifqyWd8hSSMxyP9oie3dLAqYPblag9KgA==}
|
||||
|
||||
'@types/react-dom@19.1.6':
|
||||
resolution: {integrity: sha512-4hOiT/dwO8Ko0gV1m/TJZYk3y0KBnY9vzDh7W+DH17b2HFSOGgdj33dhihPeuy3l0q23+4e+hoXHV6hCC4dCXw==}
|
||||
@@ -1022,8 +1022,8 @@ packages:
|
||||
resolution: {integrity: sha512-JepyLROIad6f44uyqMF6HKE2QbunNzp3mYKRcPoDGt0QkxXmH222FAFC64WTyQu2Kg8NNEXHTN/sWuUId9sSxw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
astro@5.11.2:
|
||||
resolution: {integrity: sha512-jKJCqp0PMZ1ZpP2xySghsJ1xK7ZNh/ISTRNBf/7khY3iEGq/zup49ZMhNZXK5Cd/dFWP/pdBNHD91SByA42IvQ==}
|
||||
astro@5.12.0:
|
||||
resolution: {integrity: sha512-Oov5JsMFHuUmuO+Nx6plfv3nQNK1Xl/8CgLvR8lBhZTjYnraxhuPX5COVAzbom+YLgwaDfK7KBd8zOEopRf9mg==}
|
||||
engines: {node: 18.20.8 || ^20.3.0 || >=22.0.0, npm: '>=9.6.5', pnpm: '>=7.1.0'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1301,8 +1301,8 @@ packages:
|
||||
esast-util-from-js@2.0.1:
|
||||
resolution: {integrity: sha512-8Ja+rNJ0Lt56Pcf3TAmpBZjmx8ZcK5Ts4cAzIOjsjevg9oSXJnl6SUQ2EevU8tv3h6ZLWmoKL5H4fgWvdvfETw==}
|
||||
|
||||
esbuild@0.25.6:
|
||||
resolution: {integrity: sha512-GVuzuUwtdsghE3ocJ9Bs8PNoF13HNQ5TXbEi2AhvVb8xU1Iwt9Fos9FEamfoee+u/TOsn7GUWc04lz46n2bbTg==}
|
||||
esbuild@0.25.8:
|
||||
resolution: {integrity: sha512-vVC0USHGtMi8+R4Kz8rt6JhEWLxsv9Rnu/lGYbPR8u47B+DCBksq9JarW0zOO7bs37hyOK1l2/oqtbciutL5+Q==}
|
||||
engines: {node: '>=18'}
|
||||
hasBin: true
|
||||
|
||||
@@ -1327,8 +1327,8 @@ packages:
|
||||
peerDependencies:
|
||||
eslint: '>=6.0.0'
|
||||
|
||||
eslint-config-prettier@10.1.5:
|
||||
resolution: {integrity: sha512-zc1UmCpNltmVY34vuLRV61r1K27sWuX39E+uyUnY8xS2Bex88VV9cugG+UZbRSRGtGyFboj+D8JODyme1plMpw==}
|
||||
eslint-config-prettier@10.1.8:
|
||||
resolution: {integrity: sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
eslint: '>=7.0.0'
|
||||
@@ -2478,11 +2478,11 @@ packages:
|
||||
strnum@2.1.1:
|
||||
resolution: {integrity: sha512-7ZvoFTiCnGxBtDqJ//Cu6fWtZtc7Y3x+QOirG15wztbdngGSkht27o2pyGWrVy0b4WAy3jbKmnoK6g5VlVNUUw==}
|
||||
|
||||
style-to-js@1.1.16:
|
||||
resolution: {integrity: sha512-/Q6ld50hKYPH3d/r6nr117TZkHR0w0kGGIVfpG9N6D8NymRPM9RqCUv4pRpJ62E5DqOYx2AFpbZMyCPnjQCnOw==}
|
||||
style-to-js@1.1.17:
|
||||
resolution: {integrity: sha512-xQcBGDxJb6jjFCTzvQtfiPn6YvvP2O8U1MDIPNfJQlWMYfktPy+iGsHE7cssjs7y84d9fQaK4UF3RIJaAHSoYA==}
|
||||
|
||||
style-to-object@1.0.8:
|
||||
resolution: {integrity: sha512-xT47I/Eo0rwJmaXC4oilDGDWLohVhR6o/xAQcPQN8q6QBuZVL8qMYL85kLmST5cPjAorwvqIA4qXTRQoYHaL6g==}
|
||||
style-to-object@1.0.9:
|
||||
resolution: {integrity: sha512-G4qppLgKu/k6FwRpHiGiKPaPTFcG3g4wNVX/Qsfu+RqQM30E7Tyu/TEgxcL9PNLF5pdRLwQdE3YKKf+KF2Dzlw==}
|
||||
|
||||
suf-log@2.5.3:
|
||||
resolution: {integrity: sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==}
|
||||
@@ -2849,7 +2849,7 @@ snapshots:
|
||||
|
||||
'@astrojs/internal-helpers@0.6.1': {}
|
||||
|
||||
'@astrojs/markdown-remark@6.3.2':
|
||||
'@astrojs/markdown-remark@6.3.3':
|
||||
dependencies:
|
||||
'@astrojs/internal-helpers': 0.6.1
|
||||
'@astrojs/prism': 3.3.0
|
||||
@@ -2875,12 +2875,12 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@astrojs/mdx@4.3.0(astro@5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))':
|
||||
'@astrojs/mdx@4.3.1(astro@5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@astrojs/markdown-remark': 6.3.2
|
||||
'@astrojs/markdown-remark': 6.3.3
|
||||
'@mdx-js/mdx': 3.1.0(acorn@8.15.0)
|
||||
acorn: 8.15.0
|
||||
astro: 5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0)
|
||||
astro: 5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0)
|
||||
es-module-lexer: 1.7.0
|
||||
estree-util-visit: 2.0.0
|
||||
hast-util-to-html: 9.0.5
|
||||
@@ -2894,10 +2894,10 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@astrojs/node@9.3.0(astro@5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))':
|
||||
'@astrojs/node@9.3.0(astro@5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@astrojs/internal-helpers': 0.6.1
|
||||
astro: 5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0)
|
||||
astro: 5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0)
|
||||
send: 1.2.0
|
||||
server-destroy: 1.0.1
|
||||
transitivePeerDependencies:
|
||||
@@ -2907,15 +2907,15 @@ snapshots:
|
||||
dependencies:
|
||||
prismjs: 1.30.0
|
||||
|
||||
'@astrojs/react@4.3.0(@types/node@24.0.14)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yaml@2.8.0)':
|
||||
'@astrojs/react@4.3.0(@types/node@24.0.15)(@types/react-dom@19.1.6(@types/react@19.1.6))(@types/react@19.1.6)(jiti@2.4.2)(lightningcss@1.30.1)(react-dom@19.1.0(react@19.1.0))(react@19.1.0)(yaml@2.8.0)':
|
||||
dependencies:
|
||||
'@types/react': 19.1.6
|
||||
'@types/react-dom': 19.1.6(@types/react@19.1.6)
|
||||
'@vitejs/plugin-react': 4.5.1(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||
'@vitejs/plugin-react': 4.5.1(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||
react: 19.1.0
|
||||
react-dom: 19.1.0(react@19.1.0)
|
||||
ultrahtml: 1.6.0
|
||||
vite: 6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- '@types/node'
|
||||
- jiti
|
||||
@@ -3067,87 +3067,87 @@ snapshots:
|
||||
|
||||
'@directus/sdk@20.0.0': {}
|
||||
|
||||
'@emnapi/runtime@1.4.4':
|
||||
'@emnapi/runtime@1.4.5':
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
optional: true
|
||||
|
||||
'@esbuild/aix-ppc64@0.25.6':
|
||||
'@esbuild/aix-ppc64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm64@0.25.6':
|
||||
'@esbuild/android-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-arm@0.25.6':
|
||||
'@esbuild/android-arm@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/android-x64@0.25.6':
|
||||
'@esbuild/android-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-arm64@0.25.6':
|
||||
'@esbuild/darwin-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/darwin-x64@0.25.6':
|
||||
'@esbuild/darwin-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-arm64@0.25.6':
|
||||
'@esbuild/freebsd-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/freebsd-x64@0.25.6':
|
||||
'@esbuild/freebsd-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm64@0.25.6':
|
||||
'@esbuild/linux-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-arm@0.25.6':
|
||||
'@esbuild/linux-arm@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ia32@0.25.6':
|
||||
'@esbuild/linux-ia32@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-loong64@0.25.6':
|
||||
'@esbuild/linux-loong64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-mips64el@0.25.6':
|
||||
'@esbuild/linux-mips64el@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-ppc64@0.25.6':
|
||||
'@esbuild/linux-ppc64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-riscv64@0.25.6':
|
||||
'@esbuild/linux-riscv64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-s390x@0.25.6':
|
||||
'@esbuild/linux-s390x@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/linux-x64@0.25.6':
|
||||
'@esbuild/linux-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-arm64@0.25.6':
|
||||
'@esbuild/netbsd-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/netbsd-x64@0.25.6':
|
||||
'@esbuild/netbsd-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-arm64@0.25.6':
|
||||
'@esbuild/openbsd-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openbsd-x64@0.25.6':
|
||||
'@esbuild/openbsd-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/openharmony-arm64@0.25.6':
|
||||
'@esbuild/openharmony-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/sunos-x64@0.25.6':
|
||||
'@esbuild/sunos-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-arm64@0.25.6':
|
||||
'@esbuild/win32-arm64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-ia32@0.25.6':
|
||||
'@esbuild/win32-ia32@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@esbuild/win32-x64@0.25.6':
|
||||
'@esbuild/win32-x64@0.25.8':
|
||||
optional: true
|
||||
|
||||
'@eslint-community/eslint-utils@4.7.0(eslint@9.31.0(jiti@2.4.2))':
|
||||
@@ -3277,7 +3277,7 @@ snapshots:
|
||||
|
||||
'@img/sharp-wasm32@0.33.5':
|
||||
dependencies:
|
||||
'@emnapi/runtime': 1.4.4
|
||||
'@emnapi/runtime': 1.4.5
|
||||
optional: true
|
||||
|
||||
'@img/sharp-win32-ia32@0.33.5':
|
||||
@@ -3542,12 +3542,12 @@ snapshots:
|
||||
postcss-selector-parser: 6.0.10
|
||||
tailwindcss: 4.1.11
|
||||
|
||||
'@tailwindcss/vite@4.1.11(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))':
|
||||
'@tailwindcss/vite@4.1.11(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@tailwindcss/node': 4.1.11
|
||||
'@tailwindcss/oxide': 4.1.11
|
||||
tailwindcss: 4.1.11
|
||||
vite: 6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
|
||||
'@types/babel__core@7.20.5':
|
||||
dependencies:
|
||||
@@ -3582,7 +3582,7 @@ snapshots:
|
||||
|
||||
'@types/fontkit@2.0.8':
|
||||
dependencies:
|
||||
'@types/node': 24.0.14
|
||||
'@types/node': 24.0.15
|
||||
|
||||
'@types/hast@3.0.4':
|
||||
dependencies:
|
||||
@@ -3602,7 +3602,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/unist': 3.0.3
|
||||
|
||||
'@types/node@24.0.14':
|
||||
'@types/node@24.0.15':
|
||||
dependencies:
|
||||
undici-types: 7.8.0
|
||||
|
||||
@@ -3727,7 +3727,7 @@ snapshots:
|
||||
|
||||
'@ungap/structured-clone@1.3.0': {}
|
||||
|
||||
'@vitejs/plugin-react@4.5.1(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))':
|
||||
'@vitejs/plugin-react@4.5.1(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))':
|
||||
dependencies:
|
||||
'@babel/core': 7.27.4
|
||||
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.27.4)
|
||||
@@ -3735,7 +3735,7 @@ snapshots:
|
||||
'@rolldown/pluginutils': 1.0.0-beta.9
|
||||
'@types/babel__core': 7.20.5
|
||||
react-refresh: 0.17.0
|
||||
vite: 6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
@@ -3796,11 +3796,11 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
astro@5.11.2(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0):
|
||||
astro@5.12.0(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(rollup@4.45.1)(typescript@5.8.3)(yaml@2.8.0):
|
||||
dependencies:
|
||||
'@astrojs/compiler': 2.12.2
|
||||
'@astrojs/internal-helpers': 0.6.1
|
||||
'@astrojs/markdown-remark': 6.3.2
|
||||
'@astrojs/markdown-remark': 6.3.3
|
||||
'@astrojs/telemetry': 3.3.0
|
||||
'@capsizecss/unpack': 2.4.0
|
||||
'@oslojs/encoding': 1.1.0
|
||||
@@ -3821,7 +3821,7 @@ snapshots:
|
||||
dlv: 1.1.3
|
||||
dset: 3.1.4
|
||||
es-module-lexer: 1.7.0
|
||||
esbuild: 0.25.6
|
||||
esbuild: 0.25.8
|
||||
estree-walker: 3.0.3
|
||||
flattie: 1.1.1
|
||||
fontace: 0.3.0
|
||||
@@ -3843,6 +3843,7 @@ snapshots:
|
||||
rehype: 13.0.2
|
||||
semver: 7.7.2
|
||||
shiki: 3.8.1
|
||||
smol-toml: 1.4.1
|
||||
tinyexec: 0.3.2
|
||||
tinyglobby: 0.2.14
|
||||
tsconfck: 3.1.6(typescript@5.8.3)
|
||||
@@ -3851,8 +3852,8 @@ snapshots:
|
||||
unist-util-visit: 5.0.0
|
||||
unstorage: 1.16.1
|
||||
vfile: 6.0.3
|
||||
vite: 6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
vitefu: 1.1.1(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||
vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
vitefu: 1.1.1(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0))
|
||||
xxhash-wasm: 1.1.0
|
||||
yargs-parser: 21.1.1
|
||||
yocto-spinner: 0.2.3
|
||||
@@ -4135,34 +4136,34 @@ snapshots:
|
||||
esast-util-from-estree: 2.0.0
|
||||
vfile-message: 4.0.2
|
||||
|
||||
esbuild@0.25.6:
|
||||
esbuild@0.25.8:
|
||||
optionalDependencies:
|
||||
'@esbuild/aix-ppc64': 0.25.6
|
||||
'@esbuild/android-arm': 0.25.6
|
||||
'@esbuild/android-arm64': 0.25.6
|
||||
'@esbuild/android-x64': 0.25.6
|
||||
'@esbuild/darwin-arm64': 0.25.6
|
||||
'@esbuild/darwin-x64': 0.25.6
|
||||
'@esbuild/freebsd-arm64': 0.25.6
|
||||
'@esbuild/freebsd-x64': 0.25.6
|
||||
'@esbuild/linux-arm': 0.25.6
|
||||
'@esbuild/linux-arm64': 0.25.6
|
||||
'@esbuild/linux-ia32': 0.25.6
|
||||
'@esbuild/linux-loong64': 0.25.6
|
||||
'@esbuild/linux-mips64el': 0.25.6
|
||||
'@esbuild/linux-ppc64': 0.25.6
|
||||
'@esbuild/linux-riscv64': 0.25.6
|
||||
'@esbuild/linux-s390x': 0.25.6
|
||||
'@esbuild/linux-x64': 0.25.6
|
||||
'@esbuild/netbsd-arm64': 0.25.6
|
||||
'@esbuild/netbsd-x64': 0.25.6
|
||||
'@esbuild/openbsd-arm64': 0.25.6
|
||||
'@esbuild/openbsd-x64': 0.25.6
|
||||
'@esbuild/openharmony-arm64': 0.25.6
|
||||
'@esbuild/sunos-x64': 0.25.6
|
||||
'@esbuild/win32-arm64': 0.25.6
|
||||
'@esbuild/win32-ia32': 0.25.6
|
||||
'@esbuild/win32-x64': 0.25.6
|
||||
'@esbuild/aix-ppc64': 0.25.8
|
||||
'@esbuild/android-arm': 0.25.8
|
||||
'@esbuild/android-arm64': 0.25.8
|
||||
'@esbuild/android-x64': 0.25.8
|
||||
'@esbuild/darwin-arm64': 0.25.8
|
||||
'@esbuild/darwin-x64': 0.25.8
|
||||
'@esbuild/freebsd-arm64': 0.25.8
|
||||
'@esbuild/freebsd-x64': 0.25.8
|
||||
'@esbuild/linux-arm': 0.25.8
|
||||
'@esbuild/linux-arm64': 0.25.8
|
||||
'@esbuild/linux-ia32': 0.25.8
|
||||
'@esbuild/linux-loong64': 0.25.8
|
||||
'@esbuild/linux-mips64el': 0.25.8
|
||||
'@esbuild/linux-ppc64': 0.25.8
|
||||
'@esbuild/linux-riscv64': 0.25.8
|
||||
'@esbuild/linux-s390x': 0.25.8
|
||||
'@esbuild/linux-x64': 0.25.8
|
||||
'@esbuild/netbsd-arm64': 0.25.8
|
||||
'@esbuild/netbsd-x64': 0.25.8
|
||||
'@esbuild/openbsd-arm64': 0.25.8
|
||||
'@esbuild/openbsd-x64': 0.25.8
|
||||
'@esbuild/openharmony-arm64': 0.25.8
|
||||
'@esbuild/sunos-x64': 0.25.8
|
||||
'@esbuild/win32-arm64': 0.25.8
|
||||
'@esbuild/win32-ia32': 0.25.8
|
||||
'@esbuild/win32-x64': 0.25.8
|
||||
|
||||
escalade@3.2.0: {}
|
||||
|
||||
@@ -4177,7 +4178,7 @@ snapshots:
|
||||
eslint: 9.31.0(jiti@2.4.2)
|
||||
semver: 7.7.2
|
||||
|
||||
eslint-config-prettier@10.1.5(eslint@9.31.0(jiti@2.4.2)):
|
||||
eslint-config-prettier@10.1.8(eslint@9.31.0(jiti@2.4.2)):
|
||||
dependencies:
|
||||
eslint: 9.31.0(jiti@2.4.2)
|
||||
|
||||
@@ -4481,7 +4482,7 @@ snapshots:
|
||||
mdast-util-mdxjs-esm: 2.0.1
|
||||
property-information: 7.1.0
|
||||
space-separated-tokens: 2.0.2
|
||||
style-to-js: 1.1.16
|
||||
style-to-js: 1.1.17
|
||||
unist-util-position: 5.0.0
|
||||
zwitch: 2.0.4
|
||||
transitivePeerDependencies:
|
||||
@@ -4515,7 +4516,7 @@ snapshots:
|
||||
mdast-util-mdxjs-esm: 2.0.1
|
||||
property-information: 7.1.0
|
||||
space-separated-tokens: 2.0.2
|
||||
style-to-js: 1.1.16
|
||||
style-to-js: 1.1.17
|
||||
unist-util-position: 5.0.0
|
||||
vfile-message: 4.0.2
|
||||
transitivePeerDependencies:
|
||||
@@ -5717,11 +5718,11 @@ snapshots:
|
||||
|
||||
strnum@2.1.1: {}
|
||||
|
||||
style-to-js@1.1.16:
|
||||
style-to-js@1.1.17:
|
||||
dependencies:
|
||||
style-to-object: 1.0.8
|
||||
style-to-object: 1.0.9
|
||||
|
||||
style-to-object@1.0.8:
|
||||
style-to-object@1.0.9:
|
||||
dependencies:
|
||||
inline-style-parser: 0.2.4
|
||||
|
||||
@@ -5918,24 +5919,24 @@ snapshots:
|
||||
'@types/unist': 3.0.3
|
||||
vfile-message: 4.0.2
|
||||
|
||||
vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0):
|
||||
vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0):
|
||||
dependencies:
|
||||
esbuild: 0.25.6
|
||||
esbuild: 0.25.8
|
||||
fdir: 6.4.6(picomatch@4.0.3)
|
||||
picomatch: 4.0.3
|
||||
postcss: 8.5.6
|
||||
rollup: 4.45.1
|
||||
tinyglobby: 0.2.14
|
||||
optionalDependencies:
|
||||
'@types/node': 24.0.14
|
||||
'@types/node': 24.0.15
|
||||
fsevents: 2.3.3
|
||||
jiti: 2.4.2
|
||||
lightningcss: 1.30.1
|
||||
yaml: 2.8.0
|
||||
|
||||
vitefu@1.1.1(vite@6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)):
|
||||
vitefu@1.1.1(vite@6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)):
|
||||
optionalDependencies:
|
||||
vite: 6.3.5(@types/node@24.0.14)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
vite: 6.3.5(@types/node@24.0.15)(jiti@2.4.2)(lightningcss@1.30.1)(yaml@2.8.0)
|
||||
|
||||
web-namespaces@2.0.1: {}
|
||||
|
||||
|
@@ -55,13 +55,13 @@
|
||||
/* Grid pattern for dots */
|
||||
.bg-grid-pattern {
|
||||
background-size: 24px 24px;
|
||||
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.15) 1px, transparent 1px);
|
||||
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.2) 1px, transparent 1px);
|
||||
transition: background-image 0.7s cubic-bezier(0.65, 0, 0.35, 1);
|
||||
}
|
||||
|
||||
/* Dark mode version */
|
||||
:global(.dark) .bg-grid-pattern {
|
||||
background-image: radial-gradient(circle, rgba(255, 255, 255, 0.1) 1px, transparent 1px);
|
||||
background-image: radial-gradient(circle, rgba(255, 255, 255, 0.15) 1px, transparent 1px);
|
||||
}
|
||||
|
||||
/* Ambient glow animations */
|
||||
|
@@ -10,8 +10,8 @@ const currentYear = new Date().getFullYear();
|
||||
const navLinks = [
|
||||
{ text: 'Home', href: '/' },
|
||||
{ text: 'Blog', href: '/blog' },
|
||||
{ text: 'Topics', href: '/topics' },
|
||||
{ text: 'About', href: '/about' },
|
||||
{ text: 'RSS', href: '/rss' },
|
||||
];
|
||||
|
||||
const socialLinks = [
|
||||
@@ -60,17 +60,14 @@ const socialLinks = [
|
||||
<a href="/" class="group inline-block">
|
||||
<div class="flex items-center">
|
||||
<div
|
||||
class="relative flex h-10 w-10 transform items-center justify-center overflow-hidden rounded-lg bg-gradient-to-br from-zinc-800 to-zinc-600 shadow-lg transition-transform group-hover:scale-105 dark:from-zinc-200 dark:to-zinc-400"
|
||||
class="relative flex h-10 w-10 transform items-center justify-center overflow-hidden rounded-lg bg-gradient-to-br from-zinc-800 to-zinc-600 shadow-lg transition-transform dark:from-zinc-200 dark:to-zinc-400"
|
||||
>
|
||||
<span
|
||||
class="theme-transition-all text-xl font-bold text-white transition-transform duration-300 group-hover:scale-110 dark:text-zinc-900"
|
||||
class="theme-transition-all text-xl font-bold text-zinc-100 duration-300 dark:text-zinc-900"
|
||||
>
|
||||
{global.initals}
|
||||
</span>
|
||||
<div
|
||||
class="absolute inset-0 bg-gradient-to-br from-zinc-700 to-zinc-900 opacity-0 transition-opacity duration-300 group-hover:opacity-100 dark:from-zinc-300 dark:to-zinc-100"
|
||||
>
|
||||
</div>
|
||||
<div class="absolute inset-0"></div>
|
||||
</div>
|
||||
<span
|
||||
class="theme-transition-color ml-3 text-xl font-bold text-zinc-900 dark:text-zinc-100"
|
||||
@@ -129,7 +126,6 @@ const socialLinks = [
|
||||
>
|
||||
<span class="relative inline-block overflow-hidden">
|
||||
<span class="relative z-10">{link.text}</span>
|
||||
<span class="absolute bottom-0 left-0 h-0.5 w-0 bg-zinc-800 transition-all duration-300 group-hover:w-full dark:bg-zinc-200" />
|
||||
</span>
|
||||
</a>
|
||||
</li>
|
||||
|
@@ -10,7 +10,22 @@ const parsedDate = typeof date === 'string' ? new Date(date) : date;
|
||||
|
||||
{
|
||||
parsedDate && (
|
||||
<time datetime={parsedDate.toISOString()}>
|
||||
<time datetime={parsedDate.toISOString()} class="z-10 flex items-center gap-1.5">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0
|
||||
A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
||||
{parsedDate.toLocaleDateString('en-us', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
|
@@ -5,12 +5,13 @@ import directus from '../../lib/directus';
|
||||
import { readSingleton } from '@directus/sdk';
|
||||
|
||||
const global = await directus.request(readSingleton('global'));
|
||||
const links = await directus.request(readSingleton('links'));
|
||||
|
||||
const navItems = [
|
||||
{ text: 'Home', href: '/' },
|
||||
{ text: 'Blog', href: '/blog' },
|
||||
{ text: 'Topics', href: '/topics' },
|
||||
{ text: 'About', href: '/about' },
|
||||
{ text: 'Gitea', href: links.gitea },
|
||||
{ text: 'RSS', href: 'rss.xml' },
|
||||
];
|
||||
|
||||
@@ -36,8 +37,8 @@ const currentPath = pathname.slice(1);
|
||||
href={item.href}
|
||||
class={`text-sm font-medium ${
|
||||
isActive
|
||||
? 'text-zinc-900 dark:text-white'
|
||||
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white'
|
||||
? 'text-zinc-900 dark:text-zinc-100'
|
||||
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100'
|
||||
}`}
|
||||
>
|
||||
{item.text}
|
||||
@@ -101,8 +102,8 @@ const currentPath = pathname.slice(1);
|
||||
href={item.href}
|
||||
class={`mobile-nav-item translate-y-4 text-lg font-medium opacity-0 ${
|
||||
isActive
|
||||
? 'text-zinc-900 dark:text-white'
|
||||
: 'text-zinc-600 hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-white'
|
||||
? 'text-zinc-900 dark:text-zinc-100'
|
||||
: 'text-zinc-600 group-hover:text-zinc-900 dark:text-zinc-400 dark:group-hover:text-zinc-100'
|
||||
}`}
|
||||
style={`transition-delay: ${index * 0.05}s;`}
|
||||
>
|
||||
|
@@ -8,16 +8,21 @@ const { tags = [], class: className = '' } = Astro.props;
|
||||
---
|
||||
|
||||
{
|
||||
tags.length > 0 && (
|
||||
<div class={`mt-3 flex flex-wrap gap-2 ${className}`}>
|
||||
{tags.map((tag) => (
|
||||
tags && (
|
||||
<div class={`mb-3 flex flex-wrap justify-center gap-2 sm:mb-0 sm:justify-start ${className}`}>
|
||||
{tags.slice(0, 2).map((postTag) => (
|
||||
<a
|
||||
href={`/tag/${tag}`}
|
||||
class="inline-flex items-center rounded-full bg-zinc-100 px-2.5 py-0.5 text-xs font-medium text-zinc-800 hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700"
|
||||
href={`/tags/${postTag}`}
|
||||
class={`inline-flex items-center rounded-full bg-zinc-100 px-2.5 py-0.5 text-xs font-medium text-zinc-600 transition-colors hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-400 dark:hover:bg-zinc-700`}
|
||||
>
|
||||
{tag}
|
||||
#{postTag}
|
||||
</a>
|
||||
))}
|
||||
{tags.length > 2 && (
|
||||
<span class="inline-flex items-center rounded-full bg-zinc-50 px-2 py-0.5 text-xs text-zinc-500 dark:bg-zinc-800/50 dark:text-zinc-400">
|
||||
+{tags.length - 3}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@@ -18,7 +18,6 @@ export async function getStaticPaths() {
|
||||
}
|
||||
|
||||
const post = Astro.props;
|
||||
const published_date: string = post.published_date.toLocaleString();
|
||||
|
||||
let canonicalURL;
|
||||
try {
|
||||
@@ -30,19 +29,34 @@ try {
|
||||
---
|
||||
|
||||
<Layout title={post.title} description={post.description}>
|
||||
<article class="prose prose-zinc dark:prose-invert lg:prose-lg mx-auto max-w-4xl">
|
||||
<div class="mb-12">
|
||||
<article
|
||||
class="prose prose-zinc dark:prose-invert lg:prose-lg mx-auto max-w-4xl"
|
||||
transition:animate="slide"
|
||||
>
|
||||
<div class="hero-text mb-12">
|
||||
<h1
|
||||
class="mb-4 text-4xl font-bold tracking-tight text-zinc-900 sm:text-5xl dark:text-zinc-100"
|
||||
>
|
||||
{post.title}
|
||||
</h1>
|
||||
|
||||
<div class="mb-6 flex items-center gap-x-4 text-sm text-zinc-500 dark:text-zinc-400">
|
||||
<FormattedDate date={published_date} />
|
||||
<p
|
||||
class="mb-2 line-clamp-2 text-center text-sm text-zinc-600 sm:mb-3 sm:line-clamp-3 sm:text-left sm:text-base dark:text-zinc-400"
|
||||
>
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
<div
|
||||
class="hero-text mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400"
|
||||
>
|
||||
<FormattedDate date={post.published_date} />
|
||||
</div>
|
||||
|
||||
<TagList tags={post.tags} class="mt-2" />
|
||||
<div
|
||||
class="hero-text mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400"
|
||||
>
|
||||
<TagList tags={post.tags} />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hero image -->
|
||||
@@ -85,7 +99,60 @@ try {
|
||||
<slot name="after-article" />
|
||||
</Layout>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
// Add smooth reveal animations for content after loading
|
||||
const animateContent = () => {
|
||||
// Animate hero section
|
||||
const heroElements = document.querySelectorAll(
|
||||
'.hero-text div, .hero-text ~ div, .hero-text h1, .hero-text span, .hero-text p, .hero-text + a'
|
||||
);
|
||||
heroElements.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
100 + index * 150
|
||||
);
|
||||
});
|
||||
|
||||
// Animate posts with staggered delay
|
||||
const articles = document.querySelectorAll('article.group');
|
||||
articles.forEach((article, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
article.classList.add('animate-reveal');
|
||||
},
|
||||
500 + index * 150
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
animateContent();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Content reveal animations */
|
||||
.hero-text h1,
|
||||
.hero-text div,
|
||||
.hero-text ~ div,
|
||||
.hero-text span,
|
||||
.hero-text p,
|
||||
.hero-text + a,
|
||||
article.group {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition:
|
||||
opacity 0.8s ease,
|
||||
transform 0.8s ease;
|
||||
}
|
||||
|
||||
.animate-reveal {
|
||||
opacity: 1 !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
/* Hero image styling */
|
||||
article img:first-of-type {
|
||||
border-radius: 1rem;
|
||||
|
@@ -7,22 +7,6 @@ import Layout from '../layouts/Layout.astro';
|
||||
class="relative flex min-h-[80vh] flex-col items-center justify-center overflow-hidden px-4 py-20 text-center"
|
||||
transition:animate="slide"
|
||||
>
|
||||
<!-- Animated background elements -->
|
||||
<div class="absolute inset-0 overflow-hidden">
|
||||
<div
|
||||
class="animate-blob absolute -top-20 -left-20 h-64 w-64 rounded-full bg-zinc-100 opacity-50 blur-3xl dark:bg-zinc-800/50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 absolute top-1/2 right-1/4 h-96 w-96 rounded-full bg-zinc-200 opacity-30 blur-3xl dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-4000 absolute bottom-20 left-1/3 h-72 w-72 rounded-full bg-zinc-100 opacity-40 blur-3xl dark:bg-zinc-800/40"
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Main content with animation -->
|
||||
<div class="relative z-10 mx-auto max-w-xl">
|
||||
<div class="glitch-wrapper">
|
||||
@@ -135,34 +119,6 @@ import Layout from '../layouts/Layout.astro';
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Animation for floating blobs */
|
||||
@keyframes blob {
|
||||
0% {
|
||||
transform: translate(0px, 0px) scale(1);
|
||||
}
|
||||
33% {
|
||||
transform: translate(30px, -50px) scale(1.1);
|
||||
}
|
||||
66% {
|
||||
transform: translate(-20px, 20px) scale(0.9);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0px, 0px) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-blob {
|
||||
animation: blob 7s infinite;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.animation-delay-4000 {
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
/* Glitch effect for 404 text */
|
||||
.glitch-wrapper {
|
||||
position: relative;
|
||||
|
@@ -22,20 +22,10 @@ const skills = await directus.request(
|
||||
>
|
||||
<!-- Hero Section -->
|
||||
<div class="relative mb-12 sm:mb-16 md:mb-20">
|
||||
<!-- Decorative elements -->
|
||||
<div
|
||||
class="animate-blob theme-transition-bg absolute -top-10 -left-10 h-36 w-36 rounded-full bg-zinc-100 opacity-30 blur-3xl sm:-top-20 sm:-left-20 sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -right-10 -bottom-10 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:-right-20 sm:-bottom-20 sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative grid grid-cols-1 items-center gap-8 md:grid-cols-2 md:gap-12">
|
||||
<div class="order-2 text-center md:order-1 md:text-left">
|
||||
<div class="hero-text order-2 text-center md:order-1 md:text-left">
|
||||
<h1
|
||||
class="theme-transition-color mb-4 text-3xl font-bold tracking-tight text-zinc-900 sm:mb-6 sm:text-4xl md:text-5xl dark:text-zinc-100"
|
||||
class="theme-transition-color hero-text mb-4 text-3xl font-bold tracking-tight text-zinc-900 sm:mb-6 sm:text-4xl md:text-5xl dark:text-zinc-100"
|
||||
>
|
||||
Hello, I'm <span
|
||||
class="theme-transition-all bg-gradient-to-r from-zinc-500 to-zinc-900 bg-clip-text text-transparent dark:from-zinc-300 dark:to-zinc-100"
|
||||
@@ -44,7 +34,7 @@ const skills = await directus.request(
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="theme-transition-color mb-6 text-lg leading-relaxed text-zinc-600 sm:mb-8 sm:text-xl dark:text-zinc-400"
|
||||
class="theme-transition-color hero-text mb-6 text-lg leading-relaxed text-zinc-600 sm:mb-8 sm:text-xl dark:text-zinc-400"
|
||||
>
|
||||
{about.background}
|
||||
</p>
|
||||
@@ -67,13 +57,6 @@ const skills = await directus.request(
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Decorative elements -->
|
||||
<div
|
||||
class="theme-transition-all absolute -right-4 -bottom-4 flex h-16 w-16 items-center justify-center rounded-full border-2 border-white bg-zinc-100 shadow-lg sm:-right-6 sm:-bottom-6 sm:h-20 sm:w-20 sm:border-4 md:h-24 md:w-24 dark:border-zinc-900 dark:bg-zinc-800"
|
||||
>
|
||||
<span class="text-2xl sm:text-3xl">👋</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -93,16 +76,22 @@ const skills = await directus.request(
|
||||
></span>
|
||||
</h2>
|
||||
|
||||
<div class="theme-transition-all prose prose-zinc dark:prose-invert max-w-none">
|
||||
<p class="theme-transition-color mb-4 text-base leading-relaxed sm:mb-6 sm:text-lg">
|
||||
<div class="theme-transition-all hero-text prose prose-zinc dark:prose-invert max-w-none">
|
||||
<p
|
||||
class="theme-transition-color hero-text mb-4 text-base leading-relaxed sm:mb-6 sm:text-lg"
|
||||
>
|
||||
{about.experience}
|
||||
</p>
|
||||
|
||||
<p class="theme-transition-color mb-4 text-base leading-relaxed sm:mb-6 sm:text-lg">
|
||||
<p
|
||||
class="theme-transition-color hero-text mb-4 text-base leading-relaxed sm:mb-6 sm:text-lg"
|
||||
>
|
||||
{about.education}
|
||||
</p>
|
||||
|
||||
<p class="theme-transition-color mb-4 text-base leading-relaxed sm:mb-6 sm:text-lg">
|
||||
<p
|
||||
class="theme-transition-color hero-text mb-4 text-base leading-relaxed sm:mb-6 sm:text-lg"
|
||||
>
|
||||
{about.certifications}
|
||||
</p>
|
||||
</div>
|
||||
@@ -182,57 +171,159 @@ const skills = await directus.request(
|
||||
I'm always open to new opportunities and collaborations. If you'd like to work together or
|
||||
just say hello, feel free to reach out.
|
||||
</p>
|
||||
|
||||
<a
|
||||
href=`mailto:${global.email}`
|
||||
class="hover theme-transition-all inline-flex items-center justify-center rounded-lg bg-zinc-900 px-6 py-3 text-base font-medium text-zinc-100 transition-colors hover:bg-zinc-700 sm:px-8 sm:py-4 sm:text-lg dark:bg-zinc-100 dark:text-zinc-900 dark:hover:bg-zinc-300"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-4 w-4 sm:h-5 sm:w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
<div class="group">
|
||||
<a
|
||||
href=`mailto:${global.email}`
|
||||
class="theme-transition-all inline-flex items-center justify-center rounded-lg bg-zinc-900 px-6 py-3 text-base font-medium text-zinc-100 transition-colors group-hover:bg-blue-600 group-hover:text-zinc-100 sm:px-8 sm:py-4 sm:text-lg dark:bg-zinc-100 dark:text-zinc-900 dark:group-hover:bg-blue-600 dark:group-hover:text-zinc-100"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
Say Hello
|
||||
</a>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
class="mr-2 h-4 w-4 sm:h-5 sm:w-5"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke="currentColor"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M3 8l7.89 5.26a2 2 0 002.22 0L21 8M5 19h14a2 2 0 002-2V7a2 2 0 00-2-2H5a2 2 0 00-2 2v10a2 2 0 002 2z"
|
||||
></path>
|
||||
</svg>
|
||||
<span class="relative inline-block overflow-hidden">
|
||||
<span class="relative z-10">Say Hello</span>
|
||||
<span
|
||||
class="absolute bottom-0 left-0 h-0.5 w-0 bg-zinc-100 transition-all duration-300 group-hover:w-full"
|
||||
></span>
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
// Add smooth reveal animations for content after loading
|
||||
const animateContent = () => {
|
||||
const heroElements = document.querySelectorAll(
|
||||
'.hero-text ~ div, .hero-text h1, .hero-text span, .hero-text p'
|
||||
);
|
||||
heroElements.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
100 + index * 150
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
animateContent();
|
||||
|
||||
// Create seamless infinite scrolling effect
|
||||
const sliderTrack = document.querySelector('.slider-track');
|
||||
function setupInfiniteScroll() {
|
||||
const cards = document.querySelectorAll('.skill-card');
|
||||
if (!cards.length) return;
|
||||
|
||||
// Set proper animation based on screen size
|
||||
function updateScrollAnimation() {
|
||||
if (window.innerWidth >= 640) {
|
||||
sliderTrack.style.animation = 'scroll 60s linear infinite';
|
||||
} else {
|
||||
sliderTrack.style.animation = 'scroll 40s linear infinite';
|
||||
}
|
||||
}
|
||||
|
||||
updateScrollAnimation();
|
||||
window.addEventListener('resize', updateScrollAnimation);
|
||||
}
|
||||
|
||||
setupInfiniteScroll();
|
||||
|
||||
// Pause animation on hover/touch
|
||||
sliderTrack?.addEventListener('mouseenter', () => {
|
||||
sliderTrack.style.animationPlayState = 'paused';
|
||||
});
|
||||
|
||||
sliderTrack?.addEventListener('touchstart', () => {
|
||||
sliderTrack.style.animationPlayState = 'paused';
|
||||
});
|
||||
|
||||
sliderTrack?.addEventListener('mouseleave', () => {
|
||||
sliderTrack.style.animationPlayState = 'running';
|
||||
});
|
||||
|
||||
sliderTrack?.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
sliderTrack.style.animationPlayState = 'running';
|
||||
}, 1000); // Delay resuming animation after touch
|
||||
});
|
||||
|
||||
// Add hover effects to cards - only on non-touch devices
|
||||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
const cards = document.querySelectorAll('.skill-card');
|
||||
|
||||
if (!isTouchDevice) {
|
||||
cards.forEach((card) => {
|
||||
card.addEventListener('mousemove', (e) => {
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const centerX = rect.width / 2;
|
||||
const centerY = rect.height / 2;
|
||||
|
||||
const angleX = (y - centerY) / 15;
|
||||
const angleY = (centerX - x) / 15;
|
||||
|
||||
card.style.transform = `perspective(1000px) rotateX(${angleX}deg) rotateY(${angleY}deg) scale(1.08) translateZ(20px)`;
|
||||
|
||||
// Dynamic shadow based on tilt
|
||||
const shadowX = (x - centerX) / 25;
|
||||
const shadowY = (y - centerY) / 25;
|
||||
card.style.boxShadow = `
|
||||
${shadowX}px ${shadowY}px 20px rgba(0, 0, 0, 0.1),
|
||||
0 10px 20px rgba(0, 0, 0, 0.05)
|
||||
`;
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.style.transform = '';
|
||||
card.style.boxShadow = '';
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Simpler effects for touch devices
|
||||
cards.forEach((card) => {
|
||||
card.addEventListener('touchstart', () => {
|
||||
card.classList.add('is-touched');
|
||||
});
|
||||
|
||||
card.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
card.classList.remove('is-touched');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle theme transition
|
||||
document.addEventListener('themeChange', () => {
|
||||
cards.forEach((card, index) => {
|
||||
setTimeout(() => {
|
||||
card.classList.add('theme-changing');
|
||||
setTimeout(() => {
|
||||
card.classList.remove('theme-changing');
|
||||
}, 600);
|
||||
}, index * 50);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Blob animation */
|
||||
.animate-blob {
|
||||
animation: blob-bounce 8s infinite ease;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
@keyframes blob-bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
25% {
|
||||
transform: translate(5%, 5%) scale(1.05);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, 10%) scale(1);
|
||||
}
|
||||
75% {
|
||||
transform: translate(-5%, 5%) scale(0.95);
|
||||
}
|
||||
}
|
||||
|
||||
/* Tech Stack Slider */
|
||||
.slider-track {
|
||||
width: fit-content;
|
||||
@@ -352,6 +443,23 @@ const skills = await directus.request(
|
||||
}
|
||||
}
|
||||
|
||||
/* Content reveal animations */
|
||||
.hero-text h1,
|
||||
.hero-text span,
|
||||
.hero-text p,
|
||||
.hero-text ~ div {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition:
|
||||
opacity 0.8s ease,
|
||||
transform 0.8s ease;
|
||||
}
|
||||
|
||||
.animate-reveal {
|
||||
opacity: 1 !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
/* Theme transition effect */
|
||||
:global(.theme-switching) .theme-transition-element {
|
||||
animation: fadeIn 0.6s cubic-bezier(0.22, 1, 0.36, 1);
|
||||
@@ -367,108 +475,3 @@ const skills = await directus.request(
|
||||
transform 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const sliderTrack = document.querySelector('.slider-track');
|
||||
|
||||
// Create seamless infinite scrolling effect
|
||||
function setupInfiniteScroll() {
|
||||
const cards = document.querySelectorAll('.skill-card');
|
||||
if (!cards.length) return;
|
||||
|
||||
// Set proper animation based on screen size
|
||||
function updateScrollAnimation() {
|
||||
if (window.innerWidth >= 640) {
|
||||
sliderTrack.style.animation = 'scroll 60s linear infinite';
|
||||
} else {
|
||||
sliderTrack.style.animation = 'scroll 40s linear infinite';
|
||||
}
|
||||
}
|
||||
|
||||
updateScrollAnimation();
|
||||
window.addEventListener('resize', updateScrollAnimation);
|
||||
}
|
||||
|
||||
setupInfiniteScroll();
|
||||
|
||||
// Pause animation on hover/touch
|
||||
sliderTrack?.addEventListener('mouseenter', () => {
|
||||
sliderTrack.style.animationPlayState = 'paused';
|
||||
});
|
||||
|
||||
sliderTrack?.addEventListener('touchstart', () => {
|
||||
sliderTrack.style.animationPlayState = 'paused';
|
||||
});
|
||||
|
||||
sliderTrack?.addEventListener('mouseleave', () => {
|
||||
sliderTrack.style.animationPlayState = 'running';
|
||||
});
|
||||
|
||||
sliderTrack?.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
sliderTrack.style.animationPlayState = 'running';
|
||||
}, 1000); // Delay resuming animation after touch
|
||||
});
|
||||
|
||||
// Add hover effects to cards - only on non-touch devices
|
||||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
const cards = document.querySelectorAll('.skill-card');
|
||||
|
||||
if (!isTouchDevice) {
|
||||
cards.forEach((card) => {
|
||||
card.addEventListener('mousemove', (e) => {
|
||||
const rect = card.getBoundingClientRect();
|
||||
const x = e.clientX - rect.left;
|
||||
const y = e.clientY - rect.top;
|
||||
|
||||
const centerX = rect.width / 2;
|
||||
const centerY = rect.height / 2;
|
||||
|
||||
const angleX = (y - centerY) / 15;
|
||||
const angleY = (centerX - x) / 15;
|
||||
|
||||
card.style.transform = `perspective(1000px) rotateX(${angleX}deg) rotateY(${angleY}deg) scale(1.08) translateZ(20px)`;
|
||||
|
||||
// Dynamic shadow based on tilt
|
||||
const shadowX = (x - centerX) / 25;
|
||||
const shadowY = (y - centerY) / 25;
|
||||
card.style.boxShadow = `
|
||||
${shadowX}px ${shadowY}px 20px rgba(0, 0, 0, 0.1),
|
||||
0 10px 20px rgba(0, 0, 0, 0.05)
|
||||
`;
|
||||
});
|
||||
|
||||
card.addEventListener('mouseleave', () => {
|
||||
card.style.transform = '';
|
||||
card.style.boxShadow = '';
|
||||
});
|
||||
});
|
||||
} else {
|
||||
// Simpler effects for touch devices
|
||||
cards.forEach((card) => {
|
||||
card.addEventListener('touchstart', () => {
|
||||
card.classList.add('is-touched');
|
||||
});
|
||||
|
||||
card.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
card.classList.remove('is-touched');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Handle theme transition
|
||||
document.addEventListener('themeChange', () => {
|
||||
cards.forEach((card, index) => {
|
||||
setTimeout(() => {
|
||||
card.classList.add('theme-changing');
|
||||
setTimeout(() => {
|
||||
card.classList.remove('theme-changing');
|
||||
}, 600);
|
||||
}, index * 50);
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@@ -41,14 +41,14 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
updated_date={post.updated_date}
|
||||
tags={post.tags}
|
||||
>
|
||||
<!-- Main Content - Enhanced with better typography and spacing -->
|
||||
<!-- Main Content -->
|
||||
<div
|
||||
class="prose prose-sm prose-zinc dark:prose-invert sm:prose-base prose-headings:scroll-mt-24 prose-headings:font-semibold prose-a:font-medium prose-a:text-zinc-800 prose-a:underline-offset-4 hover:prose-a:text-zinc-600 prose-img:rounded-xl dark:prose-a:text-zinc-300 dark:hover:prose-a:text-zinc-100 max-w-none"
|
||||
class="hero-text prose prose-sm prose-zinc dark:prose-invert sm:prose-base prose-headings:scroll-mt-24 prose-headings:font-semibold prose-a:font-medium prose-a:text-zinc-800 prose-a:underline-offset-4 hover:prose-a:text-zinc-600 prose-img:rounded-xl dark:prose-a:text-zinc-300 dark:hover:prose-a:text-zinc-100 max-w-none"
|
||||
>
|
||||
<div set:html={post.content} />
|
||||
</div>
|
||||
|
||||
<!-- Next/Previous Navigation - Improved responsive design -->
|
||||
<!-- Next/Previous Navigation -->
|
||||
<div
|
||||
class="mt-12 grid grid-cols-1 gap-4 border-t border-zinc-200 pt-8 sm:mt-16 sm:gap-6 sm:pt-12 md:grid-cols-2 dark:border-zinc-800"
|
||||
>
|
||||
@@ -116,6 +116,63 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
</BlogPost>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
// Show button when scrolled down
|
||||
const backToTopButton = document.getElementById('back-to-top');
|
||||
if (backToTopButton) {
|
||||
const toggleBackToTopButton = () => {
|
||||
if (window.scrollY > 300) {
|
||||
backToTopButton.classList.remove('opacity-0', 'invisible');
|
||||
backToTopButton.classList.add('opacity-100', 'visible');
|
||||
} else {
|
||||
backToTopButton.classList.remove('opacity-100', 'visible');
|
||||
backToTopButton.classList.add('opacity-0', 'invisible');
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll to top when clicked
|
||||
backToTopButton.addEventListener('click', () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
|
||||
// Check scroll position
|
||||
window.addEventListener('scroll', toggleBackToTopButton);
|
||||
toggleBackToTopButton();
|
||||
}
|
||||
|
||||
// Add smooth reveal animations for content after loading
|
||||
const animateContent = () => {
|
||||
// Animate hero section
|
||||
const heroElements = document.querySelectorAll(
|
||||
'.hero-text ~ div, .hero-text h1, .hero-text span, .hero-text p'
|
||||
);
|
||||
heroElements.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
100 + index * 150
|
||||
);
|
||||
});
|
||||
|
||||
// Animate posts with staggered delay
|
||||
const articles = document.querySelectorAll('article.group');
|
||||
articles.forEach((article, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
article.classList.add('animate-reveal');
|
||||
},
|
||||
500 + index * 150
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
animateContent();
|
||||
});
|
||||
|
||||
// Add copy buttons to code blocks
|
||||
function initializeCodeCopyButtons() {
|
||||
const codeBlocks = document.querySelectorAll('pre');
|
||||
@@ -201,6 +258,24 @@ const { post, nextPost, prevPost } = Astro.props;
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Content reveal animations */
|
||||
.hero-text h1,
|
||||
.hero-text span,
|
||||
.hero-text p,
|
||||
.hero-text ~ div,
|
||||
article.group {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition:
|
||||
opacity 0.8s ease,
|
||||
transform 0.8s ease;
|
||||
}
|
||||
|
||||
.animate-reveal {
|
||||
opacity: 1 !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
/* Language badge styling */
|
||||
.language-badge {
|
||||
font-family:
|
||||
|
@@ -1,5 +1,7 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
import FormattedDate from '../../components/FormattedDate.astro';
|
||||
import TagList from '../../components/TagList.astro';
|
||||
|
||||
import directus from '../../../lib/directus';
|
||||
import { readItems } from '@directus/sdk';
|
||||
@@ -11,81 +13,57 @@ const posts = await directus.request(
|
||||
})
|
||||
);
|
||||
|
||||
const sortedPosts = posts.sort((a, b) => b.published_date.valueOf() - a.published_date.valueOf());
|
||||
|
||||
// Group posts by year for timeline effect
|
||||
const sortedPosts = posts.sort((a, b) => b.published_date.valueOf() - a.published_date.valueOf());
|
||||
const postsByYear = sortedPosts.reduce((acc, post) => {
|
||||
const year = new Date(post.published_date).getFullYear();
|
||||
if (!acc[year]) acc[year] = [];
|
||||
acc[year].push(post);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
---
|
||||
|
||||
<BaseLayout title="Blog">
|
||||
<div class="mx-auto w-full max-w-6xl px-4 py-10 sm:px-6 sm:py-16" transition:animate="slide">
|
||||
<div class="relative mb-12 sm:mb-20">
|
||||
<div
|
||||
class="animate-blob absolute -top-10 -left-10 h-48 w-48 rounded-full bg-zinc-100 opacity-30 blur-3xl sm:-top-20 sm:-left-20 sm:h-72 sm:w-72 dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 absolute -right-10 -bottom-10 h-48 w-48 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:-right-20 sm:-bottom-20 sm:h-72 sm:w-72 dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative text-center">
|
||||
<div class="hero-text relative text-center">
|
||||
<h1
|
||||
class="mb-4 text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl md:text-5xl dark:text-zinc-100"
|
||||
class="hero-text mb-4 text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl md:text-5xl dark:text-zinc-100"
|
||||
>
|
||||
Blog
|
||||
</h1>
|
||||
|
||||
<p
|
||||
class="mx-auto mb-6 max-w-2xl text-sm text-zinc-600 sm:mb-10 sm:text-base dark:text-zinc-400"
|
||||
class="hero-text mx-auto mb-6 max-w-2xl text-sm text-zinc-600 sm:mb-10 sm:text-base dark:text-zinc-400"
|
||||
>
|
||||
Thoughts, ideas, and explorations on technology and selfhosting.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Grid layout for mobile experience -->
|
||||
<!-- Featured post -->
|
||||
<div class="grid grid-cols-1 gap-6 sm:gap-8 md:grid-cols-12">
|
||||
<!-- Featured post (if exists) -->
|
||||
{
|
||||
sortedPosts.length > 0 && (
|
||||
<div class="mb-8 sm:mb-12 md:col-span-12">
|
||||
<article class="group relative overflow-hidden rounded-none border-b border-zinc-200 pb-6 sm:pb-8 dark:border-zinc-800">
|
||||
<div class="flex h-full flex-col gap-6 sm:gap-8 md:flex-row">
|
||||
<article class="hover-3d theme-transition-element group relative mx-auto flex max-w-2xl flex-col p-5 sm:mx-0 sm:p-8">
|
||||
<div class="absolute inset-0 rounded-2xl border border-zinc-200 bg-white/50 transition-all duration-300 group-hover:bg-zinc-50 hover:bg-zinc-50/80 hover:shadow-md dark:border-zinc-800 dark:bg-zinc-900/50 dark:group-hover:bg-zinc-800/70 dark:hover:bg-zinc-900/50" />
|
||||
|
||||
<div class="flex flex-col gap-5 sm:flex-row sm:gap-6">
|
||||
{sortedPosts[0].image && (
|
||||
<div class="mx-auto h-60 w-full max-w-full overflow-hidden sm:h-80 sm:max-w-md md:mx-0 md:h-96 md:w-1/2">
|
||||
<div class="z-10 mx-auto h-60 w-full max-w-full overflow-hidden sm:h-80 sm:max-w-md md:mx-0 md:h-96 md:w-1/2">
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${sortedPosts[0].image}`}
|
||||
alt={sortedPosts[0].title}
|
||||
class="h-full w-full object-cover grayscale transition-all duration-700 group-hover:scale-105 hover:grayscale-0"
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${sortedPosts[0].image}?width=500`}
|
||||
alt={sortedPosts[0].image_alt}
|
||||
class="h-full w-full object-cover"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex flex-1 flex-col justify-center">
|
||||
<div class="mb-3 flex items-center justify-center gap-2 text-xs text-zinc-500 sm:text-sm md:justify-start dark:text-zinc-400">
|
||||
<span class="font-medium tracking-wider uppercase">Featured</span>
|
||||
<span class="h-px w-6 bg-zinc-300 sm:w-8 dark:bg-zinc-700" />
|
||||
{sortedPosts[0].published_date && (
|
||||
<time datetime={sortedPosts[0].published_date.toLocaleString()}>
|
||||
{sortedPosts[0].published_date.toLocaleString('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
})}
|
||||
</time>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h2 class="mb-3 text-center text-2xl font-bold text-zinc-900 transition-colors group-hover:text-zinc-700 sm:mb-4 sm:text-3xl md:text-left dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<div class="z-10 flex-1">
|
||||
<h2 class="mb-2 text-center text-xl font-semibold text-zinc-900 sm:mb-3 sm:text-left sm:text-2xl dark:text-zinc-100">
|
||||
<a
|
||||
href={`/blog/${sortedPosts[0].slug}/`}
|
||||
class="before:absolute before:inset-0"
|
||||
@@ -94,23 +72,44 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<p class="mb-4 line-clamp-3 text-center text-sm text-zinc-600 sm:mb-6 sm:text-base md:text-left dark:text-zinc-400">
|
||||
<p class="mb-2 line-clamp-2 text-center text-sm text-zinc-600 sm:mb-3 sm:line-clamp-3 sm:text-left sm:text-base dark:text-zinc-400">
|
||||
{sortedPosts[0].description}
|
||||
</p>
|
||||
|
||||
<div class="flex flex-wrap items-center justify-center gap-3 sm:gap-4 md:justify-start">
|
||||
{sortedPosts[0].tags && (
|
||||
<div class="flex flex-wrap justify-center gap-2 md:justify-start">
|
||||
{sortedPosts[0].tags.slice(0, 2).map((tag) => (
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs tracking-wider text-zinc-600 uppercase sm:px-3 dark:border-zinc-800 dark:text-zinc-400">
|
||||
{tag}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400">
|
||||
<FormattedDate date={sortedPosts[0].published_date} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="z-10 mt-4 flex flex-wrap items-end justify-center border-t border-zinc-100 pt-4 sm:justify-between dark:border-zinc-800">
|
||||
<TagList tags={sortedPosts[0].tags} />
|
||||
|
||||
<div class="mx-auto sm:mr-0 sm:ml-auto">
|
||||
<a
|
||||
href={`/blog/${sortedPosts[0].slug}`}
|
||||
class="theme-transition-color relative z-10 mx-auto mt-3 flex min-h-[44px] items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 sm:mx-0 sm:mt-4 dark:text-zinc-300 dark:group-hover:text-zinc-100"
|
||||
>
|
||||
<span class="relative inline-block overflow-hidden">
|
||||
<span class="relative z-10">Read article</span>
|
||||
<span class="absolute bottom-0 left-0 h-0.5 w-0 bg-zinc-800 transition-all duration-300 group-hover:w-full dark:bg-zinc-200" />
|
||||
</span>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
class="ml-1 h-4 w-4 stroke-current transition-transform duration-300"
|
||||
>
|
||||
<path
|
||||
d="M6.75 5.75 9.25 8l-2.5 2.25"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
)
|
||||
@@ -122,10 +121,9 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
<h3
|
||||
class="mb-4 text-center text-lg font-medium tracking-wider text-zinc-900 uppercase md:text-left dark:text-zinc-100"
|
||||
>
|
||||
Archive
|
||||
History
|
||||
</h3>
|
||||
|
||||
<!-- Horizontal scrollable archive on mobile, vertical on desktop -->
|
||||
<div
|
||||
class="hide-scrollbar flex overflow-x-auto pb-4 md:flex-col md:overflow-visible md:pb-0"
|
||||
>
|
||||
@@ -133,12 +131,12 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
years.map((year, index) => (
|
||||
<a
|
||||
href={`#year-${year}`}
|
||||
class={`hover mr-3 flex items-center rounded-full border-b border-zinc-100 px-4 py-2 whitespace-nowrap transition-colors hover:bg-zinc-50 md:mr-0 md:w-full md:rounded-none md:px-0 md:py-3 md:whitespace-normal dark:border-zinc-800 dark:hover:bg-zinc-900 ${index === 0 ? 'bg-zinc-50 dark:bg-zinc-800/50' : ''}`}
|
||||
class={`mr-3 flex items-center rounded-xl border border-zinc-300 bg-white/50 px-4 py-2 whitespace-nowrap transition-all duration-300 hover:bg-zinc-50 sm:rounded-2xl md:mr-0 md:w-full md:px-0 md:py-3 md:whitespace-normal dark:border-zinc-800 dark:hover:bg-zinc-800/70 ${index === 0 ? 'bg-white/50 dark:bg-zinc-900/50' : ''}`}
|
||||
>
|
||||
<span class="text-base font-medium text-zinc-900 md:text-lg dark:text-zinc-100">
|
||||
<span class="ml-3 text-base font-medium text-zinc-900 md:text-lg dark:text-zinc-100">
|
||||
{year}
|
||||
</span>
|
||||
<span class="ml-2 text-xs text-zinc-500 md:ml-auto md:text-sm dark:text-zinc-400">
|
||||
<span class="mr-3 text-xs text-zinc-500 md:ml-auto md:text-sm dark:text-zinc-400">
|
||||
{postsByYear[year].length} post{postsByYear[year].length !== 1 ? 's' : ''}
|
||||
</span>
|
||||
</a>
|
||||
@@ -148,7 +146,7 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Post grid for mobile -->
|
||||
<!-- Post grid -->
|
||||
<div class="md:col-span-9">
|
||||
{
|
||||
years.map((year) => (
|
||||
@@ -161,13 +159,15 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
class={`grid grid-cols-1 ${postsByYear[year].length >= 2 ? 'md:grid-cols-2' : 'md:grid-cols-1'} gap-8 sm:gap-12`}
|
||||
>
|
||||
{postsByYear[year].map((post) => (
|
||||
<article class="group relative mx-auto flex h-full w-full max-w-sm flex-col sm:max-w-md md:mx-0">
|
||||
<article class="hover-3d theme-transition-element group relative mx-auto flex max-w-2xl flex-col p-5 sm:mx-0 sm:p-8">
|
||||
<div class="absolute inset-0 rounded-2xl border border-zinc-200 bg-white/50 transition-all duration-300 group-hover:bg-zinc-50 hover:bg-zinc-50/80 hover:shadow-md dark:border-zinc-800 dark:bg-zinc-900/50 dark:group-hover:bg-zinc-800/70 dark:hover:bg-zinc-900/50" />
|
||||
|
||||
{post.image && (
|
||||
<div class="mb-4 h-48 overflow-hidden rounded-lg sm:h-56">
|
||||
<div class="relative z-10 mb-4 aspect-video w-full overflow-hidden rounded-lg">
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${post.image}`}
|
||||
alt={post.title}
|
||||
class="h-full w-full object-cover grayscale transition-all duration-700 group-hover:scale-105 hover:grayscale-0"
|
||||
class="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
@@ -188,30 +188,48 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h3 class="mb-2 text-center text-lg font-semibold text-zinc-900 transition-colors group-hover:text-zinc-700 sm:mb-3 sm:text-xl md:text-left dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<h3 class="z-10 mb-2 text-center text-lg font-semibold text-zinc-900 transition-colors group-hover:text-zinc-700 sm:mb-3 sm:text-xl md:text-left dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<a href={`/blog/${post.slug}/`} class="before:absolute before:inset-0">
|
||||
{post.title}
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p class="mb-4 line-clamp-2 grow text-center text-sm text-zinc-600 md:text-left dark:text-zinc-400">
|
||||
<p class="z-10 mb-4 line-clamp-2 grow text-center text-sm text-zinc-600 md:text-left dark:text-zinc-400">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
{post.tags && (
|
||||
<div class="mt-auto flex flex-wrap justify-center gap-2 md:justify-start">
|
||||
{post.tags.slice(0, 2).map((tag) => (
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs tracking-wider text-zinc-600 uppercase sm:px-3 dark:border-zinc-800 dark:text-zinc-400">
|
||||
{tag}
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400">
|
||||
<FormattedDate date={post.published_date} />
|
||||
</div>
|
||||
|
||||
<div class="z-10 mt-4 flex flex-wrap items-end justify-center border-t border-zinc-100 pt-4 sm:justify-between dark:border-zinc-800">
|
||||
<TagList tags={post.tags} />
|
||||
|
||||
<div class="mx-auto sm:mr-0 sm:ml-auto">
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
class="theme-transition-color relative z-10 mx-auto mt-3 flex min-h-[44px] items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 sm:mx-0 sm:mt-4 dark:text-zinc-300 dark:group-hover:text-zinc-100"
|
||||
>
|
||||
<span class="relative inline-block overflow-hidden">
|
||||
<span class="relative z-10">Read article</span>
|
||||
<span class="absolute bottom-0 left-0 h-0.5 w-0 bg-zinc-800 transition-all duration-300 group-hover:w-full dark:bg-zinc-200" />
|
||||
</span>
|
||||
))}
|
||||
{post.tags.length > 2 && (
|
||||
<span class="border border-zinc-200 px-2 py-1 text-xs tracking-wider text-zinc-600 uppercase sm:px-3 dark:border-zinc-800 dark:text-zinc-400">
|
||||
+{post.tags.length - 2}
|
||||
</span>
|
||||
)}
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
class="ml-1 h-4 w-4 stroke-current transition-transform duration-300"
|
||||
>
|
||||
<path
|
||||
d="M6.75 5.75 9.25 8l-2.5 2.25"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
))}
|
||||
@@ -224,30 +242,316 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
// Force the viewport to be exactly the width of the device
|
||||
const fixViewportWidth = () => {
|
||||
const viewport = document.querySelector('meta[name="viewport"]');
|
||||
if (!viewport) {
|
||||
const meta = document.createElement('meta');
|
||||
meta.name = 'viewport';
|
||||
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
|
||||
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||
} else {
|
||||
viewport.setAttribute(
|
||||
'content',
|
||||
'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
|
||||
);
|
||||
}
|
||||
|
||||
// Fix for horizontal overflow
|
||||
document.body.style.overflowX = 'hidden';
|
||||
document.documentElement.style.overflowX = 'hidden';
|
||||
document.documentElement.style.width = '100%';
|
||||
document.body.style.width = '100%';
|
||||
};
|
||||
|
||||
fixViewportWidth();
|
||||
|
||||
// Show button when scrolled down
|
||||
const backToTopButton = document.getElementById('back-to-top');
|
||||
if (backToTopButton) {
|
||||
const toggleBackToTopButton = () => {
|
||||
if (window.scrollY > 300) {
|
||||
backToTopButton.classList.remove('opacity-0', 'invisible');
|
||||
backToTopButton.classList.add('opacity-100', 'visible');
|
||||
} else {
|
||||
backToTopButton.classList.remove('opacity-100', 'visible');
|
||||
backToTopButton.classList.add('opacity-0', 'invisible');
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll to top when clicked
|
||||
backToTopButton.addEventListener('click', () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
|
||||
// Check scroll position
|
||||
window.addEventListener('scroll', toggleBackToTopButton);
|
||||
toggleBackToTopButton();
|
||||
}
|
||||
|
||||
// Add smooth reveal animations for content after loading
|
||||
const animateContent = () => {
|
||||
// Animate hero section
|
||||
const heroElements = document.querySelectorAll(
|
||||
'.hero-text ~ div, .hero-text h1, .hero-text span, .hero-text p'
|
||||
);
|
||||
heroElements.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
100 + index * 150
|
||||
);
|
||||
});
|
||||
|
||||
// Animate posts with staggered delay
|
||||
const articles = document.querySelectorAll('article.group');
|
||||
articles.forEach((article, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
article.classList.add('animate-reveal');
|
||||
},
|
||||
500 + index * 150
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
animateContent();
|
||||
|
||||
// Add smooth scrolling to year links
|
||||
document.querySelectorAll('a[href^="#year-"]').forEach((anchor) => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const targetId = this.getAttribute('href');
|
||||
const targetElement = document.querySelector(targetId);
|
||||
|
||||
if (targetElement) {
|
||||
window.scrollTo({
|
||||
top: targetElement.offsetTop - 100,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
|
||||
// Update URL hash without jumping
|
||||
history.pushState(null, null, targetId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Adjust tag items based on screen size with extreme precision
|
||||
const adjustTagItems = () => {
|
||||
const tagItems = document.querySelectorAll('.theme-ripple');
|
||||
const width =
|
||||
window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
const isVerySmall = width < 360;
|
||||
const isExtremelySmall = width < 280;
|
||||
const isMicroScreen = width < 240;
|
||||
|
||||
// Fix container width to match viewport exactly
|
||||
const container = document.querySelector('.tag-cloud');
|
||||
if (container) {
|
||||
container.style.maxWidth = '100vw';
|
||||
container.style.width = '100%';
|
||||
container.style.boxSizing = 'border-box';
|
||||
|
||||
// Remove any margins that might cause overflow
|
||||
container.style.marginLeft = '0';
|
||||
container.style.marginRight = '0';
|
||||
}
|
||||
|
||||
// Fix grid width
|
||||
const grid = document.querySelector('.grid');
|
||||
if (grid) {
|
||||
grid.style.width = '100%';
|
||||
grid.style.maxWidth = '100%';
|
||||
}
|
||||
|
||||
tagItems.forEach((item) => {
|
||||
// Set appropriate classes based on screen size
|
||||
if (isMicroScreen) {
|
||||
item.classList.add('micro-screen');
|
||||
item.classList.remove('extremely-small-screen', 'very-small-screen');
|
||||
} else if (isExtremelySmall) {
|
||||
item.classList.add('extremely-small-screen');
|
||||
item.classList.remove('very-small-screen', 'micro-screen');
|
||||
} else if (isVerySmall) {
|
||||
item.classList.add('very-small-screen');
|
||||
item.classList.remove('extremely-small-screen', 'micro-screen');
|
||||
} else {
|
||||
item.classList.remove('very-small-screen', 'extremely-small-screen', 'micro-screen');
|
||||
}
|
||||
|
||||
// Ensure text doesn't overflow on small screens
|
||||
const tagName = item.querySelector('h3');
|
||||
const tagCount = item.querySelector('p');
|
||||
|
||||
if (tagName) {
|
||||
// Set title for accessibility
|
||||
tagName.title = tagName.textContent.trim();
|
||||
|
||||
// Adjust text length based on screen size
|
||||
if (isMicroScreen && tagName.textContent.length > 6) {
|
||||
tagName.dataset.fullText = tagName.textContent;
|
||||
tagName.textContent = tagName.textContent.substring(0, 6) + '...';
|
||||
} else if (isExtremelySmall && tagName.textContent.length > 8) {
|
||||
tagName.dataset.fullText = tagName.textContent;
|
||||
tagName.textContent = tagName.textContent.substring(0, 8) + '...';
|
||||
} else if (isVerySmall && tagName.textContent.length > 12) {
|
||||
tagName.dataset.fullText = tagName.textContent;
|
||||
tagName.textContent = tagName.textContent.substring(0, 12) + '...';
|
||||
} else if (tagName.dataset.fullText) {
|
||||
tagName.textContent = tagName.dataset.fullText;
|
||||
delete tagName.dataset.fullText;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the tag hue for hover effects
|
||||
const hue = item.style.getPropertyValue('--tag-hue');
|
||||
item.style.setProperty('--hover-hue', hue);
|
||||
});
|
||||
};
|
||||
|
||||
adjustTagItems();
|
||||
|
||||
// Run on resize with optimized debounce
|
||||
let resizeTimer;
|
||||
const handleResize = () => {
|
||||
if (resizeTimer) {
|
||||
window.cancelAnimationFrame(resizeTimer);
|
||||
}
|
||||
|
||||
resizeTimer = window.requestAnimationFrame(() => {
|
||||
fixViewportWidth();
|
||||
adjustTagItems();
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('orientationchange', handleResize);
|
||||
|
||||
// Ensure layout is recalculated after page is fully loaded
|
||||
window.addEventListener('load', () => {
|
||||
fixViewportWidth();
|
||||
adjustTagItems();
|
||||
// Force recalculation after images and fonts are loaded
|
||||
setTimeout(() => {
|
||||
fixViewportWidth();
|
||||
adjustTagItems();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Add touch support for hover effects
|
||||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
if (isTouchDevice) {
|
||||
const articles = document.querySelectorAll('article');
|
||||
|
||||
articles.forEach((article) => {
|
||||
article.addEventListener('touchstart', () => {
|
||||
article.classList.add('is-touched');
|
||||
});
|
||||
|
||||
article.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
article.classList.remove('is-touched');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Add touch support for mobile devices
|
||||
const addTouchSupport = () => {
|
||||
const tagItems = document.querySelectorAll('.theme-ripple');
|
||||
|
||||
tagItems.forEach((item) => {
|
||||
item.addEventListener(
|
||||
'touchstart',
|
||||
() => {
|
||||
item.classList.add('touch-active');
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
item.addEventListener(
|
||||
'touchend',
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
item.classList.remove('touch-active');
|
||||
}, 150);
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
// Cancel active state if touch moves away
|
||||
item.addEventListener(
|
||||
'touchmove',
|
||||
(e) => {
|
||||
const touch = e.touches[0];
|
||||
const rect = item.getBoundingClientRect();
|
||||
|
||||
if (
|
||||
touch.clientX < rect.left ||
|
||||
touch.clientX > rect.right ||
|
||||
touch.clientY < rect.top ||
|
||||
touch.clientY > rect.bottom
|
||||
) {
|
||||
item.classList.remove('touch-active');
|
||||
}
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
addTouchSupport();
|
||||
|
||||
// Fix for iOS Safari and other mobile browsers
|
||||
if (/iPhone|iPad|iPod|Android/.test(navigator.userAgent)) {
|
||||
document.documentElement.style.setProperty(
|
||||
'--safe-area-inset-bottom',
|
||||
'env(safe-area-inset-bottom)'
|
||||
);
|
||||
|
||||
// Fix for mobile viewport height issues
|
||||
const setVh = () => {
|
||||
const vh = window.innerHeight * 0.01;
|
||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
};
|
||||
|
||||
setVh();
|
||||
window.addEventListener('resize', setVh);
|
||||
window.addEventListener('orientationchange', () => {
|
||||
// Wait for orientation change to complete
|
||||
setTimeout(() => {
|
||||
setVh();
|
||||
fixViewportWidth();
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Blob animation */
|
||||
.animate-blob {
|
||||
animation: blob-bounce 8s infinite ease;
|
||||
/* Content reveal animations */
|
||||
.hero-text h1,
|
||||
.hero-text span,
|
||||
.hero-text p,
|
||||
.hero-text ~ div,
|
||||
article.group {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition:
|
||||
opacity 0.8s ease,
|
||||
transform 0.8s ease;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
@keyframes blob-bounce {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
25% {
|
||||
transform: translate(5%, 5%) scale(1.05);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, 10%) scale(1);
|
||||
}
|
||||
75% {
|
||||
transform: translate(-5%, 5%) scale(0.95);
|
||||
}
|
||||
.animate-reveal {
|
||||
opacity: 1 !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
/* Search container hover effect */
|
||||
@@ -265,11 +569,196 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
}
|
||||
}
|
||||
|
||||
/* Shadow for dark mode */
|
||||
:global(.dark) .tag-cloud {
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05),
|
||||
0 2px 4px rgba(0, 0, 0, 0.1),
|
||||
0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Input focus animation */
|
||||
input:focus + div .search-pulse {
|
||||
animation: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
|
||||
}
|
||||
|
||||
/* Base styles */
|
||||
.tag-cloud {
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 0, 0, 0.03),
|
||||
0 2px 4px rgba(0, 0, 0, 0.03),
|
||||
0 4px 8px rgba(0, 0, 0, 0.05);
|
||||
transform-style: preserve-3d;
|
||||
perspective: 1000px;
|
||||
transition: all var(--theme-transition);
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
/* Fix for horizontal overflow */
|
||||
:global(html),
|
||||
:global(body) {
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:global(.max-w-6xl) {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Micro screens (below 240px) */
|
||||
@media (max-width: 239px) {
|
||||
.tag-cloud {
|
||||
padding: 0.5rem !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0.25rem !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.tag-cloud h2 {
|
||||
font-size: 0.875rem !important;
|
||||
margin-bottom: 0.375rem !important;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
|
||||
gap: 0.375rem !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.micro-screen .flex {
|
||||
padding: 0.25rem !important;
|
||||
}
|
||||
|
||||
.micro-screen h3 {
|
||||
font-size: 0.625rem !important;
|
||||
}
|
||||
|
||||
.micro-screen p {
|
||||
font-size: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra extra extra small screens (240px-279px) */
|
||||
@media (min-width: 240px) and (max-width: 279px) {
|
||||
.xxxs\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.xxxs\:px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.xxxs\:gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:text-\[9px\] {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra extra small screens (280px-359px) */
|
||||
@media (min-width: 280px) {
|
||||
.xxs\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.xxs\:px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.xxs\:py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.xxs\:w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.xxs\:h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.xxs\:text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.xxs\:gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.xxs\:text-\[9px\] {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra small screens (360px-639px) */
|
||||
@media (min-width: 360px) {
|
||||
.xs\:grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.xs\:px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.xs\:py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.xs\:w-7 {
|
||||
width: 1.75rem;
|
||||
}
|
||||
|
||||
.xs\:h-7 {
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
.xs\:text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.xs\:text-sm {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.xs\:gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.xs\:text-\[10px\] {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Hide scrollbar but keep functionality */
|
||||
.hide-scrollbar {
|
||||
-ms-overflow-style: none;
|
||||
@@ -295,6 +784,38 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Prevent layout shifts */
|
||||
.grow {
|
||||
grow: 1;
|
||||
}
|
||||
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Ensure container doesn't overflow */
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Ensure text doesn't overflow on small screens */
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Ensure proper word breaking for long tag names */
|
||||
.break-words {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.hyphens-auto {
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
/* Touch targets for mobile */
|
||||
@media (max-width: 640px) {
|
||||
a,
|
||||
@@ -304,73 +825,21 @@ const years = Object.keys(postsByYear).sort((a, b) => b - a);
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.touch-active {
|
||||
transform: scale(0.97) !important;
|
||||
opacity: 0.9;
|
||||
transition:
|
||||
transform 0.15s ease-in-out,
|
||||
opacity 0.15s ease-in-out !important;
|
||||
}
|
||||
|
||||
/* Fix for iOS Safari notch */
|
||||
@supports (padding: max(0px)) {
|
||||
.tag-cloud {
|
||||
padding-left: max(0.75rem, env(safe-area-inset-left));
|
||||
padding-right: max(0.75rem, env(safe-area-inset-right));
|
||||
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const backToTopButton = document.getElementById('back-to-top');
|
||||
|
||||
if (backToTopButton) {
|
||||
// Show button when scrolled down
|
||||
const toggleBackToTopButton = () => {
|
||||
if (window.scrollY > 300) {
|
||||
backToTopButton.classList.remove('opacity-0', 'invisible');
|
||||
backToTopButton.classList.add('opacity-100', 'visible');
|
||||
} else {
|
||||
backToTopButton.classList.remove('opacity-100', 'visible');
|
||||
backToTopButton.classList.add('opacity-0', 'invisible');
|
||||
}
|
||||
};
|
||||
|
||||
// Scroll to top when clicked
|
||||
backToTopButton.addEventListener('click', () => {
|
||||
window.scrollTo({
|
||||
top: 0,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
});
|
||||
|
||||
// Check scroll position
|
||||
window.addEventListener('scroll', toggleBackToTopButton);
|
||||
toggleBackToTopButton();
|
||||
}
|
||||
|
||||
// Add smooth scrolling to year links
|
||||
document.querySelectorAll('a[href^="#year-"]').forEach((anchor) => {
|
||||
anchor.addEventListener('click', function (e) {
|
||||
e.preventDefault();
|
||||
const targetId = this.getAttribute('href');
|
||||
const targetElement = document.querySelector(targetId);
|
||||
|
||||
if (targetElement) {
|
||||
window.scrollTo({
|
||||
top: targetElement.offsetTop - 100,
|
||||
behavior: 'smooth',
|
||||
});
|
||||
|
||||
// Update URL hash without jumping
|
||||
history.pushState(null, null, targetId);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Add touch support for hover effects
|
||||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
if (isTouchDevice) {
|
||||
const articles = document.querySelectorAll('article');
|
||||
|
||||
articles.forEach((article) => {
|
||||
article.addEventListener('touchstart', () => {
|
||||
article.classList.add('is-touched');
|
||||
});
|
||||
|
||||
article.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
article.classList.remove('is-touched');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import FormattedDate from '../components/FormattedDate.astro';
|
||||
import TagList from '../components/TagList.astro';
|
||||
|
||||
import directus from '../../lib/directus';
|
||||
import { readItems, readSingleton } from '@directus/sdk';
|
||||
@@ -22,22 +23,11 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
---
|
||||
|
||||
<Layout title=`Home | ${global.name}`>
|
||||
<!-- Hero Section with mobile responsiveness -->
|
||||
<section
|
||||
class="theme-transition-all px-4 py-10 sm:px-6 sm:py-16 md:py-20"
|
||||
transition:animate="slide"
|
||||
>
|
||||
<div class="relative mx-auto max-w-2xl">
|
||||
<!-- Adjusted blob positions and sizes for mobile appearance -->
|
||||
<div
|
||||
class="animate-blob theme-transition-bg absolute -top-10 -left-10 h-40 w-40 rounded-full bg-zinc-100 opacity-50 blur-3xl sm:-top-20 sm:-left-20 sm:h-64 sm:w-64 dark:bg-zinc-800/50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -right-10 -bottom-10 h-40 w-40 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:-right-20 sm:-bottom-20 sm:h-64 sm:w-64 dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative text-center sm:text-left">
|
||||
<h1
|
||||
class="theme-transition-color hero-text text-3xl font-bold tracking-tight text-zinc-900 sm:text-4xl md:text-5xl lg:text-6xl dark:text-zinc-100"
|
||||
@@ -63,7 +53,7 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
>
|
||||
<a
|
||||
href="/about"
|
||||
class="theme-transition-color group relative inline-flex min-h-[44px] items-center gap-2 text-sm font-medium text-zinc-900 transition-all duration-300 hover:text-zinc-700 dark:text-zinc-100 dark:hover:text-zinc-300"
|
||||
class="theme-transition-color group relative inline-flex min-h-[44px] items-center gap-2 text-sm font-medium text-zinc-900 transition-all duration-300 hover:text-zinc-700 dark:text-zinc-400 dark:hover:text-zinc-100"
|
||||
>
|
||||
<span>More about me</span>
|
||||
<svg
|
||||
@@ -79,9 +69,6 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"></path>
|
||||
</svg>
|
||||
<span
|
||||
class="theme-transition-bg absolute -bottom-1 left-0 h-0.5 w-0 bg-zinc-800 transition-all duration-300 group-hover:w-full dark:bg-zinc-200"
|
||||
></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@@ -103,7 +90,7 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
</h2>
|
||||
<a
|
||||
href="/blog"
|
||||
class="theme-transition-color group relative flex min-h-[44px] items-center justify-center self-center text-sm font-medium text-zinc-900 hover:text-zinc-700 sm:self-auto dark:text-zinc-100 dark:hover:text-zinc-300"
|
||||
class="theme-transition-color group relative flex min-h-[44px] items-center justify-center self-center text-sm font-medium text-zinc-900 hover:text-zinc-700 sm:self-auto dark:text-zinc-400 dark:hover:text-zinc-100"
|
||||
>
|
||||
<span class="flex items-center gap-1">
|
||||
View all posts
|
||||
@@ -121,9 +108,6 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<span
|
||||
class="theme-transition-bg absolute -bottom-1 left-0 h-0.5 w-0 bg-zinc-800 transition-all duration-300 group-hover:w-full dark:bg-zinc-200"
|
||||
></span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
@@ -132,14 +116,14 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
{
|
||||
recentPosts.map((post, index) => (
|
||||
<article class="hover-3d theme-transition-element group relative mx-auto flex w-full max-w-sm flex-col items-start sm:mx-0">
|
||||
<div class="theme-transition-bg absolute -inset-x-4 -inset-y-6 z-0 scale-95 bg-zinc-50 opacity-0 transition group-hover:scale-100 group-hover:opacity-100 sm:-inset-x-6 sm:rounded-2xl dark:bg-zinc-800/50" />
|
||||
<div class="theme-transition-all absolute -inset-x-4 -inset-y-6 z-0 border border-zinc-200 bg-white/50 transition-all duration-300 group-hover:bg-zinc-50 sm:-inset-x-6 sm:rounded-2xl dark:border-zinc-800 dark:bg-zinc-900/50 dark:group-hover:bg-zinc-800/70" />
|
||||
|
||||
{post.image && (
|
||||
<div class="relative z-10 mb-4 aspect-video w-full overflow-hidden rounded-lg">
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${post.image}`}
|
||||
alt={post.title}
|
||||
class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
class="h-full w-full object-cover"
|
||||
loading={index === 0 ? 'eager' : 'lazy'}
|
||||
width="400"
|
||||
height="225"
|
||||
@@ -147,12 +131,6 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="theme-transition-color relative z-10 flex w-full flex-wrap items-center justify-center gap-x-3 gap-y-2 text-xs text-zinc-500 sm:justify-start sm:gap-x-4 dark:text-zinc-400">
|
||||
<time datetime={post.published_date.toLocaleString()} class="font-medium">
|
||||
<FormattedDate date={post.published_date} />
|
||||
</time>
|
||||
</div>
|
||||
|
||||
<h3 class="theme-transition-color relative z-10 mt-3 w-full text-center text-lg font-semibold tracking-tight text-zinc-900 transition-colors group-hover:text-zinc-700 sm:text-left sm:text-xl dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
@@ -163,45 +141,29 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
</a>
|
||||
</h3>
|
||||
|
||||
<p class="theme-transition-color relative z-10 mt-2 line-clamp-3 w-full text-center text-sm text-zinc-600 sm:mt-3 sm:text-left dark:text-zinc-400">
|
||||
<p class="z-10 mb-2 line-clamp-2 text-center text-sm text-zinc-600 sm:mb-3 sm:line-clamp-3 sm:text-left sm:text-base dark:text-zinc-400">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div class="relative z-10 mt-3 flex w-full flex-wrap justify-center gap-2 sm:mt-4 sm:justify-start">
|
||||
{post.tags.slice(0, 3).map((tag) => (
|
||||
<a
|
||||
href={`/topics/${tag}`}
|
||||
class="theme-transition-all inline-flex min-h-[28px] items-center rounded-full bg-zinc-100 px-2 py-1 text-xs font-medium text-zinc-800 transition-colors hover:bg-zinc-200 sm:px-3 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700"
|
||||
>
|
||||
#{tag}
|
||||
</a>
|
||||
))}
|
||||
{post.tags.length > 3 && (
|
||||
<span class="theme-transition-all inline-flex min-h-[28px] items-center rounded-full bg-zinc-50 px-2 py-1 text-xs font-medium text-zinc-500 dark:bg-zinc-800/50 dark:text-zinc-400">
|
||||
+{post.tags.length - 3} more
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400">
|
||||
<FormattedDate date={post.published_date} />
|
||||
</div>
|
||||
|
||||
<TagList tags={post.tags} class="z-10" />
|
||||
|
||||
<a
|
||||
href={`/blog/${post.slug}`}
|
||||
class="theme-transition-color relative z-10 mx-auto mt-3 flex min-h-[44px] items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 sm:mx-0 sm:mt-4 dark:text-zinc-300 dark:group-hover:text-zinc-100"
|
||||
>
|
||||
<span class="relative inline-block overflow-hidden">
|
||||
<span class="block transition-transform duration-300 group-hover:-translate-y-full">
|
||||
Read article
|
||||
</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full whitespace-nowrap transition-transform duration-300 group-hover:translate-y-0">
|
||||
Explore now
|
||||
</span>
|
||||
<span class="relative z-10">Read article</span>
|
||||
<span class="absolute bottom-0 left-0 h-0.5 w-0 bg-zinc-800 transition-all duration-300 group-hover:w-full dark:bg-zinc-200" />
|
||||
</span>
|
||||
<svg
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
aria-hidden="true"
|
||||
class="ml-1 h-4 w-4 stroke-current transition-transform duration-300 group-hover:translate-x-1"
|
||||
class="ml-1 h-4 w-4 stroke-current transition-transform duration-300"
|
||||
>
|
||||
<path
|
||||
d="M6.75 5.75 9.25 8l-2.5 2.25"
|
||||
@@ -224,16 +186,16 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
<section class="theme-transition-all border-t border-zinc-100 px-4 py-10 sm:px-6 sm:py-12 md:py-16 dark:border-zinc-800">
|
||||
<div class="mx-auto max-w-3xl">
|
||||
<h2 class="theme-transition-color mb-6 text-center text-xl font-bold tracking-tight text-zinc-900 sm:mb-8 sm:text-left sm:text-2xl md:text-3xl dark:text-zinc-100">
|
||||
Explore Topics
|
||||
Popular Tags
|
||||
</h2>
|
||||
|
||||
<div class="mx-auto grid max-w-xs grid-cols-1 gap-3 sm:max-w-none sm:grid-cols-2 sm:gap-4 md:grid-cols-3">
|
||||
<div class="hover-3d mx-auto grid max-w-xs grid-cols-1 gap-3 sm:max-w-none sm:grid-cols-2 sm:gap-4 md:grid-cols-3">
|
||||
{allTags.map((tag) => {
|
||||
const tagCount = posts.filter((post) => post.tags && post.tags.includes(tag)).length;
|
||||
return (
|
||||
<a
|
||||
href={`/topics/${tag}`}
|
||||
class="theme-transition-all group flex min-h-[80px] flex-col rounded-xl border border-zinc-200 p-3 transition-all duration-300 hover:bg-zinc-50 sm:min-h-[90px] sm:p-4 md:p-6 dark:border-zinc-800 dark:hover:bg-zinc-800/70"
|
||||
href={`/tags/${tag}`}
|
||||
class="theme-transition-all flex min-h-[80px] flex-col rounded-xl border border-zinc-300 bg-white/50 p-3 transition-all duration-300 hover:bg-zinc-50 sm:min-h-[90px] sm:p-4 md:p-6 dark:border-zinc-800 dark:bg-zinc-900/50 dark:hover:bg-zinc-800/70"
|
||||
>
|
||||
<div class="mb-2 flex items-start justify-between">
|
||||
<span class="theme-transition-color mr-2 text-sm font-medium text-zinc-900 dark:text-zinc-100">
|
||||
@@ -250,29 +212,6 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div class="mt-6 text-center sm:mt-8">
|
||||
<a
|
||||
href="/tags"
|
||||
class="theme-transition-color inline-flex min-h-[44px] items-center text-sm font-medium text-zinc-900 hover:text-zinc-700 dark:text-zinc-100 dark:hover:text-zinc-300"
|
||||
>
|
||||
<span>View all topics</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="ml-1 h-4 w-4 transition-transform duration-300 group-hover:translate-x-1"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
@@ -447,37 +386,7 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
});
|
||||
};
|
||||
|
||||
// Run animations after the loading screen is hidden
|
||||
const loadingScreen = document.getElementById('loading-screen');
|
||||
if (loadingScreen) {
|
||||
// Check if loading screen is already hidden (page refresh)
|
||||
if (loadingScreen.style.display === 'none') {
|
||||
animateContent();
|
||||
} else {
|
||||
// Wait for loading screen to hide
|
||||
const observer = new MutationObserver((mutations) => {
|
||||
mutations.forEach((mutation) => {
|
||||
if (
|
||||
mutation.target === loadingScreen &&
|
||||
mutation.type === 'attributes' &&
|
||||
mutation.attributeName === 'style' &&
|
||||
loadingScreen.style.display === 'none'
|
||||
) {
|
||||
animateContent();
|
||||
observer.disconnect();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
observer.observe(loadingScreen, { attributes: true });
|
||||
|
||||
// Fallback
|
||||
setTimeout(animateContent, 3500);
|
||||
}
|
||||
} else {
|
||||
// If loading screen doesn't exist for some reason
|
||||
animateContent();
|
||||
}
|
||||
animateContent();
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -505,13 +414,6 @@ const allTags = [...new Set(posts.flatMap((post) => post.tags || []))].slice(0,
|
||||
transition: color var(--theme-transition-duration) var(--theme-transition-timing);
|
||||
}
|
||||
|
||||
/* Ensure transitions apply to all theme-related properties */
|
||||
:global(*) {
|
||||
transition-property: background-color, border-color, color, fill, stroke, opacity;
|
||||
transition-duration: var(--theme-transition-duration);
|
||||
transition-timing-function: var(--theme-transition-timing);
|
||||
}
|
||||
|
||||
/* Remove the forced transition disabling which causes flickering */
|
||||
:global(.theme-switching),
|
||||
:global(.theme-switching *) {
|
||||
|
@@ -48,18 +48,9 @@ const relatedTags = [
|
||||
<BaseLayout title={`Posts tagged with "${tag}"`}>
|
||||
<div class="mx-auto max-w-5xl px-4 py-10 sm:py-16">
|
||||
<div class="relative mb-10 sm:mb-16">
|
||||
<div
|
||||
class="animate-blob absolute -top-20 -left-20 h-48 w-48 rounded-full bg-zinc-100 opacity-30 blur-3xl sm:h-64 sm:w-64 dark:bg-zinc-900/30"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 absolute -right-10 -bottom-10 h-36 w-36 rounded-full bg-zinc-200 opacity-20 blur-2xl sm:h-48 sm:w-48 dark:bg-zinc-900/20"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative text-center sm:text-left">
|
||||
<a
|
||||
href="/tags"
|
||||
href="/blog#topics"
|
||||
class="group mb-4 inline-flex items-center gap-2 text-sm font-medium text-zinc-600 transition-colors hover:text-zinc-900 dark:text-zinc-400 dark:hover:text-zinc-100"
|
||||
>
|
||||
<svg
|
||||
@@ -73,9 +64,11 @@ const relatedTags = [
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"></path>
|
||||
d="M10.5 19.5L3 12m0 0l7.5-7.5M3 12h18"
|
||||
>
|
||||
</path>
|
||||
</svg>
|
||||
<span>Back to all topics</span>
|
||||
<span>Back to blog</span>
|
||||
<span
|
||||
class="block h-0.5 max-w-0 bg-zinc-300 transition-all duration-300 group-hover:max-w-full dark:bg-zinc-700"
|
||||
></span>
|
||||
@@ -99,8 +92,9 @@ const relatedTags = [
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z"
|
||||
></path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z"></path>
|
||||
>
|
||||
</path>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z"> </path>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
@@ -112,7 +106,7 @@ const relatedTags = [
|
||||
<span class="absolute -bottom-1 left-0 h-1 w-full bg-zinc-200 dark:bg-zinc-700"
|
||||
></span>
|
||||
<span
|
||||
class="animate-expand absolute -bottom-1 left-0 h-1 w-1/2 bg-zinc-900 opacity-70 dark:bg-zinc-100"
|
||||
class="animate-expand absolute -bottom-1 left-0 h-1 w-full bg-zinc-900 opacity-70 dark:bg-zinc-100"
|
||||
></span>
|
||||
</span>
|
||||
</h1>
|
||||
@@ -133,14 +127,14 @@ const relatedTags = [
|
||||
<!-- Related tags section -->
|
||||
{
|
||||
relatedTags.length > 0 && (
|
||||
<div class="hide-scrollbar mb-8 overflow-x-auto pb-4 sm:mb-12">
|
||||
<div class="hero-text hide-scrollbar mb-8 overflow-x-auto pb-4 sm:mb-12">
|
||||
<h2 class="mb-3 text-center text-lg font-medium text-zinc-900 sm:text-left dark:text-zinc-100">
|
||||
Related topics
|
||||
</h2>
|
||||
<div class="flex flex-nowrap justify-center gap-2 sm:justify-start">
|
||||
{relatedTags.map((relatedTag) => (
|
||||
<a
|
||||
href={`/topics/${relatedTag}`}
|
||||
href={`/tags/${relatedTag}`}
|
||||
class="inline-flex shrink-0 items-center rounded-full bg-zinc-100 px-3 py-1.5 text-sm font-medium text-zinc-900 transition-colors hover:bg-zinc-200 dark:bg-zinc-800 dark:text-zinc-100 dark:hover:bg-zinc-700"
|
||||
>
|
||||
#{relatedTag}
|
||||
@@ -153,55 +147,31 @@ const relatedTags = [
|
||||
|
||||
<!-- Posts list -->
|
||||
<div class="relative">
|
||||
<div class="bg-grid-pattern pointer-events-none absolute inset-0 opacity-5 dark:opacity-10">
|
||||
<div
|
||||
class="hero-text bg-grid-pattern pointer-events-none absolute inset-0 opacity-5 dark:opacity-10"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="relative space-y-6 sm:space-y-8">
|
||||
{
|
||||
sortedPosts.map((post) => (
|
||||
<article class="hover-card group relative mx-auto flex max-w-2xl flex-col rounded-2xl border border-zinc-200 p-5 transition-all duration-300 hover:bg-zinc-50/80 hover:shadow-md sm:mx-0 sm:p-8 dark:border-zinc-800 dark:hover:bg-zinc-900/50">
|
||||
<div class="absolute inset-0 rounded-2xl bg-gradient-to-br from-zinc-50/0 to-zinc-100/0 opacity-0 transition-opacity duration-500 group-hover:opacity-100 dark:from-zinc-900/0 dark:to-zinc-800/0" />
|
||||
<article class="hover-3d theme-transition-element group relative mx-auto flex max-w-2xl flex-col p-5 sm:mx-0 sm:p-8">
|
||||
<div class="absolute inset-0 rounded-2xl border border-zinc-200 bg-white/50 transition-all duration-300 group-hover:bg-zinc-50 hover:bg-zinc-50/80 hover:shadow-md dark:border-zinc-800 dark:bg-zinc-900/50 dark:group-hover:bg-zinc-800/70 dark:hover:bg-zinc-900/50" />
|
||||
|
||||
<div class="flex flex-col gap-5 sm:flex-row sm:gap-6">
|
||||
{post.image && (
|
||||
<div class="mx-auto h-40 w-full shrink-0 overflow-hidden rounded-xl shadow-xs transition-all duration-300 group-hover:shadow-md sm:mx-0 sm:w-56">
|
||||
<div class="z-10 mx-auto h-40 w-full shrink-0 overflow-hidden rounded-xl sm:mx-0 sm:w-56">
|
||||
<img
|
||||
src={`${process.env.DIRECTUS_URL ?? 'https://directus.alexlebens.dev'}/assets/${post.image}?width=500`}
|
||||
alt={post.image_alt}
|
||||
class="h-full w-full object-cover transition-transform duration-500 group-hover:scale-105"
|
||||
class="h-full w-full object-cover"
|
||||
loading="lazy"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div class="flex-1">
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400">
|
||||
{post.published_date && (
|
||||
<time
|
||||
datetime={post.published_date.toLocaleString()}
|
||||
class="flex items-center gap-1.5"
|
||||
>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="h-3.5 w-3.5 sm:h-4 sm:w-4"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M6.75 3v2.25M17.25 3v2.25M3 18.75V7.5a2.25 2.25 0 012.25-2.25h13.5A2.25 2.25 0 0121 7.5v11.25m-18 0
|
||||
A2.25 2.25 0 005.25 21h13.5A2.25 2.25 0 0021 18.75m-18 0v-7.5A2.25 2.25 0 015.25 9h13.5A2.25 2.25 0 0121 11.25v7.5"
|
||||
/>
|
||||
</svg>
|
||||
<FormattedDate date={post.published_date} />
|
||||
</time>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<h2 class="mb-2 text-center text-xl font-semibold text-zinc-900 transition-colors group-hover:text-zinc-700 sm:mb-3 sm:text-left sm:text-2xl dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
<div class="z-10 flex-1">
|
||||
<h2 class="mb-2 text-center text-xl font-semibold text-zinc-900 sm:mb-3 sm:text-left sm:text-2xl dark:text-zinc-100">
|
||||
<a href={`/blog/${post.slug}/`} class="before:absolute before:inset-0">
|
||||
{post.title}
|
||||
</a>
|
||||
@@ -210,15 +180,19 @@ const relatedTags = [
|
||||
<p class="mb-4 line-clamp-2 text-center text-sm text-zinc-600 sm:line-clamp-3 sm:text-left sm:text-base dark:text-zinc-400">
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
<div class="mb-2 flex flex-wrap items-center justify-center gap-3 text-xs text-zinc-500 sm:mb-3 sm:justify-start sm:gap-4 sm:text-sm dark:text-zinc-400">
|
||||
<FormattedDate date={post.published_date} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="mt-4 flex flex-wrap items-end justify-center border-t border-zinc-100 pt-4 sm:justify-between dark:border-zinc-800">
|
||||
<div class="z-10 mt-4 flex flex-wrap items-end justify-center border-t border-zinc-100 pt-4 sm:justify-between dark:border-zinc-800">
|
||||
{post.tags && post.tags.length > 0 && (
|
||||
<div class="mb-3 flex flex-wrap justify-center gap-2 sm:mb-0 sm:justify-start">
|
||||
{post.tags.slice(0, 3).map((postTag) => (
|
||||
<a
|
||||
href={`/topics/${postTag}`}
|
||||
href={`/blog/${postTag}`}
|
||||
class={`inline-flex items-center rounded-full px-2.5 py-0.5 text-xs font-medium transition-colors ${
|
||||
postTag === tag
|
||||
? 'bg-zinc-900/10 text-zinc-900 dark:bg-zinc-100/20 dark:text-zinc-100'
|
||||
@@ -238,31 +212,24 @@ const relatedTags = [
|
||||
|
||||
<div class="mx-auto sm:mr-0 sm:ml-auto">
|
||||
<a
|
||||
href={`/blog/${post.slug}/`}
|
||||
class="inline-flex items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 dark:text-zinc-300 dark:group-hover:text-zinc-100"
|
||||
aria-hidden="true"
|
||||
tabindex="-1"
|
||||
href={`/blog/${post.slug}`}
|
||||
class="theme-transition-color relative z-10 mx-auto mt-3 flex min-h-[44px] items-center text-sm font-medium text-zinc-700 transition-colors group-hover:text-zinc-900 sm:mx-0 sm:mt-4 dark:text-zinc-300 dark:group-hover:text-zinc-100"
|
||||
>
|
||||
<span class="relative inline-block overflow-hidden">
|
||||
<span class="block transition-transform duration-300 group-hover:-translate-y-full">
|
||||
Read article
|
||||
</span>
|
||||
<span class="absolute top-0 left-0 translate-y-full whitespace-nowrap transition-transform duration-300 group-hover:translate-y-0">
|
||||
Explore now
|
||||
</span>
|
||||
<span class="relative z-10">Read article</span>
|
||||
<span class="absolute bottom-0 left-0 h-0.5 w-0 bg-zinc-800 transition-all duration-300 group-hover:w-full dark:bg-zinc-200" />
|
||||
</span>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 16 16"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="ml-1 h-4 w-4 transition-transform duration-300 group-hover:translate-x-1"
|
||||
aria-hidden="true"
|
||||
class="ml-1 h-4 w-4 stroke-current transition-transform duration-300"
|
||||
>
|
||||
<path
|
||||
d="M6.75 5.75 9.25 8l-2.5 2.25"
|
||||
stroke-width="1.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M13.5 4.5L21 12m0 0l-7.5 7.5M21 12H3"
|
||||
/>
|
||||
</svg>
|
||||
</a>
|
||||
@@ -324,6 +291,61 @@ const relatedTags = [
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
// Add smooth reveal animations for content after loading
|
||||
const animateContent = () => {
|
||||
// Animate hero section
|
||||
const heroElements = document.querySelectorAll(
|
||||
'.hero-text ~ div, .hero-text h1, .hero-text span, .hero-text p'
|
||||
);
|
||||
heroElements.forEach((el, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
el.classList.add('animate-reveal');
|
||||
},
|
||||
100 + index * 150
|
||||
);
|
||||
});
|
||||
|
||||
// Animate posts with staggered delay
|
||||
const articles = document.querySelectorAll('article.group');
|
||||
articles.forEach((article, index) => {
|
||||
setTimeout(
|
||||
() => {
|
||||
article.classList.add('animate-reveal');
|
||||
},
|
||||
500 + index * 150
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
animateContent();
|
||||
|
||||
// Add hover effect for cards on touch devices
|
||||
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
||||
|
||||
if (isTouchDevice) {
|
||||
const cards = document.querySelectorAll('.hover-3d');
|
||||
|
||||
cards.forEach((card) => {
|
||||
card.addEventListener('touchstart', () => {
|
||||
card.classList.add('is-touched');
|
||||
});
|
||||
|
||||
card.addEventListener('touchend', () => {
|
||||
setTimeout(() => {
|
||||
card.classList.remove('is-touched');
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
|
||||
// Disable hover animations on touch devices
|
||||
document.documentElement.classList.add('touch-device');
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Grid pattern background */
|
||||
.bg-grid-pattern {
|
||||
@@ -351,7 +373,7 @@ const relatedTags = [
|
||||
width: 0;
|
||||
}
|
||||
to {
|
||||
width: 50%;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -359,43 +381,22 @@ const relatedTags = [
|
||||
animation: expand 1s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Blob animation */
|
||||
.animate-blob {
|
||||
animation: blob 7s infinite;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
@keyframes blob {
|
||||
0% {
|
||||
transform: translate(0px, 0px) scale(1);
|
||||
}
|
||||
33% {
|
||||
transform: translate(20px, -20px) scale(1.1);
|
||||
}
|
||||
66% {
|
||||
transform: translate(-20px, 20px) scale(0.9);
|
||||
}
|
||||
100% {
|
||||
transform: translate(0px, 0px) scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
/* Hover card effect */
|
||||
.hover-card {
|
||||
transform: translateY(0);
|
||||
/* Content reveal animations */
|
||||
.hero-text h1,
|
||||
.hero-text span,
|
||||
.hero-text p,
|
||||
.hero-text ~ div,
|
||||
article.group {
|
||||
opacity: 0;
|
||||
transform: translateY(20px);
|
||||
transition:
|
||||
transform 0.3s ease,
|
||||
box-shadow 0.3s ease,
|
||||
background-color 0.3s ease;
|
||||
opacity 0.8s ease,
|
||||
transform 0.8s ease;
|
||||
}
|
||||
|
||||
@media (hover: hover) {
|
||||
.hover-card:hover {
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.animate-reveal {
|
||||
opacity: 1 !important;
|
||||
transform: translateY(0) !important;
|
||||
}
|
||||
|
||||
/* Line clamp for descriptions */
|
@@ -1,629 +0,0 @@
|
||||
---
|
||||
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||
|
||||
import directus from '../../../lib/directus';
|
||||
import { readItems } from '@directus/sdk';
|
||||
|
||||
const posts = await directus.request(
|
||||
readItems('posts', {
|
||||
fields: ['*'],
|
||||
sort: ['-published_date'],
|
||||
})
|
||||
);
|
||||
|
||||
const tags = [...new Set(posts.flatMap((post) => post.tags || []))].sort();
|
||||
|
||||
// Count posts for each tag and create tag objects with additional data
|
||||
const tagObjects = tags.map((tag) => {
|
||||
const count = posts.filter((post) => post.tags?.includes(tag)).length;
|
||||
// Generate a consistent but random-looking hue for each tag
|
||||
const hue = Math.abs(tag.split('').reduce((acc, char) => acc + char.charCodeAt(0), 0) % 360);
|
||||
return {
|
||||
name: tag,
|
||||
count,
|
||||
size: Math.max(1, Math.min(3, Math.floor(count / 2) + 1)), // Size 1-3 based on count
|
||||
hue,
|
||||
};
|
||||
});
|
||||
|
||||
const sortedTags = [...tagObjects].sort((a, b) => b.count - a.count);
|
||||
---
|
||||
|
||||
<BaseLayout title="Explore Tags">
|
||||
<div
|
||||
class="theme-transition-all mx-auto w-full px-3 py-6 sm:px-6 sm:py-12 md:py-16"
|
||||
transition:animate="slide"
|
||||
>
|
||||
<div class="theme-transition-element relative mb-8 text-center sm:mb-12 md:mb-16">
|
||||
<div
|
||||
class="animate-blob theme-transition-bg absolute -top-16 -left-16 h-36 w-36 rounded-full bg-zinc-100 opacity-50 blur-3xl sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/50"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-2000 theme-transition-bg absolute -right-16 -bottom-16 h-36 w-36 rounded-full bg-zinc-200 opacity-30 blur-3xl sm:h-48 sm:w-48 md:h-72 md:w-72 dark:bg-zinc-800/30"
|
||||
>
|
||||
</div>
|
||||
<div
|
||||
class="animate-blob animation-delay-4000 theme-transition-bg absolute top-8 right-8 h-24 w-24 rounded-full bg-zinc-100/30 opacity-40 blur-2xl sm:h-32 sm:w-32 md:h-40 md:w-40 dark:bg-zinc-700/20"
|
||||
>
|
||||
</div>
|
||||
|
||||
<h1
|
||||
class="theme-transition-color relative mb-3 text-3xl font-bold tracking-tight text-zinc-900 sm:mb-4 sm:text-4xl md:mb-6 md:text-5xl lg:text-6xl dark:text-zinc-100"
|
||||
>
|
||||
<span class="relative inline-block">
|
||||
<span class="relative inline-block">
|
||||
<span
|
||||
class="theme-transition-bg absolute -inset-1 rounded-lg bg-gradient-to-r from-zinc-200/50 to-zinc-300/50 blur-xs dark:from-zinc-800/50 dark:to-zinc-700/50"
|
||||
></span>
|
||||
<span class="relative">Explore</span>
|
||||
</span>
|
||||
{' '}
|
||||
<span class="relative inline-block">
|
||||
Topics
|
||||
<span
|
||||
class="animate-underline theme-transition-bg absolute -bottom-1 left-0 h-0.5 w-full origin-left transform bg-gradient-to-r from-zinc-400 to-zinc-600 sm:-bottom-2 sm:h-1 dark:from-zinc-600 dark:to-zinc-400"
|
||||
></span>
|
||||
</span>
|
||||
</span>
|
||||
</h1>
|
||||
<p
|
||||
class="theme-transition-color relative mx-auto max-w-2xl text-sm text-zinc-600 sm:text-base md:text-lg lg:text-xl dark:text-zinc-400"
|
||||
>
|
||||
Discover content organized by your interests
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{
|
||||
tags.length === 0 ? (
|
||||
<div class="theme-transition-element py-8 text-center sm:py-12 md:py-16">
|
||||
<div class="theme-transition-bg mb-3 inline-flex h-16 w-16 items-center justify-center rounded-full bg-zinc-100 shadow-inner sm:mb-4 sm:h-20 sm:w-20 md:mb-6 md:h-24 md:w-24 dark:bg-zinc-800">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
stroke-width="1.5"
|
||||
stroke="currentColor"
|
||||
class="theme-transition-color h-8 w-8 text-zinc-500 sm:h-10 sm:w-10 md:h-12 md:w-12 dark:text-zinc-400"
|
||||
>
|
||||
<path
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
d="M9.568 3H5.25A2.25 2.25 0 003 5.25v4.318c0 .597.237 1.17.659 1.591l9.581 9.581c.699.699 1.78.872 2.607.33a18.095 18.095 0 005.223-5.223c.542-.827.369-1.908-.33-2.607L11.16 3.66A2.25 2.25 0 009.568 3z"
|
||||
/>
|
||||
<path stroke-linecap="round" stroke-linejoin="round" d="M6 6h.008v.008H6V6z" />
|
||||
</svg>
|
||||
</div>
|
||||
<p class="theme-transition-color text-lg font-medium text-zinc-800 sm:text-xl md:text-2xl dark:text-zinc-200">
|
||||
No tags found yet.
|
||||
</p>
|
||||
<p class="theme-transition-color mt-2 text-xs text-zinc-500 sm:text-sm md:text-base dark:text-zinc-500">
|
||||
Check back later for categorized content.
|
||||
</p>
|
||||
</div>
|
||||
) : (
|
||||
<div class="flex w-full justify-center">
|
||||
<div class="tag-cloud hover-3d glass theme-transition-all relative w-full rounded-lg border border-zinc-100 bg-white/50 p-3 backdrop-blur-xs sm:rounded-xl sm:p-4 md:rounded-2xl md:p-6 lg:rounded-3xl lg:p-8 dark:border-zinc-800 dark:bg-zinc-900/50">
|
||||
<div class="bg-grid-pattern theme-transition-bg absolute inset-0 opacity-5 dark:opacity-10" />
|
||||
<div class="theme-transition-bg absolute -top-8 -right-8 h-20 w-20 rounded-full bg-gradient-to-br from-zinc-200/30 to-zinc-300/20 blur-xl sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40 dark:from-zinc-700/20 dark:to-zinc-800/10" />
|
||||
<div class="theme-transition-bg absolute -bottom-8 -left-8 h-20 w-20 rounded-full bg-gradient-to-tl from-zinc-200/30 to-zinc-300/20 blur-xl sm:h-24 sm:w-24 md:h-32 md:w-32 lg:h-40 lg:w-40 dark:from-zinc-700/20 dark:to-zinc-800/10" />
|
||||
|
||||
<h2 class="theme-transition-color mb-3 text-center text-lg font-bold text-zinc-900 sm:mb-4 sm:text-xl md:mb-6 md:text-2xl lg:mb-8 lg:text-3xl dark:text-zinc-100">
|
||||
Popular Topics
|
||||
</h2>
|
||||
|
||||
<div class="xxxs:grid-cols-2 xxs:grid-cols-2 xs:grid-cols-3 xxxs:gap-2 xxs:gap-2 xs:gap-2 grid w-full grid-cols-2 gap-1.5 sm:grid-cols-3 sm:gap-3 md:grid-cols-4 md:gap-4 lg:grid-cols-5">
|
||||
{sortedTags.map((tag) => (
|
||||
<a
|
||||
href={`/topics/${tag.name}`}
|
||||
class="theme-transition-element theme-ripple group relative min-w-0 grow overflow-hidden rounded-md border border-zinc-200 transition-all duration-300 hover:scale-[1.03] hover:border-zinc-300 hover:shadow-md active:scale-95 sm:rounded-lg sm:hover:shadow-lg md:rounded-xl dark:border-zinc-800 dark:hover:border-zinc-700"
|
||||
style={`--tag-hue: ${tag.hue};`}
|
||||
>
|
||||
<div class="theme-transition-bg absolute inset-0 bg-gradient-to-br from-zinc-50/90 to-zinc-100/90 opacity-100 transition-opacity group-hover:opacity-95 dark:from-zinc-800/90 dark:to-zinc-900/90" />
|
||||
|
||||
<div class="xxxs:px-2 xxs:px-2 xs:px-2 xxxs:py-2 xxs:py-2 xs:py-2 xxs:gap-2 relative flex w-full items-center gap-1.5 px-1.5 py-1.5 sm:px-3 sm:py-3 md:px-4 md:py-4">
|
||||
<div class="xxxs:w-6 xxxs:h-6 xxs:w-6 xxs:h-6 xs:w-7 xs:h-7 group-hover:bg-accent/20 dark:group-hover:bg-accent/20 group-hover:text-accent-dark dark:group-hover:text-accent-light theme-transition-all flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-zinc-100 text-zinc-700 shadow-xs transition-all duration-300 sm:h-8 sm:w-8 md:h-10 md:w-10 dark:bg-zinc-800 dark:text-zinc-300">
|
||||
<span class="xxxs:text-xs xxs:text-xs xs:text-sm text-xs font-semibold sm:text-base md:text-lg">
|
||||
#
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="min-w-0 flex-1 overflow-hidden">
|
||||
<h3 class="xxxs:text-xs xxs:text-xs xs:text-xs theme-transition-color truncate text-[10px] font-bold break-words hyphens-auto text-zinc-900 transition-colors group-hover:text-zinc-700 sm:text-sm md:text-base dark:text-zinc-100 dark:group-hover:text-zinc-300">
|
||||
{tag.name}
|
||||
</h3>
|
||||
<p class="xxxs:text-[9px] xxs:text-[9px] xs:text-[10px] theme-transition-color truncate text-[8px] text-zinc-500 sm:text-xs md:text-xs dark:text-zinc-400">
|
||||
{tag.count} article{tag.count !== 1 ? 's' : ''}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</BaseLayout>
|
||||
|
||||
<script>
|
||||
document.addEventListener('astro:page-load', () => {
|
||||
const fixViewportWidth = () => {
|
||||
// Force the viewport to be exactly the width of the device
|
||||
const viewport = document.querySelector('meta[name="viewport"]');
|
||||
if (!viewport) {
|
||||
const meta = document.createElement('meta');
|
||||
meta.name = 'viewport';
|
||||
meta.content = 'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no';
|
||||
document.getElementsByTagName('head')[0].appendChild(meta);
|
||||
} else {
|
||||
viewport.setAttribute(
|
||||
'content',
|
||||
'width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no'
|
||||
);
|
||||
}
|
||||
|
||||
// Fix for horizontal overflow
|
||||
document.body.style.overflowX = 'hidden';
|
||||
document.documentElement.style.overflowX = 'hidden';
|
||||
document.documentElement.style.width = '100%';
|
||||
document.body.style.width = '100%';
|
||||
};
|
||||
|
||||
fixViewportWidth();
|
||||
|
||||
// Adjust tag items based on screen size with extreme precision
|
||||
const adjustTagItems = () => {
|
||||
const tagItems = document.querySelectorAll('.theme-ripple');
|
||||
const width =
|
||||
window.innerWidth || document.documentElement.clientWidth || document.body.clientWidth;
|
||||
const isVerySmall = width < 360;
|
||||
const isExtremelySmall = width < 280;
|
||||
const isMicroScreen = width < 240;
|
||||
|
||||
// Fix container width to match viewport exactly
|
||||
const container = document.querySelector('.tag-cloud');
|
||||
if (container) {
|
||||
container.style.maxWidth = '100vw';
|
||||
container.style.width = '100%';
|
||||
container.style.boxSizing = 'border-box';
|
||||
|
||||
// Remove any margins that might cause overflow
|
||||
container.style.marginLeft = '0';
|
||||
container.style.marginRight = '0';
|
||||
}
|
||||
|
||||
// Fix grid width
|
||||
const grid = document.querySelector('.grid');
|
||||
if (grid) {
|
||||
grid.style.width = '100%';
|
||||
grid.style.maxWidth = '100%';
|
||||
}
|
||||
|
||||
tagItems.forEach((item) => {
|
||||
// Set appropriate classes based on screen size
|
||||
if (isMicroScreen) {
|
||||
item.classList.add('micro-screen');
|
||||
item.classList.remove('extremely-small-screen', 'very-small-screen');
|
||||
} else if (isExtremelySmall) {
|
||||
item.classList.add('extremely-small-screen');
|
||||
item.classList.remove('very-small-screen', 'micro-screen');
|
||||
} else if (isVerySmall) {
|
||||
item.classList.add('very-small-screen');
|
||||
item.classList.remove('extremely-small-screen', 'micro-screen');
|
||||
} else {
|
||||
item.classList.remove('very-small-screen', 'extremely-small-screen', 'micro-screen');
|
||||
}
|
||||
|
||||
// Ensure text doesn't overflow on small screens
|
||||
const tagName = item.querySelector('h3');
|
||||
const tagCount = item.querySelector('p');
|
||||
|
||||
if (tagName) {
|
||||
// Set title for accessibility
|
||||
tagName.title = tagName.textContent.trim();
|
||||
|
||||
// Adjust text length based on screen size
|
||||
if (isMicroScreen && tagName.textContent.length > 6) {
|
||||
tagName.dataset.fullText = tagName.textContent;
|
||||
tagName.textContent = tagName.textContent.substring(0, 6) + '...';
|
||||
} else if (isExtremelySmall && tagName.textContent.length > 8) {
|
||||
tagName.dataset.fullText = tagName.textContent;
|
||||
tagName.textContent = tagName.textContent.substring(0, 8) + '...';
|
||||
} else if (isVerySmall && tagName.textContent.length > 12) {
|
||||
tagName.dataset.fullText = tagName.textContent;
|
||||
tagName.textContent = tagName.textContent.substring(0, 12) + '...';
|
||||
} else if (tagName.dataset.fullText) {
|
||||
tagName.textContent = tagName.dataset.fullText;
|
||||
delete tagName.dataset.fullText;
|
||||
}
|
||||
}
|
||||
|
||||
// Set the tag hue for hover effects
|
||||
const hue = item.style.getPropertyValue('--tag-hue');
|
||||
item.style.setProperty('--hover-hue', hue);
|
||||
});
|
||||
};
|
||||
|
||||
// Run on load
|
||||
adjustTagItems();
|
||||
|
||||
// Run on resize with optimized debounce
|
||||
let resizeTimer;
|
||||
const handleResize = () => {
|
||||
if (resizeTimer) {
|
||||
window.cancelAnimationFrame(resizeTimer);
|
||||
}
|
||||
|
||||
resizeTimer = window.requestAnimationFrame(() => {
|
||||
fixViewportWidth();
|
||||
adjustTagItems();
|
||||
});
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
window.addEventListener('orientationchange', handleResize);
|
||||
|
||||
// Ensure layout is recalculated after page is fully loaded
|
||||
window.addEventListener('load', () => {
|
||||
fixViewportWidth();
|
||||
adjustTagItems();
|
||||
// Force recalculation after images and fonts are loaded
|
||||
setTimeout(() => {
|
||||
fixViewportWidth();
|
||||
adjustTagItems();
|
||||
}, 500);
|
||||
});
|
||||
|
||||
// Fix for iOS Safari and other mobile browsers
|
||||
if (/iPhone|iPad|iPod|Android/.test(navigator.userAgent)) {
|
||||
document.documentElement.style.setProperty(
|
||||
'--safe-area-inset-bottom',
|
||||
'env(safe-area-inset-bottom)'
|
||||
);
|
||||
|
||||
// Fix for mobile viewport height issues
|
||||
const setVh = () => {
|
||||
const vh = window.innerHeight * 0.01;
|
||||
document.documentElement.style.setProperty('--vh', `${vh}px`);
|
||||
};
|
||||
|
||||
setVh();
|
||||
window.addEventListener('resize', setVh);
|
||||
window.addEventListener('orientationchange', () => {
|
||||
// Wait for orientation change to complete
|
||||
setTimeout(() => {
|
||||
setVh();
|
||||
fixViewportWidth();
|
||||
}, 100);
|
||||
});
|
||||
}
|
||||
|
||||
// Add touch support for mobile devices
|
||||
const addTouchSupport = () => {
|
||||
const tagItems = document.querySelectorAll('.theme-ripple');
|
||||
|
||||
tagItems.forEach((item) => {
|
||||
item.addEventListener(
|
||||
'touchstart',
|
||||
() => {
|
||||
item.classList.add('touch-active');
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
item.addEventListener(
|
||||
'touchend',
|
||||
() => {
|
||||
setTimeout(() => {
|
||||
item.classList.remove('touch-active');
|
||||
}, 150);
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
|
||||
// Cancel active state if touch moves away
|
||||
item.addEventListener(
|
||||
'touchmove',
|
||||
(e) => {
|
||||
const touch = e.touches[0];
|
||||
const rect = item.getBoundingClientRect();
|
||||
|
||||
if (
|
||||
touch.clientX < rect.left ||
|
||||
touch.clientX > rect.right ||
|
||||
touch.clientY < rect.top ||
|
||||
touch.clientY > rect.bottom
|
||||
) {
|
||||
item.classList.remove('touch-active');
|
||||
}
|
||||
},
|
||||
{ passive: true }
|
||||
);
|
||||
});
|
||||
};
|
||||
|
||||
addTouchSupport();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Base styles */
|
||||
.tag-cloud {
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(0, 0, 0, 0.03),
|
||||
0 2px 4px rgba(0, 0, 0, 0.03),
|
||||
0 4px 8px rgba(0, 0, 0, 0.05);
|
||||
transform-style: preserve-3d;
|
||||
perspective: 1000px;
|
||||
transition: all var(--theme-transition);
|
||||
width: 100% !important;
|
||||
max-width: 100% !important;
|
||||
box-sizing: border-box;
|
||||
margin-left: 0 !important;
|
||||
margin-right: 0 !important;
|
||||
}
|
||||
|
||||
/* Fix for horizontal overflow */
|
||||
:global(html),
|
||||
:global(body) {
|
||||
overflow-x: hidden;
|
||||
width: 100%;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
:global(.max-w-6xl) {
|
||||
max-width: 100% !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
/* Micro screens (below 240px) */
|
||||
@media (max-width: 239px) {
|
||||
.tag-cloud {
|
||||
padding: 0.5rem !important;
|
||||
margin: 0 !important;
|
||||
border-radius: 0.25rem !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.tag-cloud h2 {
|
||||
font-size: 0.875rem !important;
|
||||
margin-bottom: 0.375rem !important;
|
||||
}
|
||||
|
||||
.grid {
|
||||
grid-template-columns: repeat(1, minmax(0, 1fr)) !important;
|
||||
gap: 0.375rem !important;
|
||||
width: 100% !important;
|
||||
}
|
||||
|
||||
.micro-screen .flex {
|
||||
padding: 0.25rem !important;
|
||||
}
|
||||
|
||||
.micro-screen h3 {
|
||||
font-size: 0.625rem !important;
|
||||
}
|
||||
|
||||
.micro-screen p {
|
||||
font-size: 0.5rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra extra extra small screens (240px-279px) */
|
||||
@media (min-width: 240px) and (max-width: 279px) {
|
||||
.xxxs\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.xxxs\:px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.xxxs\:gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.xxxs\:text-\[9px\] {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra extra small screens (280px-359px) */
|
||||
@media (min-width: 280px) {
|
||||
.xxs\:grid-cols-2 {
|
||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.xxs\:px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.xxs\:py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.xxs\:w-6 {
|
||||
width: 1.5rem;
|
||||
}
|
||||
|
||||
.xxs\:h-6 {
|
||||
height: 1.5rem;
|
||||
}
|
||||
|
||||
.xxs\:text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.xxs\:gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.xxs\:text-\[9px\] {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Extra small screens (360px-639px) */
|
||||
@media (min-width: 360px) {
|
||||
.xs\:grid-cols-3 {
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
}
|
||||
|
||||
.xs\:px-2 {
|
||||
padding-left: 0.5rem;
|
||||
padding-right: 0.5rem;
|
||||
}
|
||||
|
||||
.xs\:py-2 {
|
||||
padding-top: 0.5rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.xs\:w-7 {
|
||||
width: 1.75rem;
|
||||
}
|
||||
|
||||
.xs\:h-7 {
|
||||
height: 1.75rem;
|
||||
}
|
||||
|
||||
.xs\:text-xs {
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
|
||||
.xs\:text-sm {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.xs\:gap-2 {
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.xs\:text-\[10px\] {
|
||||
font-size: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Ensure text doesn't overflow on small screens */
|
||||
.truncate {
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Ensure proper word breaking for long tag names */
|
||||
.break-words {
|
||||
word-break: break-word;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
.hyphens-auto {
|
||||
hyphens: auto;
|
||||
}
|
||||
|
||||
/* Shadow for dark mode */
|
||||
:global(.dark) .tag-cloud {
|
||||
box-shadow:
|
||||
0 0 0 1px rgba(255, 255, 255, 0.05),
|
||||
0 2px 4px rgba(0, 0, 0, 0.1),
|
||||
0 4px 8px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
/* Prevent layout shifts */
|
||||
.grow {
|
||||
grow: 1;
|
||||
}
|
||||
|
||||
.min-w-0 {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
/* Ensure container doesn't overflow */
|
||||
.overflow-hidden {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Touch support for mobile */
|
||||
.touch-active {
|
||||
transform: scale(0.97) !important;
|
||||
opacity: 0.9;
|
||||
transition:
|
||||
transform 0.15s ease-in-out,
|
||||
opacity 0.15s ease-in-out !important;
|
||||
}
|
||||
|
||||
/* Animation for blob */
|
||||
@keyframes blob {
|
||||
0%,
|
||||
100% {
|
||||
transform: translate(0, 0) scale(1);
|
||||
}
|
||||
25% {
|
||||
transform: translate(10px, -10px) scale(1.05);
|
||||
}
|
||||
50% {
|
||||
transform: translate(0, 20px) scale(0.95);
|
||||
}
|
||||
75% {
|
||||
transform: translate(-10px, -10px) scale(1.05);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-blob {
|
||||
animation: blob 20s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.animation-delay-2000 {
|
||||
animation-delay: 2s;
|
||||
}
|
||||
|
||||
.animation-delay-4000 {
|
||||
animation-delay: 4s;
|
||||
}
|
||||
|
||||
/* Animation for underline */
|
||||
@keyframes underline {
|
||||
0% {
|
||||
transform: scaleX(0);
|
||||
}
|
||||
100% {
|
||||
transform: scaleX(1);
|
||||
}
|
||||
}
|
||||
|
||||
.animate-underline {
|
||||
animation: underline 1.5s ease-out forwards;
|
||||
}
|
||||
|
||||
/* Fix for iOS Safari notch */
|
||||
@supports (padding: max(0px)) {
|
||||
.tag-cloud {
|
||||
padding-left: max(0.75rem, env(safe-area-inset-left));
|
||||
padding-right: max(0.75rem, env(safe-area-inset-right));
|
||||
padding-bottom: max(0.75rem, env(safe-area-inset-bottom));
|
||||
}
|
||||
}
|
||||
</style>
|
@@ -23,19 +23,7 @@ const iconSets = {
|
||||
si: SiIcons,
|
||||
};
|
||||
|
||||
const DynamicIcon = ({
|
||||
name,
|
||||
set = 'fa',
|
||||
size = 20,
|
||||
color = 'currentColor',
|
||||
className = '',
|
||||
}: {
|
||||
name: string;
|
||||
set: string;
|
||||
size: number;
|
||||
color: string;
|
||||
className: string;
|
||||
}) => {
|
||||
const DynamicIcon = ({ name, set = 'fa' }: { name: string; set: string }) => {
|
||||
let IconComponent = FaIcons.FaAlignCenter;
|
||||
|
||||
if (name.startsWith('Fa')) {
|
||||
@@ -46,7 +34,7 @@ const DynamicIcon = ({
|
||||
IconComponent = iconSets[set][name];
|
||||
}
|
||||
|
||||
return <IconComponent size={size} color={color} className={className} />;
|
||||
return <IconComponent />;
|
||||
};
|
||||
|
||||
export default DynamicIcon;
|
||||
|
Reference in New Issue
Block a user