Compare commits

...

38 Commits

Author SHA1 Message Date
semantic-release-bot
f79906fa62 chore(release): 3.13.0 [skip ci]
# [3.13.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.12.1...3.13.0) (2026-03-16)

### Bug Fixes

* change execution mode ([a6c889f](a6c889f76a))

### Features

* add and update pre-commit ([148fe8e](148fe8eeff))
* add fallback ([787479e](787479e077))
* add fallback ([220c29f](220c29f4f7))
* add fallback to run animations on switch ([954112e](954112e30e))
* add semantic-release/npm ([91c9a4b](91c9a4bb91))
* change paths ([9319228](9319228ef6))
* consolidate css into tailwind ([dfeb181](dfeb181a1d))
* downgrade to astro 5 ([f35c73b](f35c73b028))
* move scripts to script folder ([641c7cb](641c7cb33f))
* refactor static paths and photoswipe on blog page, move script to base layout ([93a53ca](93a53cab3d))
* remove react ([e3179b0](e3179b0480))
* revert shiki css changes ([c4104a5](c4104a52d1))
2026-03-16 16:01:46 +00:00
a2c1d23af6 Merge pull request 'main' (#390) from main into release
All checks were successful
release-image / guarddog (push) Successful in 24s
release-image / build (push) Successful in 3m52s
release-image / semantic-release (push) Successful in 4m45s
release-image / release-harbor (push) Successful in 14m9s
release-image / release-gitea (push) Successful in 13m0s
Reviewed-on: #390
2026-03-16 15:53:29 +00:00
e3179b0480 feat: remove react
All checks were successful
renovate / renovate (push) Successful in 1m17s
test-build / guarddog (push) Successful in 1m8s
test-build / build (push) Successful in 2m3s
2026-03-16 00:00:26 -05:00
c4104a52d1 feat: revert shiki css changes 2026-03-16 00:00:13 -05:00
954112e30e feat: add fallback to run animations on switch
All checks were successful
test-build / guarddog (push) Successful in 21s
renovate / renovate (push) Successful in 58s
test-build / build (push) Successful in 1m51s
2026-03-15 23:50:04 -05:00
f35c73b028 feat: downgrade to astro 5
All checks were successful
test-build / guarddog (push) Successful in 17s
test-build / build (push) Successful in 1m49s
renovate / renovate (push) Successful in 2m20s
2026-03-15 23:40:08 -05:00
5cf1cd30d8 chore(deps): update deps
All checks were successful
renovate / renovate (push) Successful in 32s
test-build / guarddog (push) Successful in 40s
test-build / build (push) Successful in 2m19s
2026-03-15 23:11:38 -05:00
787479e077 feat: add fallback 2026-03-15 23:01:12 -05:00
91c9a4bb91 feat: add semantic-release/npm
All checks were successful
renovate / renovate (push) Successful in 40s
test-build / guarddog (push) Successful in 23s
test-build / build (push) Successful in 1m31s
2026-03-15 22:51:05 -05:00
220c29f4f7 feat: add fallback
All checks were successful
renovate / renovate (push) Successful in 49s
test-build / guarddog (push) Successful in 1m2s
test-build / build (push) Successful in 2m15s
2026-03-15 22:44:07 -05:00
dfeb181a1d feat: consolidate css into tailwind
Some checks failed
test-build / guarddog (push) Successful in 18s
renovate / renovate (push) Successful in 34s
test-build / build (push) Has been cancelled
2026-03-15 22:38:23 -05:00
641c7cb33f feat: move scripts to script folder
All checks were successful
renovate / renovate (push) Successful in 26s
test-build / guarddog (push) Successful in 3m51s
test-build / build (push) Successful in 4m40s
2026-03-15 21:53:59 -05:00
93a53cab3d feat: refactor static paths and photoswipe on blog page, move script to base layout
All checks were successful
test-build / guarddog (push) Successful in 31s
renovate / renovate (push) Successful in 39s
test-build / build (push) Successful in 1m39s
2026-03-15 21:44:40 -05:00
a6c889f76a fix: change execution mode 2026-03-15 21:10:08 -05:00
9319228ef6 feat: change paths 2026-03-15 20:02:27 -05:00
148fe8eeff feat: add and update pre-commit
All checks were successful
test-build / guarddog (push) Successful in 17s
test-build / build (push) Successful in 4m8s
renovate / renovate (push) Successful in 1m35s
2026-03-15 16:56:54 -05:00
semantic-release-bot
3651999a98 chore(release): 3.12.1 [skip ci]
## [3.12.1](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.12.0...3.12.1) (2026-03-14)

### Bug Fixes

* handle script with swup ([8d1d47d](8d1d47dc4f))
2026-03-14 18:22:48 +00:00
27df97522c Merge pull request 'fix: handle script with swup' (#388) from main into release
All checks were successful
release-image / guarddog (push) Successful in 25s
release-image / build (push) Successful in 1m52s
release-image / semantic-release (push) Successful in 54s
release-image / release-harbor (push) Successful in 5m35s
release-image / release-gitea (push) Successful in 5m34s
Reviewed-on: #388
2026-03-14 18:20:15 +00:00
8d1d47dc4f fix: handle script with swup
All checks were successful
test-build / guarddog (push) Successful in 33s
test-build / build (push) Successful in 1m41s
renovate / renovate (push) Successful in 1m26s
2026-03-14 13:17:33 -05:00
semantic-release-bot
6fd9084b77 chore(release): 3.12.0 [skip ci]
# [3.12.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.11.0...3.12.0) (2026-03-13)

### Features

* enable prerender pending resoltuion of server island fix ([7b8fb38](7b8fb380d4))
2026-03-13 16:49:07 +00:00
7b791dbb8a Merge pull request 'feat: enable prerender pending resoltuion of server island fix' (#387) from main into release
All checks were successful
release-image / guarddog (push) Successful in 29s
release-image / build (push) Successful in 1m23s
release-image / semantic-release (push) Successful in 2m1s
release-image / release-harbor (push) Successful in 6m41s
release-image / release-gitea (push) Successful in 4m37s
Reviewed-on: #387
2026-03-13 16:41:41 +00:00
7b8fb380d4 feat: enable prerender pending resoltuion of server island fix
All checks were successful
test-build / guarddog (push) Successful in 30s
test-build / build (push) Successful in 1m55s
renovate / renovate (push) Successful in 1m15s
2026-03-13 11:39:59 -05:00
semantic-release-bot
a08fff1d77 chore(release): 3.11.0 [skip ci]
# [3.11.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.10.0...3.11.0) (2026-03-13)

### Features

* adjustment to spacing ([629403f](629403fdde))
* improve category cards spacing, overflow, and add fade effect to edges ([d0f1098](d0f10986cd))
* move script handling to use swup instead of astro transitions, move animations to baselayout ([500d9e2](500d9e2ea0))
* remove astro features that are replaced by swup ([70a9499](70a94990e2))
2026-03-13 16:14:56 +00:00
6feb1a4f71 Merge pull request 'Release' (#386) from main into release
All checks were successful
release-image / guarddog (push) Successful in 32s
release-image / build (push) Successful in 1m47s
release-image / semantic-release (push) Successful in 2m34s
release-image / release-harbor (push) Successful in 7m10s
release-image / release-gitea (push) Successful in 7m6s
Reviewed-on: #386
2026-03-13 16:11:25 +00:00
500d9e2ea0 feat: move script handling to use swup instead of astro transitions, move animations to baselayout
All checks were successful
renovate / renovate (push) Successful in 41s
test-build / build (push) Successful in 1m25s
test-build / guarddog (push) Successful in 1m43s
2026-03-13 10:59:25 -05:00
70a94990e2 feat: remove astro features that are replaced by swup 2026-03-13 10:35:15 -05:00
d0f10986cd feat: improve category cards spacing, overflow, and add fade effect to edges
All checks were successful
test-build / guarddog (push) Successful in 23s
renovate / renovate (push) Successful in 1m42s
test-build / build (push) Successful in 1m48s
2026-03-13 10:18:04 -05:00
629403fdde feat: adjustment to spacing 2026-03-13 10:17:39 -05:00
semantic-release-bot
cd1d8b439d chore(release): 3.10.0 [skip ci]
# [3.10.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.9.0...3.10.0) (2026-03-13)

### Bug Fixes

* function needing paranthensis ([54c82a7](54c82a7f79))

### Features

* add scripts to clear and run from scratch ([805cb28](805cb28185))
* disable prerender pending resoltuion of server island fix ([eab6e0c](eab6e0c31d))
* test placing weather section below posts ([43f0f7b](43f0f7b324))
2026-03-13 14:36:01 +00:00
fb2e87749c Merge pull request 'Release' (#385) from main into release
All checks were successful
release-image / guarddog (push) Successful in 16s
release-image / build (push) Successful in 1m46s
release-image / semantic-release (push) Successful in 2m48s
release-image / release-harbor (push) Successful in 9m18s
release-image / release-gitea (push) Successful in 6m55s
Reviewed-on: #385
2026-03-13 14:32:29 +00:00
43f0f7b324 feat: test placing weather section below posts
All checks were successful
renovate / renovate (push) Successful in 2m8s
test-build / guarddog (push) Successful in 24s
test-build / build (push) Successful in 6m32s
2026-03-13 09:26:45 -05:00
eab6e0c31d feat: disable prerender pending resoltuion of server island fix 2026-03-13 09:25:52 -05:00
805cb28185 feat: add scripts to clear and run from scratch
All checks were successful
test-build / guarddog (push) Successful in 31s
renovate / renovate (push) Successful in 45s
test-build / build (push) Successful in 1m32s
2026-03-13 01:35:14 -05:00
54c82a7f79 fix: function needing paranthensis 2026-03-13 01:34:32 -05:00
9a62f867f1 ci: minor tweaks to args and env 2026-03-13 01:34:02 -05:00
semantic-release-bot
f525567877 chore(release): 3.9.0 [skip ci]
# [3.9.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.8.0...3.9.0) (2026-03-13)

### Features

* copy package.json ([0bef13c](0bef13c414))
2026-03-13 03:27:17 +00:00
7c5356088c Merge pull request 'feat: copy package.json' (#384) from main into release
All checks were successful
release-image / guarddog (push) Successful in 26s
release-image / build (push) Successful in 2m1s
release-image / semantic-release (push) Successful in 36s
release-image / release-harbor (push) Successful in 4m48s
release-image / release-gitea (push) Successful in 4m25s
Reviewed-on: #384
2026-03-13 03:20:59 +00:00
0bef13c414 feat: copy package.json
All checks were successful
test-build / guarddog (push) Successful in 30s
renovate / renovate (push) Successful in 52s
test-build / build (push) Successful in 2m10s
2026-03-12 22:18:06 -05:00
46 changed files with 911 additions and 1173 deletions

17
.pre-commit-config.yaml Normal file
View File

@@ -0,0 +1,17 @@
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v6.0.0
hooks:
- id: end-of-file-fixer
- id: trailing-whitespace
- id: check-merge-conflict
- id: check-json
- repo: https://github.com/pre-commit/mirrors-prettier
rev: v4.0.0-alpha.11
hooks:
- id: prettier
types_or: [javascript, typescript, css, scss, html, json, yaml, markdown]
additional_dependencies:
- prettier
- prettier-plugin-astro
- prettier-plugin-tailwindcss

View File

@@ -1,3 +1,71 @@
# [3.13.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.12.1...3.13.0) (2026-03-16)
### Bug Fixes
* change execution mode ([a6c889f](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/a6c889f76a2e55e2efe8a19ffdacdb7327dd0116))
### Features
* add and update pre-commit ([148fe8e](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/148fe8eeffeb377ce8006c5126625e7d338a4db7))
* add fallback ([787479e](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/787479e077e3608112f6ddad41d6d5e686c0166f))
* add fallback ([220c29f](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/220c29f4f766ff78967c8198e0e1605067404551))
* add fallback to run animations on switch ([954112e](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/954112e30e1ce912ca7098455555f675a49f9841))
* add semantic-release/npm ([91c9a4b](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/91c9a4bb91eea9baea5fadcebbc280f416555e23))
* change paths ([9319228](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/9319228ef6df0039dc8ee6ab88882f2e5e044d14))
* consolidate css into tailwind ([dfeb181](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/dfeb181a1d781d5bcdcf7240a67e594f3a170e50))
* downgrade to astro 5 ([f35c73b](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/f35c73b02873405b8ab350b6b5a8385339ff2329))
* move scripts to script folder ([641c7cb](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/641c7cb33feec65b185d307a7cf1c9879b884a03))
* refactor static paths and photoswipe on blog page, move script to base layout ([93a53ca](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/93a53cab3d6e1cb6395a60537d07de22686f21f6))
* remove react ([e3179b0](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/e3179b0480a56d173439fafddca7d75f96fb1b42))
* revert shiki css changes ([c4104a5](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/c4104a52d170cef2da0a087f7252caa5bc3a19db))
## [3.12.1](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.12.0...3.12.1) (2026-03-14)
### Bug Fixes
* handle script with swup ([8d1d47d](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/8d1d47dc4f09ecdd56d898e7d70b0c27f1b02e04))
# [3.12.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.11.0...3.12.0) (2026-03-13)
### Features
* enable prerender pending resoltuion of server island fix ([7b8fb38](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/7b8fb380d4a4770ac695c74a4cbf7019d5f069e8))
# [3.11.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.10.0...3.11.0) (2026-03-13)
### Features
* adjustment to spacing ([629403f](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/629403fddea28e86a7e3fc7f9a7cec5bc43ef027))
* improve category cards spacing, overflow, and add fade effect to edges ([d0f1098](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/d0f10986cddafffa7893a8751bd1a893dd672983))
* move script handling to use swup instead of astro transitions, move animations to baselayout ([500d9e2](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/500d9e2ea03c07bc819132b91c99e12cd8d5fc56))
* remove astro features that are replaced by swup ([70a9499](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/70a94990e2067891d68cbfb3831b07e3df346c4e))
# [3.10.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.9.0...3.10.0) (2026-03-13)
### Bug Fixes
* function needing paranthensis ([54c82a7](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/54c82a7f79a335f8dfbe41cbaed7e784a0d80aa7))
### Features
* add scripts to clear and run from scratch ([805cb28](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/805cb281853edff06adaf4c62b7ae256a00bea3f))
* disable prerender pending resoltuion of server island fix ([eab6e0c](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/eab6e0c31dbd24470fa55409326d830a374f86cf))
* test placing weather section below posts ([43f0f7b](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/43f0f7b3248cead38e8f9a6ff0d9e411483e440c))
# [3.9.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.8.0...3.9.0) (2026-03-13)
### Features
* copy package.json ([0bef13c](https://gitea.alexlebens.dev/alexlebens/site-profile/commit/0bef13c414a00f5f7750d1db1db28d29b791394f))
# [3.8.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.7.0...3.8.0) (2026-03-12) # [3.8.0](https://gitea.alexlebens.dev/alexlebens/site-profile/compare/3.7.0...3.8.0) (2026-03-12)

View File

@@ -23,8 +23,8 @@ COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist COPY --from=build /app/dist /app/dist
ARG APP_VERSION=latest ARG APP_VERSION=latest
ARG APP_VERSION
ENV NODE_ENV=production
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV PORT=4321 ENV PORT=4321

View File

@@ -1,14 +1,13 @@
import { defineConfig } from 'astro/config'; import { defineConfig } from 'astro/config';
import node from '@astrojs/node'; import node from '@astrojs/node';
import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap'; import sitemap from '@astrojs/sitemap';
import tailwindcss from '@tailwindcss/vite'; import tailwindcss from '@tailwindcss/vite';
import icon from 'astro-icon'; import icon from 'astro-icon';
import swup from '@swup/astro'; import swup from '@swup/astro';
import { getSiteURL } from './src/support/url'; import { getSiteURL } from './src/scripts/url';
export default defineConfig({ export default defineConfig({
site: getSiteURL(), site: getSiteURL(),
@@ -24,10 +23,7 @@ export default defineConfig({
} }
}, },
prefetch: true,
integrations: [ integrations: [
react(),
sitemap(), sitemap(),
icon({ icon({
include: { include: {
@@ -45,17 +41,16 @@ export default defineConfig({
}), }),
(await import('@playform/compress')).default({ (await import('@playform/compress')).default({
CSS: true, CSS: true,
JavaScript: true, JavaScript: false,
HTML: { HTML: {
'html-minifier-terser': { 'html-minifier-terser': {
collapseWhitespace: true, collapseWhitespace: true,
minifyCSS: false, minifyCSS: false,
minifyJS: true, minifyJS: false,
}, },
}, },
Image: false, Image: false,
SVG: true, SVG: true,
Logger: 2,
}), }),
], ],
@@ -76,13 +71,4 @@ export default defineConfig({
adapter: node({ adapter: node({
mode: 'standalone', mode: 'standalone',
}), }),
build: {
// Specifies the directory in the build output where Astro-generated assets (bundled JS and CSS for example) should live.
// see https://docs.astro.build/en/reference/configuration-reference/#buildassets
assets: 'assets',
// see https://docs.astro.build/en/reference/configuration-reference/#buildassetsprefix
assetsPrefix:
!!import.meta.env.S3_ENABLE || !!process.env.S3_ENABLE ? 'https://digitalocean.com' : '',
},
}); });

689
bun.lock

File diff suppressed because it is too large Load Diff

View File

@@ -1,7 +1,7 @@
{ {
"name": "site-profile", "name": "site-profile",
"type": "module", "type": "module",
"version": "3.8.0", "version": "3.13.0",
"homepage": "https://www.alexlebens.dev", "homepage": "https://www.alexlebens.dev",
"bugs": { "bugs": {
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues",
@@ -19,8 +19,10 @@
}, },
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"full-dev": "rm -rf dist node_modules .astro && bun install && astro build && astro dev",
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"full-preview": "rm -rf dist node_modules .astro && bun install && astro build && astro preview",
"astro": "astro", "astro": "astro",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\"", "format": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\"",
"lint": "eslint \"src/**/*.{js,ts,jsx,tsx,astro}\"", "lint": "eslint \"src/**/*.{js,ts,jsx,tsx,astro}\"",
@@ -28,22 +30,20 @@
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.7", "@astrojs/check": "^0.9.7",
"@astrojs/node": "^10.0.1", "@astrojs/node": "^9.5.5",
"@astrojs/react": "^5.0.0",
"@astrojs/rss": "^4.0.17", "@astrojs/rss": "^4.0.17",
"@astrojs/sitemap": "^3.7.1", "@astrojs/sitemap": "^3.7.1",
"@directus/sdk": "^21.2.0", "@directus/sdk": "^21.2.0",
"@giscus/react": "^3.1.0",
"@iconify-json/mdi": "^1.2.3", "@iconify-json/mdi": "^1.2.3",
"@iconify-json/pajamas": "^1.2.15", "@iconify-json/pajamas": "^1.2.15",
"@iconify-json/simple-icons": "^1.2.73", "@iconify-json/simple-icons": "^1.2.74",
"@playform/compress": "^0.2.1", "@playform/compress": "^0.2.2",
"@swup/astro": "^1.8.0", "@swup/astro": "^1.8.0",
"@tailwindcss/postcss": "^4.2.1", "@tailwindcss/postcss": "^4.2.1",
"@tailwindcss/vite": "^4.2.1", "@tailwindcss/vite": "^4.2.1",
"@types/react": "^19.2.14",
"@types/unist": "^3.0.3", "@types/unist": "^3.0.3",
"astro": "^6.0.4", "astro": "^5.18.1",
"astro-compress": "^2.4.0",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"dayjs": "^1.11.20", "dayjs": "^1.11.20",
"markdown-it": "^14.1.1", "markdown-it": "^14.1.1",
@@ -52,8 +52,6 @@
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"photoswipe": "^5.4.4", "photoswipe": "^5.4.4",
"preline": "^4.1.2", "preline": "^4.1.2",
"react": "^19.2.4",
"react-dom": "^19.2.4",
"reading-time": "^1.5.0", "reading-time": "^1.5.0",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"sharp-ico": "^0.1.5", "sharp-ico": "^0.1.5",
@@ -62,11 +60,11 @@
"ultrahtml": "^1.6.0" "ultrahtml": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint-react/eslint-plugin": "^2.13.0",
"@saithodev/semantic-release-gitea": "^2.1.0", "@saithodev/semantic-release-gitea": "^2.1.0",
"@semantic-release/changelog": "^6.0.3", "@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1", "@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1", "@semantic-release/git": "^10.0.1",
"@semantic-release/npm": "^13.1.5",
"@semantic-release/release-notes-generator": "^14.1.0", "@semantic-release/release-notes-generator": "^14.1.0",
"@tailwindcss/forms": "^0.5.11", "@tailwindcss/forms": "^0.5.11",
"@tailwindcss/typography": "^0.5.19", "@tailwindcss/typography": "^0.5.19",
@@ -75,9 +73,6 @@
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-astro": "^1.6.0", "eslint-plugin-astro": "^1.6.0",
"eslint-plugin-format": "^2.0.1", "eslint-plugin-format": "^2.0.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.5.2",
"prettier": "^3.8.1", "prettier": "^3.8.1",
"prettier-plugin-astro": "^0.14.1", "prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.7.2", "prettier-plugin-tailwindcss": "^0.7.2",

View File

@@ -95,5 +95,3 @@ const currentPath = pathname.slice(1);
</div> </div>
</nav> </nav>
</header> </header>
<script is:inline src="/vendor/preline/collapse2.1.0.min.js"></script>

View File

@@ -43,106 +43,93 @@
</button> </button>
<script is:inline> <script is:inline>
const applyTheme = () => { (() => {
const isDark = const isDark =
localStorage.theme === 'dark' || localStorage.theme === 'dark' ||
(!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches); (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
document.documentElement.classList.toggle('dark', isDark); document.documentElement.classList.toggle('dark', isDark);
}; })();
applyTheme();
document.addEventListener('astro:after-swap', applyTheme);
</script> </script>
<script> <script>
function setupThemeToggle() { if (!document.querySelector('.theme-switch-overlay')) {
const themeToggles = document.querySelectorAll('[data-theme-toggle]'); const overlay = document.createElement('div');
overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50';
// Create theme switch overlay element overlay.style.opacity = '0';
if (!document.querySelector('.theme-switch-overlay')) { overlay.style.transition = 'opacity 0.15s ease-out';
const overlay = document.createElement('div'); document.body.appendChild(overlay);
overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50';
overlay.style.opacity = '0';
overlay.style.transition = 'opacity 0.15s ease-out';
document.body.appendChild(overlay);
}
themeToggles.forEach((toggle) => {
['click', 'touchend'].forEach((eventType) => {
toggle.addEventListener(
eventType,
(e) => {
e.preventDefault();
e.stopPropagation();
// Get click/touch position for radial animation
let x, y;
if (e.type === 'touchend' && e.changedTouches && e.changedTouches[0]) {
const rect = toggle.getBoundingClientRect();
x = e.changedTouches[0].clientX - rect.left;
y = e.changedTouches[0].clientY - rect.top;
} else {
const rect = toggle.getBoundingClientRect();
x = e.clientX - rect.left;
y = e.clientY - rect.top;
}
document.documentElement.style.setProperty('--x', `${x}px`);
document.documentElement.style.setProperty('--y', `${y}px`);
const overlay = document.querySelector('.theme-switch-overlay');
const isDark = document.documentElement.classList.contains('dark');
const newTheme = isDark ? 'light' : 'dark';
// Show overlay during transition
if (overlay) {
overlay.style.backgroundColor =
newTheme === 'dark' ? 'rgba(24, 24, 27, 0.3)' : 'rgba(255, 255, 255, 0.3)';
overlay.style.opacity = '1';
}
document.documentElement.classList.add('theme-switching');
// Force a reflow to ensure all elements update
document.body.offsetHeight;
// Toggle dark mode with a slight delay to allow overlay to appear
setTimeout(() => {
if (isDark) {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
localStorage.setItem('theme', newTheme);
document.dispatchEvent(
new CustomEvent('themeChanged', {
detail: { isDark: newTheme === 'dark' },
})
);
// Force another reflow to ensure all elements update
document.body.offsetHeight;
setTimeout(() => {
if (overlay) {
overlay.style.opacity = '0';
}
document.documentElement.classList.remove('theme-switching');
}, 150);
}, 50);
},
{ passive: false }
);
});
});
} }
['click', 'touchend'].forEach((eventType) => {
document.addEventListener(
eventType,
(e) => {
const target = e.target as HTMLElement;
const toggle = target.closest('[data-theme-toggle]');
// Run setup on load if (!toggle) return;
document.addEventListener('astro:page-load', setupThemeToggle);
e.preventDefault();
let x, y;
const rect = toggle.getBoundingClientRect();
if (eventType === 'touchend') {
const touchEvent = e as TouchEvent;
if (touchEvent.changedTouches && touchEvent.changedTouches[0]) {
x = touchEvent.changedTouches[0].clientX - rect.left;
y = touchEvent.changedTouches[0].clientY - rect.top;
}
} else {
const mouseEvent = e as MouseEvent;
x = mouseEvent.clientX - rect.left;
y = mouseEvent.clientY - rect.top;
}
document.documentElement.style.setProperty('--x', `${x}px`);
document.documentElement.style.setProperty('--y', `${y}px`);
const overlay = document.querySelector('.theme-switch-overlay') as HTMLElement;
const isDark = document.documentElement.classList.contains('dark');
const newTheme = isDark ? 'light' : 'dark';
if (overlay) {
overlay.style.backgroundColor =
newTheme === 'dark' ? 'rgba(24, 24, 27, 0.3)' : 'rgba(255, 255, 255, 0.3)';
overlay.style.opacity = '1';
}
document.documentElement.classList.add('theme-switching');
document.body.offsetHeight;
setTimeout(() => {
if (isDark) {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
localStorage.setItem('theme', newTheme);
document.dispatchEvent(
new CustomEvent('themeChanged', {
detail: { isDark: newTheme === 'dark' },
})
);
document.body.offsetHeight;
setTimeout(() => {
if (overlay) {
overlay.style.opacity = '0';
}
document.documentElement.classList.remove('theme-switching');
}, 150);
}, 50);
},
{ passive: false }
);
});
// Also run on page visibility change to ensure theme is consistent
document.addEventListener('visibilitychange', () => { document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') { if (document.visibilityState === 'visible') {
const currentTheme = localStorage.getItem('theme'); const currentTheme = localStorage.getItem('theme');
@@ -154,7 +141,6 @@
} }
}); });
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => { window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => {
if (!localStorage.getItem('theme')) { if (!localStorage.getItem('theme')) {
if (matches) { if (matches) {

View File

@@ -4,7 +4,7 @@ import { Image } from 'astro:assets';
import type { Post } from '@lib/directusTypes'; import type { Post } from '@lib/directusTypes';
import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro'; import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
post: Post; post: Post;

View File

@@ -1,6 +1,6 @@
--- ---
import Logo from '@components/images/Logo.astro'; import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
slug: string; slug: string;
@@ -22,8 +22,8 @@ const { slug, title, description, logoLight, logoDark, count, publishDate } = As
data-astro-prefetch data-astro-prefetch
> >
<div class="relative grow overflow-hidden"> <div class="relative grow overflow-hidden">
<div class="absolute inset-1 flex flex-col p-3 md:p-4 lg:p-5"> <div class="mask-fade-edges absolute inset-1 flex flex-col p-3 md:p-4 lg:p-5">
<div class="flex flex-row items-center mb-4"> <div class="flex flex-row items-center mb-4 shrink-0">
<div class="card-hover-icon-scale shrink-0 mr-3"> <div class="card-hover-icon-scale shrink-0 mr-3">
<Logo <Logo
srcLight={getDirectusImageURL(logoLight)} srcLight={getDirectusImageURL(logoLight)}
@@ -35,12 +35,12 @@ const { slug, title, description, logoLight, logoDark, count, publishDate } = As
{title} {title}
</h3> </h3>
</div> </div>
<div> <div class="overflow-hidden">
<p class="card-text-description mb-4"> <p class="card-text-description line-clamp-3">
{description} {description}
</p> </p>
</div> </div>
<div class="card-text-description flex items-center justify-between text-xs mt-auto pt-1 md:pt-2"> <div class="card-text-description flex shrink-0 items-center justify-between text-xs mt-auto pt-1 md:pt-2">
<span class="inline-flex items-center"> <span class="inline-flex items-center">
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"

View File

@@ -2,7 +2,7 @@
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components';
import Logo from '@components/images/Logo.astro'; import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
topic: string; topic: string;

View File

@@ -5,7 +5,7 @@ import type { Post } from '@lib/directusTypes';
import ReadMoreButton from '@components/buttons/ReadMoreButton.astro'; import ReadMoreButton from '@components/buttons/ReadMoreButton.astro';
import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro'; import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
post: Post; post: Post;

View File

@@ -5,7 +5,7 @@ import type { Post } from '@lib/directusTypes';
import ReadMoreButton from '@components/buttons/ReadMoreButton.astro'; import ReadMoreButton from '@components/buttons/ReadMoreButton.astro';
import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro'; import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
post: Post; post: Post;

View File

@@ -1,6 +1,6 @@
--- ---
import Logo from '@components/images/Logo.astro'; import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
url: string; url: string;

View File

@@ -2,7 +2,7 @@
import { Icon } from 'astro-icon/components'; import { Icon } from 'astro-icon/components';
import { Image } from 'astro:assets'; import { Image } from 'astro:assets';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
title: string; title: string;

View File

@@ -1,7 +1,7 @@
--- ---
import ReadMoreButton from '@components/buttons/ReadMoreButton.astro'; import ReadMoreButton from '@components/buttons/ReadMoreButton.astro';
import Logo from '@components/images/Logo.astro'; import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
title: string; title: string;

View File

@@ -6,7 +6,7 @@ import type { Post } from '@lib/directusTypes';
import CategoryCard from '@components/cards/CategoryCard.astro'; import CategoryCard from '@components/cards/CategoryCard.astro';
import LargeCategoryCard from '@components/cards/LargeCategoryCard.astro'; import LargeCategoryCard from '@components/cards/LargeCategoryCard.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import { formatFromNow } from '@support/time'; import { formatFromNow } from '@/scripts/time';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));

View File

@@ -1,7 +1,7 @@
--- ---
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro'; import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import Logo from '@components/images/Logo.astro'; import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
title: string; title: string;

View File

@@ -7,7 +7,7 @@ import type { HeaderImage } from '@lib/directusTypes';
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro'; import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import GoLinkSecondaryButton from '@components/buttons/GoLinkSecondaryButton.astro'; import GoLinkSecondaryButton from '@components/buttons/GoLinkSecondaryButton.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
title: string; title: string;
@@ -82,7 +82,7 @@ const images = await Promise.all(imagesData.map(async (img) => ({
</section> </section>
<script> <script>
document.addEventListener('astro:page-load', () => { function initHeroImage() {
const container = document.getElementById('hero-image-container'); const container = document.getElementById('hero-image-container');
if (container) { if (container) {
const images = container.querySelectorAll('.hero-image'); const images = container.querySelectorAll('.hero-image');
@@ -96,5 +96,13 @@ const images = await Promise.all(imagesData.map(async (img) => ({
images[randomIndex].classList.add('flex'); images[randomIndex].classList.add('flex');
} }
} }
}); }
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', initHeroImage);
} else {
initHeroImage();
}
if ((window as any).swup) {
(window as any).swup.hooks.on('page:view', initHeroImage);
}
</script> </script>

View File

@@ -18,7 +18,7 @@ const global = await directus.request(readSingleton('site_global'));
const { posts, title, subTitle } = Astro.props; const { posts, title, subTitle } = Astro.props;
--- ---
<section class="max-w-340 2xl:max-w-full px-4 sm:px-6 lg:px-8 py-10 lg:py-14 mx-auto mb-2 md:mb-8"> <section class="max-w-340 2xl:max-w-full px-4 sm:px-6 lg:px-8 py-10 lg:py-14 mx-auto mb-2 md:mb-4">
<div class="text-center max-w-2xl mx-auto mb-10 lg:mb-14"> <div class="text-center max-w-2xl mx-auto mb-10 lg:mb-14">
<h1 class="smooth-reveal card-text-header block"> <h1 class="smooth-reveal card-text-header block">
{title} {title}

View File

@@ -11,7 +11,7 @@ interface Props {
const { posts } = Astro.props; const { posts } = Astro.props;
--- ---
<section class="smooth-reveal flex flex-col gap-4 md:mb-20"> <section class="smooth-reveal-cards flex flex-col gap-4 md:mb-20">
{posts.map((post, index) => index % 2 === 0 ? ( {posts.map((post, index) => index % 2 === 0 ? (
<LargeBlogLeftCard post={post}/> <LargeBlogLeftCard post={post}/>
) : ( ) : (

View File

@@ -1,13 +1,13 @@
--- ---
import WeatherCard from '@components/cards/WeatherCard.astro'; import WeatherCard from '@components/cards/WeatherCard.astro';
import { getFiveDayForecast } from '@support/weather'; import { getFiveDayForecast } from '@/scripts/weather';
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props; const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props;
const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, timezone); const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, timezone);
--- ---
<section class="max-w-340 2xl:max-w-fullpx-4 sm:px-6 lg:px-8 py-10 lg:py-14 mx-auto mb-2 md:mb-8"> <section class="max-w-340 2xl:max-w-full px-4 sm:px-6 lg:px-8 py-10 lg:py-14 mx-auto mb-2 md:mb-8">
<div class="text-center max-w-2xl mx-auto mb-10 lg:mb-14"> <div class="text-center max-w-2xl mx-auto mb-10 lg:mb-14">
<h1 class="smooth-reveal card-text-header block"> <h1 class="smooth-reveal card-text-header block">
Weather in my Area Weather in my Area
@@ -19,8 +19,12 @@ const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, ti
</div> </div>
</div> </div>
{error ? ( {error ? (
<div class="card-base p-10 text-accent text-center"> <div class="smooth-reveal flex flex-wrap justify-center gap-4 lg:gap-6">
Sorry, {error.toLowerCase} <div class="card-base flex flex-col justify-center items-center p-10 w-48 h-56">
<h1 class="card-text-header block text-accent text-center">
Sorry, {error.toLowerCase()}
</h1>
</div>
</div> </div>
) : ( ) : (
<div class="flex flex-wrap justify-center gap-4 lg:gap-6"> <div class="flex flex-wrap justify-center gap-4 lg:gap-6">

View File

@@ -4,8 +4,8 @@ import getReadingTime from 'reading-time';
import type { Post } from '@lib/directusTypes'; import type { Post } from '@lib/directusTypes';
import Logo from '@components/images/Logo.astro'; import Logo from '@components/images/Logo.astro';
import { formatShortDate, formatDate } from '@support/time'; import { formatShortDate, formatDate } from '@/scripts/time';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
interface Props { interface Props {
post: Post; post: Post;

View File

@@ -1,5 +1,4 @@
--- ---
import { ClientRouter } from 'astro:transitions';
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import BaseHead from '@components/BaseHead.astro'; import BaseHead from '@components/BaseHead.astro';
@@ -39,8 +38,7 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
structuredData={structuredData} structuredData={structuredData}
/> />
<ClientRouter fallback="swap" /> <!-- Set Theme -->
<script is:inline> <script is:inline>
const theme = (() => { const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
@@ -60,6 +58,12 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
window.localStorage.setItem('theme', theme); window.localStorage.setItem('theme', theme);
</script> </script>
<!-- Preline -->
<script
src="/vendor/preline/collapse2.1.0.min.js"
is:inline
/>
<!-- Rybbit Tracking Snippet --> <!-- Rybbit Tracking Snippet -->
<script <script
src="https://rybbit.alexlebens.dev/api/script.js" src="https://rybbit.alexlebens.dev/api/script.js"
@@ -97,14 +101,47 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
</html> </html>
<script> <script>
import { initPhotoSwipe } from '@/scripts/photoswipe';
import { animateContent } from '@/scripts/animations';
const resetScroll = () => { const resetScroll = () => {
const scrollContainer = document.getElementById('reset-scroll'); const scrollContainer = document.getElementById('reset-scroll');
if (scrollContainer) { if (scrollContainer) scrollContainer.scrollTop = 0;
scrollContainer.scrollTop = 0;
}
}; };
resetScroll(); resetScroll();
document.addEventListener('astro:after-swap', resetScroll); initPhotoSwipe();
animateContent();
const observer = new MutationObserver((mutations) => {
const hasNewNodes = mutations.some(
(mutation) => mutation.type === 'childList' && mutation.addedNodes.length > 0
);
if (hasNewNodes) {
animateContent();
if (typeof (window as any).HSStaticMethods !== 'undefined') {
(window as any).HSStaticMethods.autoInit();
}
}
});
const targetNode = document.getElementById('reset-scroll');
if (targetNode) {
observer.observe(targetNode, { childList: true, subtree: true });
}
document.addEventListener('swup:page:view', () => {
resetScroll();
initPhotoSwipe();
animateContent();
if (typeof (window as any).HSStaticMethods !== 'undefined') {
(window as any).HSStaticMethods.autoInit();
}
if (typeof (window as any).rybbit === 'function') {
(window as any).rybbit('trackPageview');
}
});
</script> </script>
<style> <style>
@@ -133,6 +170,7 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
/* Background that creates the "glimmer" effect */ /* Background that creates the "glimmer" effect */
.bg { .bg {
animation: slide 25s ease-in-out infinite alternate; animation: slide 25s ease-in-out infinite alternate;
will-change: transform;
background-image: linear-gradient(-55deg, var(--bg-primary) 33.3%, var(--bg-secondary) 33.3%, var(--bg-secondary) 66.6%, var(--bg-tertiary) 66.6%); background-image: linear-gradient(-55deg, var(--bg-primary) 33.3%, var(--bg-secondary) 33.3%, var(--bg-secondary) 66.6%, var(--bg-tertiary) 66.6%);
filter: blur(40px); filter: blur(40px);
top: 0; top: 0;

View File

@@ -14,7 +14,7 @@ import type {
Skill, Skill,
} from '@lib/directusTypes'; } from '@lib/directusTypes';
import { getDirectusURL } from '@/support/url'; import { getDirectusURL } from '@/scripts/url';
type Schema = { type Schema = {
site_global: Global; site_global: Global;

View File

@@ -86,24 +86,6 @@ const global = await directus.request(readSingleton('site_global'));
const randomFact = funFacts[Math.floor(Math.random() * funFacts.length)]; const randomFact = funFacts[Math.floor(Math.random() * funFacts.length)];
funFactElement.textContent = randomFact; funFactElement.textContent = randomFact;
} }
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
100 + index * 150
);
});
};
animateContent();
});
</script> </script>
<style> <style>

View File

@@ -47,33 +47,3 @@ const global = await directus.request(readSingleton('site_global'));
</section> </section>
</BaseLayout> </BaseLayout>
<script>
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
50 + index * 100
);
});
// Animate with just fade in with staggered delay
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal-fade');
},
100 + index * 250
);
});
};
animateContent();
});
</script>

View File

@@ -52,56 +52,3 @@ const posts = await directus.request(
</section> </section>
</BaseLayout> </BaseLayout>
<script>
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
50 + index * 100
);
});
// Animate group 2
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
smoothReveal2.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
200 + index * 150
);
});
// Animate topic cards with staggered delay
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
smoothRevealCards.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
500 + index * 100
);
});
// Animate with just fade in with staggered delay
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal-fade');
},
100 + index * 250
);
});
};
animateContent();
});
</script>

View File

@@ -37,23 +37,3 @@ const global = await directus.request(readSingleton('site_global'));
<ApplicationSection className="smooth-reveal-2" /> <ApplicationSection className="smooth-reveal-2" />
</BaseLayout> </BaseLayout>
<script>
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
50 + index * 100
);
});
};
animateContent();
});
</script>

View File

@@ -1,51 +1,64 @@
--- ---
import { Image } from 'astro:assets'; import { Image } from 'astro:assets';
import { marked } from 'marked'; import { marked } from 'marked';
import markedShiki from 'marked-shiki'; import markedShiki from 'marked-shiki';
import { createHighlighter } from 'shiki'; import { createHighlighter } from 'shiki';
import { readItems, readSingleton } from '@directus/sdk'; import { readItems, readSingleton } from '@directus/sdk';
import 'photoswipe/style.css'; import "photoswipe/style.css";
import type { Post } from '@/lib/directusTypes'
import SocialShareButton from '@components/buttons/SocialShareButton.astro'; import SocialShareButton from '@components/buttons/SocialShareButton.astro';
import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro'; import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import { getDirectusImageURL } from '@/support/url'; import { getDirectusImageURL } from '@/scripts/url';
const post = Astro.props;
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = await directus.request(readItems('posts', { const posts = await directus.request(readItems('posts', {
fields: ['*', { category: ['*'] }], fields: ['*', { category: ['*'] }],
})); }));
return posts.map((post) => ({ const globalData = await directus.request(readSingleton('site_global'));
const highlighter = await createHighlighter({
themes: ['github-light', 'github-dark'],
langs: ['typescript', 'python', 'css', 'html', 'yaml', 'bash', 'json'],
});
const renderer = {
image({ href, title, text }: { href: string; title: string | null; text: string }) {
return `
<a
href="${href}"
class="pswp-link"
data-pswp-src="${href}"
target="_blank"
>
<img src="${href}" alt="${text}" title="${title || ''}" loading="lazy" />
</a>
`;
},
};
marked.use({ renderer });
marked.use(markedShiki({
highlight(code, lang) {
return highlighter.codeToHtml(code, {
lang: lang || 'plaintext',
themes: { light: 'github-light', dark: 'github-dark' },
defaultColor: false,
});
}
}));
return posts.map((post: Post) => ({
params: { slug: post.slug }, params: { slug: post.slug },
props: post, props: {
post,
global: globalData,
content: marked.parse(post.content || ''),
},
})); }));
} }
const global = await directus.request(readSingleton('site_global')); const { post, global, content } = Astro.props;
const highlighter = await createHighlighter({
themes: ['github-light', 'github-dark'],
langs: ['typescript', 'python', 'css', 'html', 'yaml', 'bash', 'json'],
});
marked.use(markedShiki({
highlight(code, lang) {
return highlighter.codeToHtml(code, {
lang: lang || 'plaintext',
themes: {
light: 'github-light',
dark: 'github-dark',
},
defaultColor: false,
});
}
}));
const content = marked.parse(post.content || '');
--- ---
<BaseLayout <BaseLayout
@@ -65,7 +78,7 @@ const content = marked.parse(post.content || '');
name: global.name, name: global.name,
description: global.about, description: global.about,
}, },
image: [], image: [getDirectusImageURL(post.image)],
headline: post.title, headline: post.title,
datePublished: post.published_date, datePublished: post.published_date,
dateModified: post.updated_date, dateModified: post.updated_date,
@@ -85,7 +98,7 @@ const content = marked.parse(post.content || '');
<Image <Image
class="rounded-2xl sm:rounded-b-none w-full max-h-150 object-cover" class="rounded-2xl sm:rounded-b-none w-full max-h-150 object-cover"
src={getDirectusImageURL(post.image)} src={getDirectusImageURL(post.image)}
alt={post.image_alt} alt={post.image_alt || post.title}
draggable="false" draggable="false"
format="webp" format="webp"
loading="lazy" loading="lazy"
@@ -110,13 +123,15 @@ const content = marked.parse(post.content || '');
</article> </article>
<div class="grid sm:flex sm:items-center sm:justify-between gap-y-5 sm:gap-y-0 max-w-5xl mx-auto mt-10 md:mt-14"> <div class="grid sm:flex sm:items-center sm:justify-between gap-y-5 sm:gap-y-0 max-w-5xl mx-auto mt-10 md:mt-14">
<div class="flex flex-wrap sm:flex-nowrap sm:items-center gap-x-2 gap-y-1 sm:gap-y-0"> {post.tags && post.tags.length > 0 && (
{post.tags.map((tag: string) => ( <div class="flex flex-wrap sm:flex-nowrap sm:items-center gap-x-2 gap-y-1 sm:gap-y-0">
<span class="inline-flex items-center button-base bg-cobalt dark:bg-turquoise text-neutral-100 text-xs font-bold rounded-lg gap-x-1.5 px-3 py-1.5"> {post.tags.map((tag: string) => (
{tag} <span class="inline-flex items-center button-base bg-cobalt dark:bg-turquoise text-neutral-100 text-xs font-bold rounded-lg gap-x-1.5 px-3 py-1.5">
</span> {tag}
))} </span>
</div> ))}
</div>
)}
<SocialShareButton pageTitle={post.title}/> <SocialShareButton pageTitle={post.title}/>
</div> </div>
</div> </div>
@@ -139,56 +154,3 @@ const content = marked.parse(post.content || '');
</style> </style>
</BaseLayout> </BaseLayout>
<script>
import PhotoSwipeLightbox from 'photoswipe/lightbox';
const prose = document.querySelector('.prose');
if (prose) {
const images = prose.querySelectorAll('img');
images.forEach((img) => {
if (img.closest('a')) return;
const link = document.createElement('a');
link.href = img.src;
link.dataset.pswpSrc = img.src;
link.dataset.pswpWidth = img.naturalWidth.toString();
link.dataset.pswpHeight = img.naturalHeight.toString();
link.target = '_blank';
link.classList.add('pswp-link');
img.parentNode?.insertBefore(link, img);
link.appendChild(img);
if (!img.complete) {
img.onload = () => {
link.dataset.pswpWidth = img.naturalWidth.toString();
link.dataset.pswpHeight = img.naturalHeight.toString();
};
}
});
const lightbox = new PhotoSwipeLightbox({
gallery: prose,
children: 'a.pswp-link',
pswpModule: () => import('photoswipe'),
});
lightbox.init();
}
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
100 + index * 100
);
});
};
animateContent();
});
</script>

View File

@@ -57,56 +57,3 @@ const recentPosts: Post[] = posts.filter(
/> />
</BaseLayout> </BaseLayout>
<script>
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
200 + index * 300
);
});
// Animate group 2
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
smoothReveal2.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
500 + index * 100
);
});
// Animate topic cards with staggered delay
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
smoothRevealCards.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
1000 + index * 250
);
});
// Animate with just fade in with staggered delay
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal-fade');
},
100 + index * 250
);
});
};
animateContent();
});
</script>

View File

@@ -75,56 +75,3 @@ const categoriesPosts = posts
</section> </section>
</BaseLayout> </BaseLayout>
<script>
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
50 + index * 100
);
});
// Animate group 2
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
smoothReveal2.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
200 + index * 150
);
});
// Animate topic cards with staggered delay
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
smoothRevealCards.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
500 + index * 100
);
});
// Animate with just fade in with staggered delay
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal-fade');
},
100 + index * 250
);
});
};
animateContent();
});
</script>

View File

@@ -37,56 +37,3 @@ const global = await directus.request(readSingleton('site_global'));
<CategorySection /> <CategorySection />
</BaseLayout> </BaseLayout>
<script>
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
50 + index * 100
);
});
// Animate group 2
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
smoothReveal2.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
200 + index * 150
);
});
// Animate topic cards with staggered delay
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
smoothRevealCards.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
500 + index * 100
);
});
// Animate with just fade in with staggered delay
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal-fade');
},
100 + index * 250
);
});
};
animateContent();
});
</script>

View File

@@ -55,19 +55,35 @@ const recentPosts = posts
<FeatureSection /> <FeatureSection />
<RecentPostsSection
posts={recentPosts}
title="Latest Posts"
subTitle="Checkout my most recent thoughts here"
/>
<WeatherSection <WeatherSection
server:defer server:defer
latitude={weather.latitude} latitude={weather.latitude}
longitude={weather.longitude} longitude={weather.longitude}
cityName={weather.location} cityName={weather.location}
timezone={weather.timezone} timezone={weather.timezone}
/> >
<div slot="fallback" class="max-w-340 2xl:max-w-full px-4 sm:px-6 lg:px-8 py-10 lg:py-14 mx-auto mb-2 md:mb-8">
<RecentPostsSection <div class="text-center max-w-2xl mx-auto mb-10 lg:mb-14">
posts={recentPosts} <h1 class="smooth-reveal card-text-header block">
title="Latest Posts" Loading weather...
subTitle="Checkout my most recent thoughts here" </h1>
/> <div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center flex justify-center">
<div class="h-6 w-64"></div>
</div>
</div>
<div class="flex flex-wrap justify-center gap-4 lg:gap-6">
{Array.from({ length: 5 }).map((_, index) => (
<div class={`card-base h-62.5 w-40 animate-pulse bg-neutral-200 dark:bg-neutral-800 ${index === 3 ? "hidden min-[800px]:block" : index >= 4 ? "hidden min-[1100px]:block" : ""}`}></div>
))}
</div>
</div>
</WeatherSection>
<GiteaSection <GiteaSection
title="Follow me on Gitea" title="Follow me on Gitea"
@@ -76,62 +92,3 @@ const recentPosts = posts
/> />
</BaseLayout> </BaseLayout>
<script>
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
50 + index * 100
);
});
// Animate group 2
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
smoothReveal2.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
200 + index * 250
);
});
// Animate topic cards with staggered delay
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
smoothRevealCards.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
400 + index * 250
);
});
// Animate with just fade in with staggered delay
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal-fade');
},
100 + index * 250
);
});
};
animateContent();
const observer = new MutationObserver(() => {
animateContent();
});
observer.observe(document.body, { childList: true, subtree: true });
});
</script>

31
src/scripts/animations.ts Normal file
View File

@@ -0,0 +1,31 @@
const animateContent = () => {
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(() => {
el.classList.add('animate-reveal');
}, 50 + index * 100);
});
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
smoothReveal2.forEach((el, index) => {
setTimeout(() => {
el.classList.add('animate-reveal');
}, 200 + index * 250);
});
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
smoothRevealCards.forEach((el, index) => {
setTimeout(() => {
el.classList.add('animate-reveal');
}, 400 + index * 250);
});
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(() => {
el.classList.add('animate-reveal-fade');
}, 100 + index * 250);
});
};
export { animateContent };

38
src/scripts/photoswipe.ts Normal file
View File

@@ -0,0 +1,38 @@
import PhotoSwipeLightbox from 'photoswipe/lightbox';
import PhotoSwipe from "photoswipe";
let lightbox: PhotoSwipeLightbox | null = null;
function initPhotoSwipe() {
const links = document.querySelectorAll<HTMLAnchorElement>('a.pswp-link');
links.forEach((link) => {
const img = link.querySelector('img');
if (img) {
const applyDimensions = () => {
link.dataset.pswpWidth = (img.naturalWidth || 1920).toString();
link.dataset.pswpHeight = (img.naturalHeight || 1080).toString();
};
if (img.complete) {
applyDimensions();
} else {
img.addEventListener('load', applyDimensions);
}
}
});
if (lightbox) {
lightbox.destroy();
}
lightbox = new PhotoSwipeLightbox({
gallery: '.prose',
children: 'a.pswp-link',
pswpModule: PhotoSwipe,
allowPanToNext: true,
});
lightbox.init();
}
export { initPhotoSwipe };

View File

@@ -1,5 +1,7 @@
@import 'tailwindcss'; @import 'tailwindcss';
@import './utilities.css'; @import './utilities-buttons.css';
@import './utilities-cards.css';
@import './utilities-misc.css';
@plugin '@tailwindcss/typography'; @plugin '@tailwindcss/typography';
@plugin '@tailwindcss/forms'; @plugin '@tailwindcss/forms';
@@ -43,6 +45,29 @@
--color-background-card: light-dark(color-mix(in srgb, var(--color-neutral-100) 80%, transparent), color-mix(in srgb, var(--color-neutral-800) 60%, transparent)); --color-background-card: light-dark(color-mix(in srgb, var(--color-neutral-100) 80%, transparent), color-mix(in srgb, var(--color-neutral-800) 60%, transparent));
--color-divider: light-dark(color-mix(in srgb, var(--color-slate-400) 40%, transparent), color-mix(in srgb, var(--color-neutral-500) 50%, transparent)); --color-divider: light-dark(color-mix(in srgb, var(--color-slate-400) 40%, transparent), color-mix(in srgb, var(--color-neutral-500) 50%, transparent));
/* Typography */
--color-prose-blog-body: var(--color-neutral-700);
--color-prose-blog-headings: var(--color-neutral-900);
--color-prose-blog-links: var(--color-orange-300);
--color-prose-blog-invert-body: var(--color-neutral-400);
--color-prose-blog-invert-headings: var(--color-neutral-200);
--radius-markdown-img: 0.5rem;
/* Reveal Animations */
--animate-reveal: reveal 0.8s ease forwards;
--animate-reveal-fade: reveal-fade 1.8s ease forwards;
@keyframes reveal {
from { opacity: 0; transform: translateY(20px); }
to { opacity: 1; transform: translateY(0); }
}
@keyframes reveal-fade {
from { opacity: 0; }
to { opacity: 1; }
}
} }
@layer base { @layer base {
@@ -51,7 +76,6 @@
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
--theme-transition: 0.3s ease; --theme-transition: 0.3s ease;
--scroll-offset: 0px;
} }
:root:where(.dark, .dark *) { :root:where(.dark, .dark *) {
@@ -90,7 +114,54 @@
border-color var(--theme-transition); border-color var(--theme-transition);
} }
/* Shiki syntax highlighting */ /* Typography */
.prose blockquote {
font-style: normal;
quotes: none;
}
.prose img {
@apply rounded-lg;
}
.prose-blog {
--tw-prose-body: var(--color-neutral-700);
--tw-prose-headings: var(--color-neutral-900);
--tw-prose-lead: var(--color-neutral-700);
--tw-prose-links: var(--color-orange-300);
--tw-prose-bold: var(--color-neutral-900);
--tw-prose-counters: var(--color-neutral-600);
--tw-prose-bullets: var(--color-neutral-400);
--tw-prose-hr: var(--color-neutral-300);
--tw-prose-quotes: var(--color-neutral-500);
--tw-prose-quote-borders: var(--color-neutral-300);
--tw-prose-captions: var(--color-neutral-700);
--tw-prose-code: var(--color-neutral-700);
--tw-prose-pre-code: var(--color-neutral-900);
--tw-prose-pre-bg: var(--color-white);
--tw-prose-th-borders: var(--color-neutral-300);
--tw-prose-td-borders: var(--color-neutral-200);
&:where(.dark, .dark *) {
--tw-prose-body: var(--color-neutral-400);
--tw-prose-headings: var(--color-neutral-200);
--tw-prose-lead: var(--color-neutral-300);
--tw-prose-links: var(--color-orange-300);
--tw-prose-bold: var(--color-neutral-300);
--tw-prose-counters: var(--color-neutral-400);
--tw-prose-bullets: var(--color-neutral-600);
--tw-prose-hr: var(--color-neutral-700);
--tw-prose-quotes: var(--color-neutral-500);
--tw-prose-quote-borders: var(--color-neutral-500);
--tw-prose-captions: var(--color-neutral-400);
--tw-prose-code: var(--color-neutral-350);
--tw-prose-pre-code: var(--color-neutral-300);
--tw-prose-th-borders: var(--color-neutral-600);
--tw-prose-td-borders: var(--color-neutral-700);
}
}
/* Shiki */
:root { :root {
--shiki-fg: var(--shiki-light); --shiki-fg: var(--shiki-light);
--shiki-bg: var(--color-neutral-200); --shiki-bg: var(--color-neutral-200);
@@ -114,32 +185,3 @@
color: var(--shiki-dark) !important; color: var(--shiki-dark) !important;
} }
} }
/* Content reveal animations */
.smooth-reveal,
.smooth-reveal-2,
.smooth-reveal-cards {
opacity: 0;
transform: translateY(20px);
transition:
opacity 0.8s ease,
transform 0.8s ease;
}
.animate-reveal {
opacity: 1 !important;
transform: translateY(0) !important;
}
.smooth-reveal-fade {
opacity: 0;
transform: translateY(0px);
transition:
opacity 1.8s ease,
transform 0.8s ease;
}
.animate-reveal-fade {
opacity: 1 !important;
transform: translateY(0) !important;
}

View File

@@ -0,0 +1,51 @@
@utility button-base {
@apply transition-all duration-300
border border-transparent
shadow-sm hover:shadow-md dark:shadow-md dark:hover:shadow-lg
px-4 py-3
}
@utility button-base-hidden {
@apply transition-all duration-300
border border-transparent
hover:bg-neutral-200 dark:hover:bg-neutral-700
p-2
}
@utility button-hover-arrow {
@apply translate-y-px transition duration-300
group-hover:translate-x-1
h-3 w-3 md:h-5 md:w-5
}
@utility button-text-title {
@apply text-neutral-200 2xl:text-base
text-sm font-bold
}
@utility button-text-title-hidden {
@apply transition-all duration-300
text-neutral-600 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-300 2xl:text-base
text-sm font-medium
}
@utility button-bg-blue {
@apply transition-all duration-300
bg-cobalt hover:bg-steel dark:bg-steel dark:hover:bg-cobalt
}
@utility button-bg-teal {
@apply transition-all duration-300
bg-bermuda hover:bg-turquoise group-hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda dark:group-hover:bg-bermuda
}
@utility button-bg-neutral {
@apply transition-all duration-300
border border-neutral-100 dark:border-stone-500/20
bg-background-card hover:bg-neutral-100 dark:hover:bg-neutral-800/90
}
@utility button-bg-gitea {
@apply transition-all duration-300
bg-gitea-primary hover:bg-gitea-secondary dark:bg-gitea-secondary dark:hover:bg-gitea-primary
}

View File

@@ -1,57 +1,3 @@
/* Button classes */
@utility button-base {
@apply transition-all duration-300
border border-transparent
shadow-sm hover:shadow-md dark:shadow-md dark:hover:shadow-lg
px-4 py-3
}
@utility button-base-hidden {
@apply transition-all duration-300
border border-transparent
hover:bg-neutral-200 dark:hover:bg-neutral-700
p-2
}
@utility button-hover-arrow {
@apply translate-y-px transition duration-300
group-hover:translate-x-1
h-3 w-3 md:h-5 md:w-5
}
@utility button-text-title {
@apply text-neutral-200 2xl:text-base
text-sm font-bold
}
@utility button-text-title-hidden {
@apply transition-all duration-300
text-neutral-600 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-300 2xl:text-base
text-sm font-medium
}
@utility button-bg-blue {
@apply transition-all duration-300
bg-cobalt hover:bg-steel dark:bg-steel dark:hover:bg-cobalt
}
@utility button-bg-teal {
@apply transition-all duration-300
bg-bermuda hover:bg-turquoise group-hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda dark:group-hover:bg-bermuda
}
@utility button-bg-neutral {
@apply transition-all duration-300
border border-neutral-100 dark:border-stone-500/20
bg-background-card hover:bg-neutral-100 dark:hover:bg-neutral-800/90
}
@utility button-bg-gitea {
@apply transition-all duration-300
bg-gitea-primary hover:bg-gitea-secondary dark:bg-gitea-secondary dark:hover:bg-gitea-primary
}
/* Card classes */
@utility card-base { @utility card-base {
@apply transition-all duration-300 @apply transition-all duration-300
rounded-xl rounded-xl
@@ -131,10 +77,3 @@
@apply transition-all duration-300 @apply transition-all duration-300
text-secondary-hover text-secondary-hover
} }
/* Misc */
@utility nav-base {
@apply border border-neutral-100 dark:border-stone-500/20
bg-neutral-100 dark:bg-neutral-800
shadow-xs dark:shadow-md
}

View File

@@ -0,0 +1,44 @@
/* Nav */
@utility nav-base {
@apply border border-neutral-100 dark:border-stone-500/20
bg-neutral-100 dark:bg-neutral-800
shadow-xs dark:shadow-md
}
/* Fade edges of a div */
@utility mask-fade-edges {
-webkit-mask-image:
linear-gradient(to right, transparent, black var(--fade-dist, 1rem), black calc(100% - var(--fade-dist, 1rem)), transparent),
linear-gradient(to bottom, transparent, black var(--fade-dist, 1rem), black calc(100% - var(--fade-dist, 1rem)), transparent);
mask-image:
linear-gradient(to right, transparent, black var(--fade-dist, 1rem), black calc(100% - var(--fade-dist, 1rem)), transparent),
linear-gradient(to bottom, transparent, black var(--fade-dist, 1rem), black calc(100% - var(--fade-dist, 1rem)), transparent);
-webkit-mask-composite: source-in;
mask-composite: intersect;
}
/* Animations */
@utility smooth-reveal {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.8s ease, transform 0.8s ease;
}
@utility smooth-reveal-2 {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.8s ease, transform 0.8s ease;
}
@utility smooth-reveal-cards {
opacity: 0;
transform: translateY(20px);
transition: opacity 0.8s ease, transform 0.8s ease;
}
@utility smooth-reveal-fade {
opacity: 0;
transform: translateY(0px);
transition: opacity 1.8s ease, transform 0.8s ease;
}

View File

@@ -1,50 +0,0 @@
// Add smooth reveal animations for content after loading
document.addEventListener('astro:page-load', () => {
const animateContent = () => {
// Animate group 1
const smoothReveal = document.querySelectorAll('.smooth-reveal');
smoothReveal.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
50 + index * 100
);
});
// Animate group 2
const smoothReveal2 = document.querySelectorAll('.smooth-reveal-2');
smoothReveal2.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
200 + index * 250
);
});
// Animate topic cards with staggered delay
const smoothRevealCards = document.querySelectorAll('.smooth-reveal-cards');
smoothRevealCards.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal');
},
400 + index * 250
);
});
// Animate with just fade in with staggered delay
const smoothRevealFade = document.querySelectorAll('.smooth-reveal-fade');
smoothRevealFade.forEach((el, index) => {
setTimeout(
() => {
el.classList.add('animate-reveal-fade');
},
100 + index * 250
);
});
};
animateContent();
});

View File

@@ -1,64 +1,6 @@
/** @type {import('tailwindcss').Config} */ /** @type {import('tailwindcss').Config} */
export default { export default {
content: [ content: ['./node_modules/preline/preline.js'],
'./src/**/*.{astro,html,js,jsx,md,mdx,svelte,ts,tsx,vue}', plugins: [require('preline/plugin')],
'./node_modules/preline/preline.js',
],
darkMode: 'class',
theme: {
extend: {
typography: ({ theme }) => ({
blog: {
css: {
'--tw-prose-body': theme('colors.neutral[700]'),
'--tw-prose-headings': theme('colors.neutral[900]'),
'--tw-prose-lead': theme('colors.neutral[700]'),
'--tw-prose-links': theme('colors.orange[300]'),
'--tw-prose-bold': theme('colors.neutral[900]'),
'--tw-prose-counters': theme('colors.neutral[600]'),
'--tw-prose-bullets': theme('colors.neutral[400]'),
'--tw-prose-hr': theme('colors.neutral[300]'),
'--tw-prose-quotes': theme('colors.neutral[500]'),
'--tw-prose-quote-borders': theme('colors.neutral[300]'),
'--tw-prose-captions': theme('colors.neutral[700]'),
'--tw-prose-code': theme('colors.neutral[700]'),
'--tw-prose-pre-code': theme('colors.neutral[900]'),
'--tw-prose-pre-bg': theme('colors.white'),
'--tw-prose-th-borders': theme('colors.neutral[300]'),
'--tw-prose-td-borders': theme('colors.neutral[200]'),
'--tw-prose-invert-body': theme('colors.neutral[400]'),
'--tw-prose-invert-headings': theme('colors.neutral[200]'),
'--tw-prose-invert-lead': theme('colors.neutral[300]'),
'--tw-prose-invert-links': theme('colors.orange[300]'),
'--tw-prose-invert-bold': theme('colors.neutral[300]'),
'--tw-prose-invert-counters': theme('colors.neutral[400]'),
'--tw-prose-invert-bullets': theme('colors.neutral[600]'),
'--tw-prose-invert-hr': theme('colors.neutral[700]'),
'--tw-prose-invert-quotes': theme('colors.neutral[500]'),
'--tw-prose-invert-quote-borders': theme('colors.neutral[500]'),
'--tw-prose-invert-captions': theme('colors.neutral[400]'),
'--tw-prose-invert-code': theme('colors.neutral[350]'),
'--tw-prose-invert-pre-code': theme('colors.neutral[300]'),
'--tw-prose-invert-th-borders': theme('colors.neutral[600]'),
'--tw-prose-invert-td-borders': theme('colors.neutral[700]'),
},
},
DEFAULT: {
css: {
blockquote: {
fontStyle: 'normal',
quotes: 'none',
},
},
},
}),
},
},
plugins: [
require('tailwindcss/nesting'),
require('preline/plugin'),
require('@tailwindcss/typography'),
],
}; };

View File

@@ -4,20 +4,19 @@
"exclude": ["dist"], "exclude": ["dist"],
"compilerOptions": { "compilerOptions": {
"strictNullChecks": true, "strictNullChecks": true,
"baseUrl": "src", "baseUrl": ".",
"jsx": "react-jsx",
"types": ["astro/client"], "types": ["astro/client"],
"paths": { "paths": {
"@/*": ["*"], "@/*": ["src/*"],
"@src/*": ["src/*"], "@src/*": ["src/*"],
"@lib/*": ["lib/*"], "@lib/*": ["src/lib/*"],
"@components/*": ["components/*"], "@components/*": ["src/components/*"],
"@content/*": ["content/*"], "@content/*": ["src/content/*"],
"@layouts/*": ["layouts/*"], "@layouts/*": ["src/layouts/*"],
"@styles/*": ["styles/*"], "@styles/*": ["src/styles/*"],
"@pages/*": ["pages/*"], "@pages/*": ["src/pages/*"],
"@support/*": ["support/*"], "@support/*": ["src/scripts/*"],
"@images/*": ["images/*"] "@images/*": ["src/images/*"]
} }
} }
} }