Compare commits

...

337 Commits

Author SHA1 Message Date
alexlebens 805cb28185 feat: add scripts to clear and run from scratch
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
alexlebens 54c82a7f79 fix: function needing paranthensis 2026-03-13 01:34:32 -05:00
alexlebens 9a62f867f1 ci: minor tweaks to args and env 2026-03-13 01:34:02 -05:00
alexlebens 0bef13c414 feat: copy package.json
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
alexlebens 0dfcc25984 feat: disable security feature
test-build / guarddog (push) Successful in 28s
test-build / build (push) Successful in 1m34s
renovate / renovate (push) Successful in 31s
2026-03-12 18:05:46 -05:00
alexlebens 4c8665ebe2 feat: use alpine-dev for bun builder
test-build / guarddog (push) Successful in 24s
renovate / renovate (push) Successful in 28s
test-build / build (push) Successful in 3m51s
2026-03-12 16:26:17 -05:00
alexlebens d083660f1c ci: make registry an arg 2026-03-12 15:44:42 -05:00
alexlebens e9a8b6de97 ci: reorder release workflow
renovate / renovate (push) Successful in 36s
2026-03-12 15:14:34 -05:00
alexlebens 578e1661cd fix: use semantic release outputs
renovate / renovate (push) Successful in 1m11s
2026-03-12 15:07:38 -05:00
alexlebens 7882c3ecc7 feat: remove old release workflows
renovate / renovate (push) Successful in 1m8s
2026-03-12 14:41:45 -05:00
alexlebens db79f912ee fix: incorrect name of step 2026-03-12 14:41:25 -05:00
alexlebens 36eaa0c132 feat: add outputs of semantic release
test-build / guarddog (push) Successful in 20s
renovate / renovate (push) Successful in 1m25s
test-build / build (push) Successful in 3m17s
2026-03-12 14:30:23 -05:00
alexlebens 24c837cf84 feat: change release format 2026-03-12 14:25:28 -05:00
alexlebens f32b75e31d feat: install deps
renovate / renovate (push) Successful in 1m10s
2026-03-12 14:20:25 -05:00
alexlebens ce75e7ca5e feat: add if to ignore harbor release result
renovate / renovate (push) Successful in 58s
2026-03-12 14:13:19 -05:00
alexlebens bf3a7ef261 feat: setup node for semantic release
renovate / renovate (push) Successful in 1m41s
2026-03-12 14:08:57 -05:00
alexlebens cae8cd3a39 chore(deps): update deps
test-build / guarddog (push) Successful in 27s
renovate / renovate (push) Successful in 1m21s
test-build / build (push) Successful in 2m40s
2026-03-12 14:02:50 -05:00
alexlebens 620b496957 feat: add semantic release 2026-03-12 14:01:14 -05:00
alexlebens 4b58117454 feat: ignore on docs updates
renovate / renovate (push) Successful in 28s
2026-03-12 13:55:39 -05:00
alexlebens 68f2080bda feat: add release branch, update and merge release workflow
test-build / guarddog (push) Successful in 50s
renovate / renovate (push) Successful in 58s
test-build / build (push) Successful in 4m4s
2026-03-12 13:54:11 -05:00
alexlebens 07fa86b17c feat: remove release-please
renovate / renovate (push) Successful in 58s
test-build / guarddog (push) Successful in 1m16s
test-build / build (push) Successful in 3m22s
2026-03-12 13:26:28 -05:00
alexlebens 1577ee4c27 feat: use different workflow for gitea
test-build / guarddog (push) Successful in 21s
renovate / renovate (push) Successful in 39s
test-build / build (push) Successful in 3m2s
release-image-harbor / release-please (push) Failing after 2m39s
release-image-gitea / release-please (push) Failing after 2m41s
release-image-harbor / build (push) Has been skipped
release-image-harbor / guarddog (push) Has been skipped
release-image-gitea / build (push) Has been skipped
release-image-gitea / guarddog (push) Has been skipped
release-image-harbor / release (push) Has been skipped
release-image-gitea / release (push) Has been skipped
2026-03-12 13:05:12 -05:00
alexlebens e0a3d391b3 feat: add token
release-image-harbor / release-please (push) Failing after 6s
release-image-harbor / build (push) Has been skipped
release-image-harbor / guarddog (push) Has been skipped
release-image-harbor / release (push) Has been skipped
renovate / renovate (push) Successful in 30s
release-image-gitea / release-please (push) Failing after 38s
release-image-gitea / build (push) Has been skipped
release-image-gitea / guarddog (push) Has been skipped
release-image-gitea / release (push) Has been skipped
test-build / guarddog (push) Successful in 58s
test-build / build (push) Successful in 1m49s
2026-03-12 13:03:26 -05:00
alexlebens 99032f7a62 feat: add automation to release using release-please in workflows
renovate / renovate (push) Successful in 38s
release-image-harbor / release-please (push) Failing after 42s
release-image-harbor / build (push) Has been skipped
release-image-harbor / guarddog (push) Has been skipped
release-image-harbor / release (push) Has been skipped
release-image-gitea / release-please (push) Failing after 1m44s
release-image-gitea / build (push) Has been skipped
release-image-gitea / guarddog (push) Has been skipped
release-image-gitea / release (push) Has been skipped
test-build / guarddog (push) Successful in 59s
test-build / build (push) Has been cancelled
2026-03-12 12:59:16 -05:00
alexlebens 03f74a8181 feat: release 3.6.0
renovate / renovate (push) Successful in 30s
test-build / guarddog (push) Successful in 1m3s
test-build / build (push) Failing after 1m10s
release-image-harbor / build (push) Failing after 1m30s
release-image-harbor / release (push) Has been skipped
release-image-gitea / build (push) Failing after 2m10s
release-image-gitea / release (push) Has been skipped
2026-03-12 12:42:45 -05:00
alexlebens 405fdf297c feat: replace timeago with dayjs
renovate / renovate (push) Successful in 49s
test-build / guarddog (push) Successful in 1m8s
test-build / build (push) Successful in 3m24s
2026-03-12 12:35:23 -05:00
alexlebens 5b6b6e479f feat: responsive for small screen 2026-03-11 22:44:11 -05:00
alexlebens 04344808bd feat: enable security feature
test-build / guarddog (push) Successful in 20s
renovate / renovate (push) Successful in 35s
test-build / build (push) Successful in 2m13s
release-image-gitea / build (push) Successful in 2m27s
release-image-gitea / release (push) Successful in 5m2s
release-image-harbor / build (push) Successful in 1m17s
release-image-harbor / release (push) Successful in 2m35s
2026-03-11 21:50:38 -05:00
alexlebens 6ec27345c3 feat: release 3.5.0 2026-03-11 19:44:49 -05:00
alexlebens 5e02443409 feat: remove security feature 2026-03-11 19:44:30 -05:00
alexlebens 8184d42942 feat: release 3.3.0
test-build / guarddog (push) Successful in 45s
renovate / renovate (push) Successful in 51s
test-build / build (push) Successful in 5m44s
release-image-gitea / build (push) Successful in 1m7s
release-image-harbor / build (push) Successful in 1m45s
release-image-gitea / release (push) Successful in 3m14s
release-image-harbor / release (push) Successful in 3m11s
2026-03-11 19:31:54 -05:00
alexlebens 04dfecc099 feat: disable security feature 2026-03-11 19:31:35 -05:00
alexlebens ec10d45fd0 feat: release 3.2.0
test-build / guarddog (push) Successful in 43s
test-build / build (push) Successful in 1m28s
release-image-gitea / build (push) Successful in 1m29s
release-image-harbor / build (push) Successful in 1m44s
release-image-gitea / release (push) Successful in 4m38s
renovate / renovate (push) Successful in 1m3s
release-image-harbor / release (push) Successful in 6m0s
2026-03-11 18:54:42 -05:00
alexlebens ceb70c7049 feat: add client:load
renovate / renovate (push) Successful in 29s
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
2026-03-11 18:54:07 -05:00
alexlebens 4dbc5d12a3 feat: remove static robots 2026-03-11 18:53:56 -05:00
alexlebens b55c3a0e31 feat: remove partytown int
test-build / guarddog (push) Successful in 17s
renovate / renovate (push) Successful in 23s
test-build / build (push) Successful in 1m25s
2026-03-11 02:04:32 -05:00
alexlebens e63abf03ef feat: remove partytown int
renovate / renovate (push) Successful in 25s
test-build / build (push) Failing after 29s
test-build / guarddog (push) Successful in 42s
2026-03-11 02:01:40 -05:00
alexlebens a7e7e5b0e8 feat: add security feature
test-build / guarddog (push) Successful in 23s
renovate / renovate (push) Successful in 43s
test-build / build (push) Failing after 1m50s
2026-03-11 01:49:07 -05:00
alexlebens 96724d0016 chore(deps): update deps 2026-03-11 01:47:26 -05:00
alexlebens 30b2e980c0 feat: change cache path
test-build / guarddog (push) Successful in 20s
renovate / renovate (push) Successful in 34s
test-build / build (push) Successful in 1m9s
2026-03-11 01:25:58 -05:00
alexlebens 113f42ca21 feat: setup node
test-build / guarddog (push) Successful in 25s
renovate / renovate (push) Successful in 59s
test-build / build (push) Successful in 1m31s
release-image-gitea / build (push) Successful in 1m33s
release-image-harbor / build (push) Successful in 1m48s
release-image-gitea / release (push) Successful in 3m53s
release-image-harbor / release (push) Successful in 4m40s
2026-03-11 01:09:20 -05:00
alexlebens 1f2820e4b4 feat: convert to bun
test-build / guarddog (push) Successful in 19s
renovate / renovate (push) Successful in 53s
test-build / build (push) Failing after 1m20s
2026-03-11 01:03:51 -05:00
alexlebens dc088306ce feat: update workflow to major version
test-build / guarddog (push) Successful in 13s
renovate / renovate (push) Successful in 40s
test-build / build (push) Successful in 1m57s
2026-03-11 00:15:25 -05:00
alexlebens f6c1cf1bf5 chore(deps): update deps
renovate / renovate (push) Successful in 30s
test-build / guarddog (push) Successful in 25s
test-build / build (push) Successful in 2m10s
2026-03-11 00:00:51 -05:00
alexlebens 962f354208 feat: release 3.0.0, major astro update to 6.0
renovate / renovate (push) Successful in 44s
test-build / guarddog (push) Successful in 1m6s
test-build / build (push) Has been cancelled
2026-03-10 23:58:53 -05:00
alexlebens bf43212afc Merge pull request 'fix(deps): update astro monorepo (major)' (#376) from renovate/major-astro-monorepo into main
test-build / guarddog (push) Successful in 21s
renovate / renovate (push) Successful in 36s
test-build / build (push) Failing after 3m19s
Reviewed-on: #376
2026-03-11 04:55:31 +00:00
renovate-bot ef810efd24 fix(deps): update astro monorepo
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 19s
test-build / build (pull_request) Successful in 1m31s
2026-03-11 04:53:28 +00:00
alexlebens b8379bbc38 feat: reduce scale effect
test-build / guarddog (push) Successful in 24s
renovate / renovate (push) Successful in 41s
test-build / build (push) Successful in 3m2s
2026-03-10 23:40:28 -05:00
alexlebens e2f5bbbe9c feat: hide cards on small screens
test-build / guarddog (push) Successful in 19s
renovate / renovate (push) Successful in 50s
test-build / build (push) Failing after 1m38s
2026-03-10 23:30:23 -05:00
alexlebens f030da549e feat: add gap to distinguish 2026-03-10 23:11:45 -05:00
alexlebens 2fbc9a764f feat: move all categories card to bottom of category section
test-build / guarddog (push) Successful in 18s
renovate / renovate (push) Successful in 32s
test-build / build (push) Successful in 1m43s
2026-03-10 23:08:43 -05:00
alexlebens 940342cc3f feat: move all posts to bottom of recent section 2026-03-10 22:57:38 -05:00
alexlebens 05d7ad6557 feat: consistent gaps and margins 2026-03-10 22:54:41 -05:00
alexlebens 31621e4f7e feat: simplify layout of the features cards 2026-03-10 22:48:17 -05:00
alexlebens 6156012c00 feat: markdown support for rss 2026-03-10 22:37:55 -05:00
alexlebens af6554985e chore(deps): update deps 2026-03-10 22:37:09 -05:00
alexlebens e25a3d0189 feat: change responsive height of image 2026-03-10 22:27:15 -05:00
alexlebens 18c2b54f65 feat: use metadata snippet for blog cards 2026-03-10 22:27:15 -05:00
alexlebens 265fd4f2cb feat: refactor how blog cards layout, add metadata, better responsiveness 2026-03-10 22:27:15 -05:00
alexlebens cc8bade886 feat: move post metadata to snippet component 2026-03-10 22:27:15 -05:00
renovate-bot c92fb72a73 Merge pull request 'chore(deps): update dependency @iconify-json/simple-icons to v1.2.73' (#375) from renovate/iconify-json-simple-icons-1.x-lockfile into main
test-build / guarddog (push) Successful in 25s
test-build / build (push) Successful in 4m16s
renovate / renovate (push) Successful in 1m29s
2026-03-10 21:24:46 +00:00
renovate-bot 5da828f5a7 chore(deps): update dependency @iconify-json/simple-icons to v1.2.73
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 31s
test-build / build (pull_request) Successful in 2m26s
2026-03-10 21:24:34 +00:00
renovate-bot f30f5cf933 Merge pull request 'chore(deps): update astro monorepo' (#374) from renovate/astro-monorepo into main
test-build / guarddog (push) Successful in 20s
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
2026-03-10 21:24:12 +00:00
renovate-bot 48231fc441 chore(deps): update astro monorepo
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 33s
test-build / build (pull_request) Successful in 6m24s
2026-03-10 21:23:54 +00:00
alexlebens d42ba08041 feat: minor tweaks
test-build / guarddog (push) Successful in 23s
renovate / renovate (push) Successful in 1m0s
test-build / build (push) Successful in 1m54s
2026-03-09 23:17:02 -05:00
alexlebens fa618b0524 feat: add margin to bring in the content 2026-03-09 23:17:02 -05:00
alexlebens 0a17e3b8af feat: remove unused properties 2026-03-09 23:17:01 -05:00
alexlebens 341453510f feat: redo layout, smaller and with logo 2026-03-09 23:17:01 -05:00
alexlebens c9cb15f201 feat: convert hero section to use randomly selected images stored in directus 2026-03-09 23:17:01 -05:00
alexlebens 74e9aff4cc Merge pull request 'chore(deps): update dependency typescript-eslint to v8.57.0' (#373) from renovate/typescript-eslint-monorepo into main
test-build / guarddog (push) Successful in 35s
test-build / build (push) Successful in 3m0s
renovate / renovate (push) Successful in 1m24s
Reviewed-on: #373
2026-03-09 22:28:39 +00:00
renovate-bot d48d61ce91 chore(deps): update dependency typescript-eslint to v8.57.0
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 32s
test-build / build (pull_request) Successful in 1m31s
2026-03-09 22:00:16 +00:00
alexlebens 68f179456a feat: reword titles and descriptions
renovate / renovate (push) Successful in 1m30s
test-build / guarddog (push) Successful in 19s
test-build / build (push) Has been cancelled
2026-03-09 16:58:34 -05:00
alexlebens 568220d39c feat: adjust layout of recent posts 2026-03-09 16:58:34 -05:00
alexlebens 9dfcf6f006 feat: make cards fixed height 2026-03-09 16:58:34 -05:00
renovate-bot 62886ba2b3 Merge pull request 'chore(deps): update dependency shiki to v4.0.2' (#372) from renovate/shiki-monorepo into main
renovate / renovate (push) Successful in 1m9s
test-build / guarddog (push) Successful in 48s
test-build / build (push) Successful in 2m2s
2026-03-09 21:18:05 +00:00
renovate-bot 90bc982371 chore(deps): update dependency shiki to v4.0.2
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 26s
test-build / build (pull_request) Successful in 2m4s
2026-03-09 21:17:55 +00:00
alexlebens e91ffd8686 feat: release 2.25.0
test-build / guarddog (push) Successful in 28s
renovate / renovate (push) Successful in 42s
release-image-harbor / build (push) Successful in 1m29s
test-build / build (push) Successful in 5m35s
release-image-harbor / release (push) Successful in 6m9s
release-image-gitea / build (push) Successful in 3m15s
release-image-gitea / release (push) Successful in 6m17s
2026-03-08 21:45:08 -05:00
alexlebens 00a86b1206 feat: remove unused files 2026-03-08 21:41:53 -05:00
alexlebens 7327795d39 feat: add an all page with cards to link to it
test-build / guarddog (push) Successful in 27s
renovate / renovate (push) Successful in 41s
test-build / build (push) Successful in 3m17s
2026-03-08 21:40:02 -05:00
alexlebens ae57c60935 feat: add photoswipe to view images embeded in posts 2026-03-08 21:40:02 -05:00
alexlebens 245e0f0624 Merge pull request 'chore(deps): update actions/cache action to v5' (#371) from renovate/actions-cache-5.x into main
test-build / build (push) Successful in 2m26s
test-build / guarddog (push) Successful in 2m34s
renovate / renovate (push) Successful in 1m21s
Reviewed-on: #371
2026-03-07 18:25:26 +00:00
renovate-bot 082afca9da chore(deps): update actions/cache action to v5
test-build / guarddog (pull_request) Successful in 3m11s
test-build / build (pull_request) Successful in 8m56s
2026-03-07 05:27:59 +00:00
alexlebens 16e14f63ef feat: enable cache
renovate / renovate (push) Successful in 6m46s
test-build / build (push) Successful in 7m51s
test-build / guarddog (push) Successful in 14m55s
2026-03-06 23:12:14 -06:00
alexlebens ce9c9c3857 feat: release 2.24.0
test-build / guarddog (push) Successful in 26s
renovate / renovate (push) Successful in 36s
test-build / build (push) Successful in 2m29s
release-image-gitea / build (push) Successful in 2m26s
release-image-harbor / build (push) Successful in 2m38s
release-image-gitea / release (push) Successful in 2m38s
release-image-harbor / release (push) Successful in 2m40s
2026-03-06 23:04:00 -06:00
alexlebens 345ab93e0c chore(deps): update deps
renovate / renovate (push) Successful in 1m32s
test-build / guarddog (push) Successful in 1m40s
test-build / build (push) Successful in 3m30s
2026-03-06 23:02:00 -06:00
alexlebens 07c7edeb0f feat: add scroll reset on navigation 2026-03-06 23:01:10 -06:00
alexlebens d3b2b40ccb feat: add gap to header above md 2026-03-06 23:01:10 -06:00
alexlebens 091af909d4 feat: add dates to selected 2026-03-06 23:01:10 -06:00
alexlebens 8a7b6b97b7 feat: use masonary style layout 2026-03-06 23:01:10 -06:00
alexlebens fe3899242a fix: change selected count 2026-03-06 23:01:10 -06:00
alexlebens e6d4e34a0a feat: improve layout for single and two images 2026-03-06 23:01:10 -06:00
alexlebens 5877086cc3 feat: remove extra spacing 2026-03-06 23:01:10 -06:00
renovate-bot 6cfc1f62a7 Merge pull request 'chore(deps): update dependency eslint to v10.0.3' (#370) from renovate/eslint-monorepo into main
renovate / renovate (push) Successful in 57s
test-build / guarddog (push) Successful in 1m27s
test-build / build (push) Successful in 2m54s
2026-03-07 01:09:34 +00:00
renovate-bot 0b452b919a chore(deps): update dependency eslint to v10.0.3
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m13s
test-build / build (pull_request) Successful in 1m40s
2026-03-07 01:09:12 +00:00
alexlebens 50f050c0b6 feat: release 2.23.0
test-build / guarddog (push) Successful in 30s
release-image-harbor / build (push) Successful in 1m8s
test-build / build (push) Successful in 2m11s
release-image-gitea / build (push) Successful in 2m55s
release-image-harbor / release (push) Successful in 2m20s
release-image-gitea / release (push) Successful in 2m21s
renovate / renovate (push) Successful in 1m39s
2026-03-06 17:30:52 -06:00
alexlebens 8b07837c0d feat: release 2.22.0
test-build / guarddog (push) Successful in 34s
release-image-gitea / build (push) Successful in 1m12s
renovate / renovate (push) Successful in 1m43s
release-image-harbor / build (push) Successful in 2m45s
test-build / build (push) Successful in 3m5s
release-image-gitea / release (push) Successful in 2m41s
release-image-harbor / release (push) Successful in 2m36s
2026-03-06 16:22:34 -06:00
alexlebens 6fca640fd8 feat: update layout's width 2026-03-06 16:22:01 -06:00
alexlebens 89fd0eb7ce feat: release 2.21.0
test-build / guarddog (push) Successful in 44s
release-image-harbor / build (push) Successful in 1m59s
renovate / renovate (push) Successful in 2m8s
test-build / build (push) Successful in 3m37s
release-image-gitea / build (push) Successful in 3m38s
release-image-harbor / release (push) Successful in 5m59s
release-image-gitea / release (push) Successful in 6m54s
2026-03-05 19:28:13 -06:00
alexlebens 95ea235f9f feat: release 2.20.1
test-build / guarddog (push) Successful in 41s
renovate / renovate (push) Successful in 1m40s
test-build / build (push) Successful in 1m52s
release-image-harbor / build (push) Successful in 2m2s
release-image-gitea / build (push) Successful in 4m38s
release-image-harbor / release (push) Successful in 8m26s
release-image-gitea / release (push) Successful in 15m39s
2026-03-05 19:12:58 -06:00
alexlebens fe6604a5d9 feat: slight optimization 2026-03-05 19:12:39 -06:00
alexlebens 2c2077053b feat: release 2.20.0
test-build / guarddog (push) Successful in 39s
renovate / renovate (push) Successful in 1m56s
test-build / build (push) Successful in 2m59s
release-image-harbor / build (push) Successful in 1m34s
release-image-harbor / release (push) Successful in 6m3s
release-image-gitea / build (push) Successful in 3m47s
release-image-gitea / release (push) Successful in 9m40s
2026-03-05 18:57:07 -06:00
alexlebens e7c660c142 feat: use many to one relationship for categories in directus 2026-03-05 18:56:44 -06:00
alexlebens f4676d151f feat: release 2.19.1
test-build / guarddog (push) Successful in 1m7s
test-build / build (push) Successful in 1m35s
release-image-harbor / build (push) Successful in 1m50s
release-image-harbor / release (push) Successful in 8m29s
release-image-gitea / build (push) Successful in 1m24s
release-image-gitea / release (push) Successful in 10m38s
renovate / renovate (push) Successful in 2m44s
2026-03-05 17:21:53 -06:00
alexlebens ed0a691442 Merge pull request 'chore(deps): update docker/setup-buildx-action action to v4' (#369) from renovate/docker-setup-buildx-action-4.x into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
Reviewed-on: #369
2026-03-05 23:21:43 +00:00
alexlebens 2aa17cd9c7 Merge pull request 'chore(deps): update docker/metadata-action action to v6' (#368) from renovate/docker-metadata-action-6.x into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
Reviewed-on: #368
2026-03-05 23:20:43 +00:00
renovate-bot 48991d411d chore(deps): update docker/setup-buildx-action action to v4
test-build / guarddog (pull_request) Successful in 30s
test-build / build (pull_request) Successful in 3m0s
2026-03-05 23:19:35 +00:00
renovate-bot 724c85332f chore(deps): update docker/metadata-action action to v6
test-build / guarddog (pull_request) Successful in 37s
test-build / build (pull_request) Successful in 1m56s
2026-03-05 23:19:29 +00:00
alexlebens bb4b9600e8 Merge pull request 'chore(deps): update docker/build-push-action action to v7' (#367) from renovate/docker-build-push-action-7.x into main
renovate / renovate (push) Successful in 1m20s
test-build / guarddog (push) Successful in 1m17s
test-build / build (push) Has been cancelled
Reviewed-on: #367
2026-03-05 23:18:08 +00:00
alexlebens 42c24882ff Merge pull request 'chore(deps): update dependency @directus/sdk to v21.2.0' (#366) from renovate/directus-sdk-21.x-lockfile into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
Reviewed-on: #366
2026-03-05 23:16:21 +00:00
renovate-bot daef38d80d chore(deps): update docker/build-push-action action to v7
test-build / guarddog (pull_request) Successful in 42s
test-build / build (pull_request) Successful in 1m49s
2026-03-05 23:15:44 +00:00
renovate-bot e4de55fab3 chore(deps): update dependency @directus/sdk to v21.2.0
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m1s
test-build / build (pull_request) Successful in 2m51s
2026-03-05 23:15:28 +00:00
alexlebens afa1f36285 Merge pull request 'chore(deps): update docker/login-action action to v4' (#364) from renovate/docker-login-action-4.x into main
test-build / guarddog (push) Successful in 45s
test-build / build (push) Successful in 1m59s
renovate / renovate (push) Successful in 2m28s
Reviewed-on: #364
2026-03-05 23:13:36 +00:00
renovate-bot 93df739ea1 Merge pull request 'chore(deps): update dependency marked to v17.0.4' (#365) from renovate/marked-17.x-lockfile into main
renovate / renovate (push) Successful in 1m27s
test-build / guarddog (push) Successful in 26s
test-build / build (push) Successful in 2m32s
2026-03-05 23:00:38 +00:00
renovate-bot 667950f053 chore(deps): update dependency marked to v17.0.4
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 51s
test-build / build (pull_request) Successful in 5m43s
2026-03-05 23:00:14 +00:00
renovate-bot 14402954f5 chore(deps): update docker/login-action action to v4
test-build / guarddog (pull_request) Successful in 50s
test-build / build (pull_request) Successful in 2m11s
2026-03-05 00:03:11 +00:00
alexlebens 6ddc382dac feat: release 2.19.0
test-build / build (push) Successful in 4m49s
test-build / guarddog (push) Successful in 13m7s
release-image-gitea / build (push) Successful in 6m0s
release-image-harbor / build (push) Successful in 6m25s
release-image-gitea / release (push) Successful in 8m10s
release-image-harbor / release (push) Successful in 14m46s
renovate / renovate (push) Successful in 1m52s
2026-03-03 14:43:48 -06:00
alexlebens 3eae720221 feat: shorten transition time 2026-03-03 14:00:40 -06:00
alexlebens f984a1f759 feat: add dark mode logo 2026-03-03 13:55:25 -06:00
alexlebens 62066c6f3b feat: release 2.18.1
test-build / guarddog (push) Successful in 2m20s
test-build / build (push) Successful in 3m20s
renovate / renovate (push) Successful in 9m38s
release-image-gitea / build (push) Successful in 15m42s
release-image-gitea / release (push) Failing after 29s
release-image-harbor / build (push) Successful in 11m25s
release-image-harbor / release (push) Failing after 13m15s
2026-03-02 22:21:18 -06:00
alexlebens 97b1fa0316 feat: add dark mode swap to logo 2026-03-02 22:20:36 -06:00
alexlebens 7f31880b51 chore(deps): update deps
test-build / guarddog (push) Successful in 43s
renovate / renovate (push) Successful in 1m5s
test-build / build (push) Successful in 2m40s
release-image-harbor / build (push) Successful in 5m34s
release-image-harbor / release (push) Failing after 4m11s
release-image-gitea / build (push) Successful in 2m50s
release-image-gitea / release (push) Failing after 24m58s
2026-03-02 21:40:10 -06:00
alexlebens ddbcb33812 feat: release 2.18.0
renovate / renovate (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
test-build / build (push) Has been cancelled
2026-03-02 21:39:40 -06:00
renovate-bot 4e9ca2759a Merge pull request 'chore(deps): update dependency shiki to v4.0.1' (#363) from renovate/shiki-monorepo into main
test-build / guarddog (push) Successful in 34s
test-build / build (push) Successful in 1m39s
renovate / renovate (push) Successful in 1m59s
2026-03-02 22:09:53 +00:00
renovate-bot eae55da29d chore(deps): update dependency shiki to v4.0.1
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 34s
test-build / build (pull_request) Successful in 1m47s
2026-03-02 22:09:43 +00:00
renovate-bot b40a58a4e0 Merge pull request 'chore(deps): update dependency @iconify-json/simple-icons to v1.2.72' (#362) from renovate/iconify-json-simple-icons-1.x-lockfile into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
2026-03-02 22:09:31 +00:00
renovate-bot 157a7bcdc5 chore(deps): update dependency @iconify-json/simple-icons to v1.2.72
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m14s
test-build / build (pull_request) Successful in 2m48s
2026-03-02 22:09:23 +00:00
alexlebens 469d9ba3f7 feat: release 2.17.2
test-build / guarddog (push) Successful in 1m13s
renovate / renovate (push) Successful in 1m28s
test-build / build (push) Successful in 1m41s
release-image-gitea / build (push) Successful in 1m57s
release-image-harbor / build (push) Successful in 1m57s
release-image-harbor / release (push) Successful in 4m40s
release-image-gitea / release (push) Successful in 5m44s
2026-03-02 15:54:42 -06:00
alexlebens 8c5488fad5 feat: use slate for accent 2026-03-02 15:53:29 -06:00
alexlebens eff067a743 Merge pull request 'fix(deps): update dependency shiki to v4' (#361) from renovate/major-shiki-monorepo into main
test-build / guarddog (push) Successful in 39s
test-build / build (push) Successful in 3m3s
renovate / renovate (push) Successful in 1m49s
Reviewed-on: #361
2026-03-01 03:14:06 +00:00
renovate-bot da28e5b50e fix(deps): update dependency shiki to v4
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 31s
test-build / build (pull_request) Successful in 2m36s
2026-02-28 18:11:16 +00:00
renovate-bot f376f2e1e4 Merge pull request 'chore(deps): update dependency preline to v4.1.2' (#360) from renovate/preline-4.x-lockfile into main
test-build / guarddog (push) Successful in 57s
test-build / build (push) Successful in 2m30s
renovate / renovate (push) Successful in 1m36s
2026-02-28 18:08:14 +00:00
renovate-bot a1cfa4ef24 chore(deps): update dependency preline to v4.1.2
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 42s
test-build / build (pull_request) Successful in 3m19s
2026-02-28 18:07:57 +00:00
alexlebens cc5e975ea6 feat: release 2.17.1
test-build / guarddog (push) Successful in 1m5s
release-image-harbor / build (push) Successful in 2m44s
release-image-gitea / build (push) Successful in 2m48s
test-build / build (push) Successful in 4m40s
release-image-gitea / release (push) Successful in 8m7s
release-image-harbor / release (push) Successful in 8m15s
renovate / renovate (push) Successful in 2m7s
2026-02-26 21:23:28 -06:00
alexlebens ea15224eae feat: tweaks to background to be more pastel 2026-02-26 21:23:08 -06:00
alexlebens d19433ae4e feat: release 2.17.0
test-build / guarddog (push) Successful in 33s
release-image-harbor / build (push) Successful in 1m27s
test-build / build (push) Successful in 5m25s
release-image-harbor / release (push) Successful in 5m16s
release-image-gitea / build (push) Successful in 1m28s
release-image-gitea / release (push) Successful in 6m29s
renovate / renovate (push) Successful in 1m6s
2026-02-26 17:02:25 -06:00
alexlebens 7498870d92 feat: adjust height of fade effect
test-build / guarddog (push) Successful in 1m26s
renovate / renovate (push) Successful in 1m30s
test-build / build (push) Successful in 1m56s
2026-02-26 16:15:18 -06:00
alexlebens d7d43369dc feat: add logos to category header pages 2026-02-26 16:01:02 -06:00
alexlebens 5b94283498 feat: add category logo to blog page 2026-02-26 15:53:40 -06:00
alexlebens 4c1da43c68 feat: add logos to category cards 2026-02-26 15:47:26 -06:00
alexlebens 6cddae61ed feat: add thanks for Icons8 2026-02-26 15:47:12 -06:00
alexlebens 890dbdf313 feat: add additional layout for small screens 2026-02-26 15:46:59 -06:00
alexlebens 6dd2209e21 chore(deps): update deps
test-build / guarddog (push) Successful in 1m40s
renovate / renovate (push) Successful in 1m43s
test-build / build (push) Successful in 2m39s
2026-02-26 15:01:24 -06:00
alexlebens 93bf44f89a feat: move categories to directus 2026-02-26 15:00:52 -06:00
alexlebens 734e9cacae feat: remove emoji 2026-02-26 14:11:42 -06:00
alexlebens c69eb58a49 feat: release 2.16.0
test-build / guarddog (push) Successful in 53s
renovate / renovate (push) Successful in 1m42s
release-image-gitea / build (push) Successful in 3m1s
release-image-harbor / build (push) Successful in 2m55s
test-build / build (push) Successful in 4m38s
release-image-harbor / release (push) Successful in 5m39s
release-image-gitea / release (push) Successful in 9m19s
2026-02-25 20:02:41 -06:00
alexlebens 543b57647b chore(deps): update node 2026-02-25 20:02:13 -06:00
alexlebens 01cbfab2f7 Merge pull request 'chore(deps): update dependency node to v24.14.0' (#354) from renovate/node-24.x into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
Reviewed-on: #354
2026-02-26 02:01:34 +00:00
alexlebens b4ecac7396 Merge pull request 'chore(deps): update dependency shiki to v3.23.0' (#359) from renovate/shiki-monorepo into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
Reviewed-on: #359
2026-02-26 02:01:10 +00:00
alexlebens 1a51ccb516 Merge pull request 'chore(deps): update dependency astro to v5.18.0' (#358) from renovate/astro-monorepo into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
Reviewed-on: #358
2026-02-26 02:00:44 +00:00
alexlebens 522cd20fe3 chore(deps): update preline
renovate / renovate (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
test-build / build (push) Has been cancelled
2026-02-25 19:57:11 -06:00
renovate-bot d8bad121f6 chore(deps): update dependency shiki to v3.23.0
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 2m0s
test-build / build (pull_request) Successful in 3m20s
2026-02-26 01:56:28 +00:00
renovate-bot d6176bd89d chore(deps): update dependency node to v24.14.0
test-build / guarddog (pull_request) Successful in 1m33s
test-build / build (pull_request) Successful in 3m35s
2026-02-26 01:54:37 +00:00
renovate-bot 594ff43110 chore(deps): update dependency astro to v5.18.0
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m8s
test-build / build (pull_request) Successful in 3m54s
2026-02-26 01:54:20 +00:00
alexlebens 7fd443535a Merge pull request 'chore(deps): update dependency eslint-plugin-format to v2' (#356) from renovate/eslint-plugin-format-2.x into main
test-build / guarddog (push) Successful in 40s
test-build / build (push) Successful in 3m37s
renovate / renovate (push) Has been cancelled
Reviewed-on: #356
2026-02-26 01:52:55 +00:00
renovate-bot f8d926199f chore(deps): update dependency eslint-plugin-format to v2
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m17s
test-build / build (pull_request) Successful in 3m3s
2026-02-26 01:47:46 +00:00
alexlebens 8c57f970d6 Merge pull request 'chore(deps): update dependency eslint-plugin-format to v1.5.0' (#353) from renovate/eslint-plugin-format-1.x-lockfile into main
test-build / guarddog (push) Successful in 47s
renovate / renovate (push) Successful in 3m12s
test-build / build (push) Successful in 3m23s
Reviewed-on: #353
2026-02-26 01:44:57 +00:00
alexlebens 779946cfec Merge pull request 'chore(deps): update tailwindcss monorepo to v4.2.1' (#351) from renovate/tailwindcss-monorepo into main
renovate / renovate (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #351
2026-02-26 01:44:02 +00:00
alexlebens 1f926d4184 Merge pull request 'chore(deps): update dependency typescript-eslint to v8.56.1' (#350) from renovate/typescript-eslint-monorepo into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
Reviewed-on: #350
2026-02-26 01:43:43 +00:00
alexlebens f99638ffe2 Merge pull request 'chore(deps): update dependency eslint-plugin-react-refresh to v0.5.2' (#349) from renovate/eslint-plugin-react-refresh-0.x-lockfile into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has started running
test-build / guarddog (push) Has been cancelled
Reviewed-on: #349
2026-02-26 01:43:33 +00:00
renovate-bot eda954542b chore(deps): update dependency eslint-plugin-format to v1.5.0
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m32s
test-build / build (pull_request) Failing after 3m29s
2026-02-26 00:07:59 +00:00
renovate-bot af79a0d4c4 chore(deps): update dependency typescript-eslint to v8.56.1
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m35s
test-build / build (pull_request) Successful in 3m29s
2026-02-26 00:05:56 +00:00
renovate-bot 466cbd790e chore(deps): update tailwindcss monorepo to v4.2.1
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m1s
test-build / build (pull_request) Failing after 2m28s
2026-02-25 00:10:11 +00:00
renovate-bot 46c69d30aa chore(deps): update dependency eslint-plugin-react-refresh to v0.5.2
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m30s
test-build / build (pull_request) Successful in 3m35s
2026-02-25 00:08:18 +00:00
renovate-bot 9dad4aa5bb Merge pull request 'chore(deps): update dependency eslint to v10.0.2' (#348) from renovate/eslint-monorepo into main
test-build / build (push) Successful in 2m42s
test-build / guarddog (push) Successful in 2m37s
renovate / renovate (push) Successful in 11m0s
2026-02-25 00:03:13 +00:00
renovate-bot 08ece5b478 chore(deps): update dependency eslint to v10.0.2
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m57s
test-build / build (pull_request) Successful in 4m56s
2026-02-25 00:02:41 +00:00
renovate-bot ac38bafb9b Merge pull request 'chore(deps): update dependency eslint to v10.0.1' (#347) from renovate/eslint-monorepo into main
test-build / build (push) Successful in 1m18s
test-build / guarddog (push) Successful in 2m26s
renovate / renovate (push) Successful in 6m30s
2026-02-21 20:19:56 +00:00
renovate-bot f08b754adc chore(deps): update dependency eslint to v10.0.1
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 28s
test-build / build (pull_request) Successful in 54s
2026-02-21 20:19:31 +00:00
alexlebens c5cda006bb feat: release 2.15.1
test-build / guarddog (push) Successful in 53s
test-build / build (push) Successful in 1m1s
release-image-harbor / build (push) Successful in 2m19s
release-image-gitea / build (push) Successful in 2m21s
release-image-gitea / release (push) Successful in 2m5s
release-image-harbor / release (push) Successful in 7m38s
renovate / renovate (push) Successful in 2m51s
2026-02-20 00:43:52 -06:00
alexlebens 959d3bd71d fix: force 3d scaling for button transform
test-build / guarddog (push) Successful in 18s
renovate / renovate (push) Successful in 1m11s
test-build / build (push) Successful in 1m55s
2026-02-20 00:39:53 -06:00
alexlebens f3b8d10106 feat: release 2.15.0
test-build / guarddog (push) Successful in 27s
test-build / build (push) Successful in 52s
renovate / renovate (push) Successful in 1m41s
release-image-harbor / build (push) Successful in 3m25s
release-image-harbor / release (push) Successful in 2m3s
release-image-gitea / build (push) Successful in 5m44s
release-image-gitea / release (push) Successful in 1m41s
2026-02-19 23:51:20 -06:00
alexlebens 0c63c6bef4 feat: use mask to blend content to background
test-build / guarddog (push) Successful in 31s
test-build / build (push) Successful in 1m7s
renovate / renovate (push) Successful in 1m37s
2026-02-19 23:48:34 -06:00
alexlebens 5e37e2bb53 feat: add background shimmer effect, use mask for content scroll fade 2026-02-19 23:34:56 -06:00
alexlebens b3c377f62d feat: adjustment pass on spacing between sections
renovate / renovate (push) Successful in 1m32s
test-build / build (push) Successful in 3m22s
test-build / guarddog (push) Successful in 4m3s
2026-02-19 18:14:44 -06:00
alexlebens 0d87af3aca fix: hidden button background color on light mode, darken
renovate / renovate (push) Successful in 39s
test-build / guarddog (push) Successful in 50s
test-build / build (push) Successful in 4m34s
2026-02-19 18:02:31 -06:00
alexlebens 9eb0f37cb2 fix: fix footer accent color 2026-02-19 18:00:33 -06:00
alexlebens 76dfef4177 feat: redo how images, icons, and logos are handled 2026-02-19 17:58:28 -06:00
alexlebens d415dda661 feat: release 2.14.1
release-image-gitea / build (push) Successful in 56s
release-image-gitea / release (push) Successful in 1m50s
test-build / build (push) Successful in 4m52s
release-image-harbor / build (push) Successful in 4m57s
test-build / guarddog (push) Successful in 5m25s
release-image-harbor / release (push) Successful in 3m17s
renovate / renovate (push) Successful in 4m2s
2026-02-18 22:42:56 -06:00
alexlebens ea9ae016d7 fix: add env 2026-02-18 22:42:38 -06:00
alexlebens 0416ab7f9e feat: release 2.14.0
test-build / guarddog (push) Successful in 30s
release-image-gitea / build (push) Successful in 48s
renovate / renovate (push) Successful in 2m21s
release-image-gitea / release (push) Successful in 1m57s
test-build / build (push) Successful in 4m7s
release-image-harbor / build (push) Successful in 6m46s
release-image-harbor / release (push) Successful in 4m20s
2026-02-18 21:48:17 -06:00
alexlebens 6f1728a909 feat: move url configuration to support file 2026-02-18 21:47:53 -06:00
alexlebens db2711d878 feat: release 2.13.1
test-build / guarddog (push) Successful in 30s
test-build / build (push) Successful in 59s
release-image-gitea / build (push) Successful in 50s
renovate / renovate (push) Successful in 2m26s
release-image-gitea / release (push) Successful in 1m52s
release-image-harbor / build (push) Successful in 6m14s
release-image-harbor / release (push) Successful in 11m50s
2026-02-18 21:26:50 -06:00
alexlebens 7f2a27248a feat: improve behavior of showmore, fix alignment 2026-02-18 21:26:23 -06:00
alexlebens c927235a5a fix: info logs
test-build / guarddog (push) Successful in 49s
test-build / build (push) Successful in 2m47s
release-image-harbor / build (push) Successful in 2m55s
release-image-harbor / release (push) Successful in 5m39s
release-image-gitea / build (push) Successful in 2m11s
release-image-gitea / release (push) Successful in 3m51s
renovate / renovate (push) Successful in 1m5s
2026-02-18 15:57:34 -06:00
alexlebens 8d5c02e2d1 fix: debug logs
test-build / guarddog (push) Successful in 47s
renovate / renovate (push) Successful in 1m21s
test-build / build (push) Successful in 1m35s
2026-02-18 15:54:28 -06:00
alexlebens 1a34b932b0 fix: correct credentials
test-build / guarddog (push) Successful in 44s
renovate / renovate (push) Successful in 59s
test-build / build (push) Has been cancelled
2026-02-18 15:52:25 -06:00
alexlebens 882063ea43 fix: correct matchhost
test-build / guarddog (push) Successful in 52s
renovate / renovate (push) Successful in 59s
test-build / build (push) Successful in 1m18s
2026-02-18 15:41:20 -06:00
alexlebens ba2477e7af fix: move host rules to workflow
test-build / guarddog (push) Successful in 57s
test-build / build (push) Successful in 1m20s
renovate / renovate (push) Successful in 1m22s
2026-02-18 15:38:49 -06:00
alexlebens 879786484d feat: add creds for dhi
test-build / guarddog (push) Successful in 38s
renovate / renovate (push) Successful in 57s
test-build / build (push) Successful in 2m11s
2026-02-18 15:33:03 -06:00
alexlebens 2c9486f687 feat: release 2.13.0 2026-02-18 15:23:41 -06:00
alexlebens ba73c1b24f fix: add remote patterns for images
test-build / guarddog (push) Successful in 1m20s
renovate / renovate (push) Successful in 1m43s
test-build / build (push) Successful in 2m6s
2026-02-18 15:22:35 -06:00
alexlebens 44bd1e4810 feat: change selected blogs to switch to card form on small screens 2026-02-18 15:07:34 -06:00
alexlebens e52d85f931 feat: refactor pass along pages 2026-02-18 15:07:34 -06:00
alexlebens 21085a1620 feat: organize to consistency 2026-02-18 15:07:34 -06:00
alexlebens 744e72efc9 feat: update robots.txt 2026-02-18 15:07:34 -06:00
alexlebens 62dd636d4e feat: organize to consistency 2026-02-18 15:07:34 -06:00
alexlebens b4d03a286c Merge pull request 'chore(deps): update tailwindcss monorepo to v4.2.0' (#344) from renovate/tailwindcss-monorepo into main
renovate / renovate (push) Successful in 1m11s
test-build / build (push) Failing after 1m23s
test-build / guarddog (push) Successful in 1m37s
Reviewed-on: #344
2026-02-18 21:00:24 +00:00
renovate-bot 442da55d5d Merge pull request 'chore(deps): update astro monorepo' (#345) from renovate/astro-monorepo into main
renovate / renovate (push) Successful in 1m12s
test-build / guarddog (push) Successful in 55s
test-build / build (push) Failing after 1m20s
2026-02-18 20:57:05 +00:00
renovate-bot 9b9c982f92 chore(deps): update astro monorepo
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 49s
test-build / build (pull_request) Failing after 1m16s
2026-02-18 20:56:41 +00:00
renovate-bot 1820650ada chore(deps): update tailwindcss monorepo to v4.2.0
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 37s
test-build / build (pull_request) Successful in 2m44s
2026-02-18 20:40:14 +00:00
renovate-bot fa2245e939 Merge pull request 'chore(deps): update dependency marked to v17.0.3' (#343) from renovate/marked-17.x-lockfile into main
test-build / guarddog (push) Successful in 39s
renovate / renovate (push) Successful in 1m14s
test-build / build (push) Successful in 1m15s
2026-02-18 04:37:31 +00:00
renovate-bot 12a8363dd2 chore(deps): update dependency marked to v17.0.3
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 52s
test-build / build (pull_request) Successful in 1m14s
2026-02-18 04:37:19 +00:00
renovate-bot 4f365a4e60 Merge pull request 'chore(deps): update dependency @iconify-json/simple-icons to v1.2.71' (#342) from renovate/iconify-json-simple-icons-1.x-lockfile into main
test-build / guarddog (push) Successful in 31s
test-build / build (push) Successful in 1m38s
renovate / renovate (push) Has been cancelled
2026-02-18 04:35:15 +00:00
renovate-bot 12e74d29af chore(deps): update dependency @iconify-json/simple-icons to v1.2.71
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 37s
test-build / build (pull_request) Successful in 1m12s
2026-02-18 04:35:06 +00:00
alexlebens 7937090533 Merge pull request 'chore(deps): update dependency typescript-eslint to v8.56.0' (#341) from renovate/typescript-eslint-monorepo into main
test-build / guarddog (push) Successful in 44s
test-build / build (push) Successful in 1m10s
renovate / renovate (push) Has been cancelled
Reviewed-on: #341
2026-02-18 04:33:53 +00:00
renovate-bot ebfd8cf4a7 chore(deps): update dependency typescript-eslint to v8.56.0
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 30s
test-build / build (pull_request) Successful in 1m34s
2026-02-18 04:23:07 +00:00
alexlebens 8270728e8f feat: organize layout to consistency
test-build / guarddog (push) Successful in 31s
test-build / build (push) Successful in 1m29s
renovate / renovate (push) Successful in 1m33s
2026-02-17 22:21:45 -06:00
alexlebens 20d8c7323f feat: tweak to gradient 2026-02-17 22:09:53 -06:00
alexlebens 5ac23f08a4 feat: improve navbar, add opacity fade beneath, layout, and refactor
test-build / guarddog (push) Successful in 31s
test-build / build (push) Successful in 2m0s
renovate / renovate (push) Successful in 2m7s
2026-02-17 21:49:51 -06:00
alexlebens c6f3179efb feat: organize footer to consistency 2026-02-17 17:44:40 -06:00
alexlebens 1a8473b964 feat: release 2.12.0
test-build / guarddog (push) Successful in 1m11s
release-image-gitea / build (push) Successful in 2m28s
release-image-harbor / build (push) Successful in 2m19s
test-build / build (push) Successful in 5m33s
release-image-gitea / release (push) Successful in 7m39s
release-image-harbor / release (push) Successful in 7m16s
renovate / renovate (push) Successful in 2m5s
2026-02-16 23:08:06 -06:00
alexlebens 18211ad485 feat: update BaseHead
renovate / renovate (push) Successful in 1m33s
test-build / build (push) Successful in 1m40s
test-build / guarddog (push) Successful in 1m59s
2026-02-16 23:04:42 -06:00
alexlebens 429cf94023 feat: organize to consistency pass on sections 2026-02-16 22:57:39 -06:00
alexlebens 0497731c45 feat: organize to consistency 2026-02-16 22:38:45 -06:00
alexlebens 6c2c6da91d feat: organize to consistency 2026-02-16 22:36:24 -06:00
alexlebens 19e17ea947 feat: remove option 2026-02-16 22:34:57 -06:00
alexlebens 3d9120c570 fix: remove unused property 2026-02-16 22:34:14 -06:00
alexlebens 875b8a7f47 fix: remove border from blog cards 2026-02-16 22:32:12 -06:00
alexlebens 1ddc76ae69 fix: remove errant semicolon 2026-02-16 22:30:04 -06:00
alexlebens 6423ffba63 feat: refactor blog components 2026-02-16 22:26:53 -06:00
alexlebens 505670dbf8 feat: remove unused packages
test-build / guarddog (push) Successful in 34s
test-build / build (push) Successful in 1m6s
release-image-harbor / build (push) Successful in 1m1s
release-image-harbor / release (push) Successful in 2m27s
release-image-gitea / build (push) Successful in 1m0s
release-image-gitea / release (push) Successful in 6m21s
renovate / renovate (push) Has started running
2026-02-16 00:28:58 -06:00
alexlebens b3d7e7af2b chore(deps): update deps
test-build / guarddog (push) Successful in 31s
renovate / renovate (push) Successful in 1m0s
test-build / build (push) Successful in 1m24s
2026-02-16 00:21:43 -06:00
alexlebens 440c95224d feat: release 2.11.0 2026-02-16 00:20:26 -06:00
alexlebens b9ee82e9d8 Merge pull request 'chore(deps): update dependency eslint-plugin-astro to v1.6.0' (#340) from renovate/eslint-plugin-astro-1.x-lockfile into main
test-build / guarddog (push) Successful in 33s
renovate / renovate (push) Successful in 49s
test-build / build (push) Successful in 1m26s
Reviewed-on: #340
2026-02-16 06:20:15 +00:00
renovate-bot 3af9f08b7c chore(deps): update dependency eslint-plugin-astro to v1.6.0
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 1m20s
test-build / build (pull_request) Successful in 2m18s
2026-02-16 06:09:50 +00:00
alexlebens 0bd56b172f Merge pull request 'chore(deps): update dependency @swup/astro to v1.8.0' (#339) from renovate/swup-astro-1.x-lockfile into main
test-build / guarddog (push) Successful in 54s
renovate / renovate (push) Successful in 1m11s
test-build / build (push) Successful in 2m34s
Reviewed-on: #339
2026-02-16 06:08:50 +00:00
renovate-bot ebf70bd747 chore(deps): update dependency @swup/astro to v1.8.0
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 38s
test-build / build (pull_request) Successful in 1m37s
2026-02-16 05:57:02 +00:00
alexlebens 9c5e9b6a5b Merge pull request 'chore(deps): update dependency @eslint-react/eslint-plugin to v2.13.0' (#338) from renovate/eslint-react-eslint-plugin-2.x-lockfile into main
test-build / guarddog (push) Successful in 51s
test-build / build (push) Successful in 1m42s
renovate / renovate (push) Successful in 2m17s
Reviewed-on: #338
2026-02-16 05:55:20 +00:00
renovate-bot 568f9e5164 chore(deps): update dependency @eslint-react/eslint-plugin to v2.13.0
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 43s
test-build / build (pull_request) Successful in 1m50s
2026-02-16 05:40:13 +00:00
alexlebens a74cc775d0 feat: final refactor of sections
test-build / guarddog (push) Successful in 35s
test-build / build (push) Successful in 1m1s
renovate / renovate (push) Successful in 2m15s
2026-02-15 23:38:55 -06:00
alexlebens 5271be52a2 feat: rename button components to include button in name for consistency 2026-02-15 22:05:36 -06:00
alexlebens 8a649b7647 feat: imporvement pass over sections 2026-02-15 15:42:27 -06:00
alexlebens c4be4653be fix: run theme on page swap 2026-02-15 00:05:16 -06:00
alexlebens 47a637353c feat: move improved components out of ui folder 2026-02-14 23:10:43 -06:00
alexlebens a09a4ee240 feat: imporve theme toggle button 2026-02-14 23:08:12 -06:00
alexlebens 342ae8900a feat: refactor buttons, except for theme 2026-02-14 22:09:49 -06:00
alexlebens 2cdef1a553 feat: release 2.10.1
test-build / guarddog (push) Successful in 45s
test-build / build (push) Successful in 1m17s
release-image-gitea / build (push) Successful in 1m19s
release-image-harbor / build (push) Successful in 3m18s
release-image-gitea / release (push) Successful in 4m4s
release-image-harbor / release (push) Successful in 2m30s
renovate / renovate (push) Successful in 4m22s
2026-02-14 17:19:58 -06:00
alexlebens a8d6446674 feat: add docker login
test-build / guarddog (push) Successful in 1m47s
renovate / renovate (push) Successful in 3m1s
test-build / build (push) Successful in 3m21s
2026-02-14 17:09:33 -06:00
alexlebens fcd3057f40 feat: release 2.10.0
test-build / guarddog (push) Successful in 1m52s
renovate / renovate (push) Successful in 2m6s
test-build / build (push) Successful in 3m18s
release-image-gitea / build (push) Successful in 1m26s
release-image-harbor / build (push) Successful in 1m28s
release-image-gitea / release (push) Failing after 1m49s
release-image-harbor / release (push) Failing after 2m1s
2026-02-14 16:53:35 -06:00
alexlebens d464f0fe43 feat: use hardened image 2026-02-14 16:52:54 -06:00
alexlebens 0f403fa274 feat: release 2.9.0
renovate / renovate (push) Successful in 1m31s
test-build / guarddog (push) Successful in 36s
test-build / build (push) Successful in 2m19s
release-image-gitea / build (push) Successful in 1m6s
release-image-harbor / build (push) Successful in 2m37s
release-image-gitea / release (push) Successful in 4m51s
release-image-harbor / release (push) Successful in 4m10s
2026-02-14 01:22:40 -06:00
alexlebens 0fc359a973 feat: scale logos
test-build / guarddog (push) Successful in 35s
test-build / build (push) Successful in 1m12s
renovate / renovate (push) Successful in 1m24s
2026-02-14 01:04:56 -06:00
alexlebens 104fe35ee8 feat: major refactor of cards to standardize styles 2026-02-14 00:55:43 -06:00
alexlebens a57f43e082 feat: release 2.8.0
test-build / guarddog (push) Successful in 42s
test-build / build (push) Successful in 1m17s
release-image-harbor / build (push) Successful in 1m19s
release-image-harbor / release (push) Successful in 5m58s
release-image-gitea / build (push) Successful in 1m32s
release-image-gitea / release (push) Successful in 4m28s
renovate / renovate (push) Successful in 1m44s
2026-02-13 14:30:59 -06:00
alexlebens efad6c30d1 feat: add rybbit tracking 2026-02-13 14:30:40 -06:00
alexlebens c2d26228ba Merge pull request 'chore(deps): update node.js to v24.13.1' (#337) from renovate/docker.io-node-24.x into main
test-build / build (push) Successful in 1m13s
renovate / renovate (push) Successful in 2m44s
test-build / guarddog (push) Successful in 3m27s
Reviewed-on: #337
2026-02-13 19:03:25 +00:00
renovate-bot 94fe56022d chore(deps): update node.js to v24.13.1
test-build / guarddog (pull_request) Successful in 50s
test-build / build (pull_request) Successful in 3m45s
2026-02-13 00:02:18 +00:00
alexlebens d171292dd2 chore(deps): update deps
test-build / guarddog (push) Successful in 38s
test-build / build (push) Successful in 1m15s
release-image-harbor / build (push) Successful in 1m14s
release-image-gitea / build (push) Successful in 1m20s
release-image-gitea / release (push) Successful in 4m12s
release-image-harbor / release (push) Successful in 4m20s
renovate / renovate (push) Successful in 2m28s
2026-02-11 15:37:05 -06:00
renovate-bot f52d285013 Merge pull request 'chore(deps): update dependency marked to v17.0.2' (#336) from renovate/marked-17.x-lockfile into main
renovate / renovate (push) Successful in 49s
test-build / build (push) Successful in 1m22s
test-build / guarddog (push) Successful in 1m58s
2026-02-11 21:32:43 +00:00
renovate-bot a79f53e90c chore(deps): update dependency marked to v17.0.2
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 1m39s
test-build / guarddog (pull_request) Successful in 1m55s
2026-02-11 21:32:29 +00:00
renovate-bot 5ad7e33c8a Merge pull request 'chore(deps): update dependency @types/react to v19.2.14' (#335) from renovate/react-monorepo into main
renovate / renovate (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
test-build / build (push) Has been cancelled
2026-02-11 21:32:12 +00:00
renovate-bot 87f266a3e2 chore(deps): update dependency @types/react to v19.2.14
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 37s
test-build / build (pull_request) Successful in 1m33s
2026-02-11 21:32:02 +00:00
renovate-bot dc039046fe Merge pull request 'chore(deps): update astro monorepo' (#334) from renovate/astro-monorepo into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / guarddog (push) Has been cancelled
2026-02-11 21:31:47 +00:00
renovate-bot 9c53f37b39 chore(deps): update astro monorepo
renovate/stability-days Updates have not met minimum release age requirement
test-build / guarddog (pull_request) Successful in 42s
test-build / build (pull_request) Successful in 1m23s
2026-02-11 21:31:05 +00:00
alexlebens 093e1e2ccb fix: remove argument
test-build / guarddog (push) Successful in 32s
renovate / renovate (push) Successful in 54s
test-build / build (push) Successful in 1m28s
2026-02-11 15:24:39 -06:00
alexlebens 7a77f0d2d2 fix: downgrade python
renovate / renovate (push) Successful in 1m0s
test-build / build (push) Successful in 1m14s
test-build / guarddog (push) Failing after 1m27s
2026-02-11 15:21:44 -06:00
alexlebens e29631c4af fix: install and run
renovate / renovate (push) Successful in 58s
test-build / build (push) Successful in 1m18s
test-build / guarddog (push) Failing after 2m4s
2026-02-11 15:19:19 -06:00
alexlebens 31aad5511f fix: only binary
test-build / guarddog (push) Failing after 30s
renovate / renovate (push) Successful in 1m2s
test-build / build (push) Successful in 1m27s
2026-02-11 15:14:20 -06:00
alexlebens 976bc0c413 fix: add paths
test-build / guarddog (push) Failing after 44s
test-build / build (push) Successful in 1m10s
renovate / renovate (push) Successful in 1m19s
2026-02-11 15:10:54 -06:00
alexlebens 0a2979ecfe fix: command order
renovate / renovate (push) Successful in 1m27s
test-build / guarddog (push) Failing after 46s
test-build / build (push) Successful in 2m10s
2026-02-11 15:01:17 -06:00
alexlebens c3e4519682 fix: use uvx
renovate / renovate (push) Successful in 53s
test-build / guarddog (push) Failing after 1m9s
test-build / build (push) Successful in 1m22s
2026-02-11 14:56:32 -06:00
alexlebens d9833e1c27 fix: path
renovate / renovate (push) Successful in 55s
test-build / build (push) Successful in 1m19s
test-build / guarddog (push) Failing after 1m30s
2026-02-11 14:53:36 -06:00
alexlebens 19e80809c1 feat: enable guarddog
renovate / renovate (push) Successful in 50s
test-build / build (push) Successful in 1m13s
test-build / guarddog (push) Failing after 1m24s
2026-02-11 14:50:17 -06:00
alexlebens 00ef91b644 feat: release 2.7.0
renovate / renovate (push) Successful in 56s
test-build / build (push) Successful in 1m23s
2026-02-11 14:44:34 -06:00
alexlebens 7f7f710fe8 feat: make weather fetching dynamic
renovate / renovate (push) Successful in 45s
test-build / build (push) Has been cancelled
2026-02-11 14:43:13 -06:00
alexlebens 1573331f87 feat: disable
renovate / renovate (push) Successful in 56s
test-build / build (push) Successful in 1m32s
2026-02-10 22:30:21 -06:00
alexlebens 14f7bdc024 feat: add guarddog scan to workflow
renovate / renovate (push) Successful in 37s
test-build / build (push) Successful in 1m8s
test-build / guarddog (push) Failing after 1m46s
2026-02-10 22:26:15 -06:00
alexlebens 0b116a05df Merge pull request 'chore(deps): update dependency node to v24.13.1' (#330) from renovate/node-24.x into main
renovate / renovate (push) Successful in 46s
test-build / build (push) Successful in 1m6s
release-image-harbor / build (push) Successful in 1m10s
release-image-harbor / release (push) Successful in 6m56s
release-image-gitea / build (push) Successful in 1m8s
release-image-gitea / release (push) Successful in 3m10s
Reviewed-on: #330
2026-02-11 03:56:27 +00:00
renovate-bot 849ca78598 chore(deps): update dependency node to v24.13.1
test-build / build (pull_request) Successful in 1m27s
2026-02-11 03:54:23 +00:00
alexlebens 8377aefaf7 chore(deps): update deps
renovate / renovate (push) Successful in 49s
test-build / build (push) Successful in 1m54s
2026-02-10 21:53:32 -06:00
alexlebens 3f5682f80c feat: release 2.6.0 2026-02-10 21:52:57 -06:00
renovate-bot ae84560ddd Merge pull request 'chore(deps): update dependency @eslint-react/eslint-plugin to v2.12.4' (#331) from renovate/eslint-react-eslint-plugin-2.x-lockfile into main
renovate / renovate (push) Successful in 1m9s
test-build / build (push) Successful in 1m48s
2026-02-11 03:47:31 +00:00
renovate-bot 1f7253d954 chore(deps): update dependency @eslint-react/eslint-plugin to v2.12.4
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 1m53s
2026-02-11 03:47:23 +00:00
alexlebens b6dfc738f1 feat: add weather widget
renovate / renovate (push) Successful in 1m3s
test-build / build (push) Successful in 1m47s
2026-02-10 21:42:04 -06:00
alexlebens 63cbcdf39b feat: improve logos and clickability of cards on about and apps
renovate / renovate (push) Successful in 46s
test-build / build (push) Successful in 1m8s
2026-02-10 18:02:12 -06:00
alexlebens 10c4f9c768 chore(deps): update deps
test-build / build (push) Successful in 1m6s
release-image-harbor / build (push) Successful in 1m16s
release-image-harbor / release (push) Successful in 7m28s
release-image-gitea / build (push) Successful in 1m1s
release-image-gitea / release (push) Successful in 2m37s
renovate / renovate (push) Successful in 1m8s
2026-02-09 22:25:54 -06:00
alexlebens 880bafd41e feat: release 2.5.0 2026-02-09 22:24:36 -06:00
alexlebens 3ebc36174b Merge pull request 'chore(deps): update dependency typescript-eslint to v8.55.0' (#329) from renovate/typescript-eslint-monorepo into main
renovate / renovate (push) Successful in 57s
test-build / build (push) Has been cancelled
Reviewed-on: #329
2026-02-10 04:24:19 +00:00
alexlebens 0abd1a2465 Merge pull request 'chore(deps): update dependency motion to v12.34.0' (#328) from renovate/motion-12.x-lockfile into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #328
2026-02-10 04:24:06 +00:00
renovate-bot f2b27a01bf chore(deps): update dependency typescript-eslint to v8.55.0
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 1m22s
2026-02-10 04:21:50 +00:00
renovate-bot 503cb401fc chore(deps): update dependency motion to v12.34.0
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 2m20s
2026-02-10 04:21:11 +00:00
alexlebens a45a4d7dd7 feat: remove text-justify from content
renovate / renovate (push) Successful in 55s
test-build / build (push) Successful in 1m32s
2026-02-09 22:12:28 -06:00
alexlebens 6d3f3a49ab fix: padding, margin, and width issues
renovate / renovate (push) Successful in 1m3s
test-build / build (push) Successful in 1m31s
2026-02-09 22:08:35 -06:00
alexlebens 197ad63ada feat: move directus to local endpoint
test-build / build (push) Successful in 1m26s
renovate / renovate (push) Successful in 1m40s
2026-02-09 17:07:11 -06:00
alexlebens 4c4421c8a8 fix: fix lint error
test-build / build (push) Successful in 1m7s
renovate / renovate (push) Successful in 1m12s
release-image-harbor / build (push) Successful in 58s
release-image-gitea / build (push) Successful in 1m20s
release-image-gitea / release (push) Successful in 2m53s
release-image-harbor / release (push) Successful in 3m15s
2026-02-08 23:15:40 -06:00
alexlebens d0ff16c8dc feat: release 2.4.0 2026-02-08 23:11:20 -06:00
alexlebens 9678b3c718 feat: add applications page
test-build / build (push) Failing after 43s
renovate / renovate (push) Successful in 1m34s
2026-02-08 23:10:40 -06:00
alexlebens 7fafa5c4cf feat: update features 2026-02-08 17:15:43 -06:00
alexlebens a909743feb Merge pull request 'chore(deps): update dependency eslint to v10' (#323) from renovate/major-eslint-monorepo into main
test-build / build (push) Successful in 2m12s
renovate / renovate (push) Successful in 1m56s
Reviewed-on: #323
2026-02-08 22:12:30 +00:00
renovate-bot f116173cb8 chore(deps): update dependency eslint to v10
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m18s
2026-02-08 21:04:30 +00:00
alexlebens ce62de8883 Merge pull request 'chore(deps): update dependency eslint-plugin-format to v1.4.0' (#326) from renovate/eslint-plugin-format-1.x-lockfile into main
test-build / build (push) Successful in 1m22s
renovate / renovate (push) Successful in 1m39s
Reviewed-on: #326
2026-02-08 21:02:57 +00:00
renovate-bot 94f2779463 chore(deps): update dependency eslint-plugin-format to v1.4.0
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m57s
2026-02-08 20:58:23 +00:00
renovate-bot ed3cf80921 Merge pull request 'chore(deps): update dependency @iconify-json/simple-icons to v1.2.70' (#327) from renovate/iconify-json-simple-icons-1.x-lockfile into main
renovate / renovate (push) Successful in 1m49s
test-build / build (push) Successful in 2m5s
2026-02-08 20:57:00 +00:00
renovate-bot 63aa6bfdbc chore(deps): update dependency @iconify-json/simple-icons to v1.2.70
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 1m25s
2026-02-08 20:56:47 +00:00
alexlebens 4343124c3f Merge pull request 'chore(deps): update dependency @eslint-react/eslint-plugin to v2.12.2' (#325) from renovate/eslint-react-eslint-plugin-2.x-lockfile into main
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #325
2026-02-08 20:55:25 +00:00
renovate-bot a48063a694 chore(deps): update dependency @eslint-react/eslint-plugin to v2.12.2
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m31s
2026-02-08 20:39:47 +00:00
alexlebens e476efb96b feat: use latest alpine
test-build / build (push) Successful in 1m41s
renovate / renovate (push) Successful in 3m23s
2026-02-08 14:38:05 -06:00
renovate-bot a99201138e Merge pull request 'chore(deps): update dependency @eslint-react/eslint-plugin to v2.11.2' (#324) from renovate/eslint-react-eslint-plugin-2.x-lockfile into main
renovate / renovate (push) Successful in 2m15s
test-build / build (push) Successful in 3m14s
2026-02-08 00:02:47 +00:00
renovate-bot 9ef86e71dc chore(deps): update dependency @eslint-react/eslint-plugin to v2.11.2
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m40s
2026-02-08 00:02:23 +00:00
alexlebens 5cd59cd1ff Merge pull request 'chore(deps): update dependency @eslint-react/eslint-plugin to v2.11.0' (#321) from renovate/eslint-react-eslint-plugin-2.x-lockfile into main
test-build / build (push) Successful in 1m6s
renovate / renovate (push) Successful in 3m28s
Reviewed-on: #321
2026-02-07 00:31:33 +00:00
renovate-bot d5cf6fe130 chore(deps): update dependency @eslint-react/eslint-plugin to v2.11.0
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m0s
2026-02-07 00:28:07 +00:00
alexlebens 91136e2e54 Merge pull request 'chore(deps): update dependency @directus/sdk to v21.1.0' (#320) from renovate/directus-sdk-21.x-lockfile into main
renovate / renovate (push) Successful in 1m1s
test-build / build (push) Successful in 1m16s
Reviewed-on: #320
2026-02-07 00:27:00 +00:00
renovate-bot 7b915cf021 chore(deps): update dependency @directus/sdk to v21.1.0
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 59s
2026-02-07 00:19:11 +00:00
alexlebens 807b8dd9b9 Merge pull request 'chore(deps): update dependency motion to v12.33.0' (#322) from renovate/motion-12.x-lockfile into main
test-build / build (push) Successful in 2m20s
renovate / renovate (push) Successful in 2m39s
Reviewed-on: #322
2026-02-07 00:17:06 +00:00
renovate-bot 76c6933682 chore(deps): update dependency motion to v12.33.0
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 3m6s
2026-02-07 00:10:24 +00:00
renovate-bot bd34eb6f75 Merge pull request 'chore(deps): update dependency @types/react to v19.2.13' (#319) from renovate/react-monorepo into main
renovate / renovate (push) Successful in 2m4s
test-build / build (push) Successful in 3m5s
2026-02-07 00:02:58 +00:00
renovate-bot c8d9def6dc chore(deps): update dependency @types/react to v19.2.13
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 2m3s
2026-02-07 00:02:40 +00:00
renovate-bot 5fb2ff16c6 Merge pull request 'chore(deps): update dependency @types/react to v19.2.11' (#318) from renovate/react-monorepo into main
test-build / build (push) Successful in 1m44s
renovate / renovate (push) Successful in 4m59s
2026-02-05 00:06:50 +00:00
renovate-bot 9a86ea4053 chore(deps): update dependency @types/react to v19.2.11
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 2m9s
2026-02-05 00:06:24 +00:00
alexlebens 49969e27b0 feat: release 2.3.2
test-build / build (push) Successful in 1m47s
release-image-gitea / build (push) Successful in 1m37s
release-image-harbor / build (push) Successful in 1m42s
release-image-gitea / release (push) Successful in 2m31s
release-image-harbor / release (push) Successful in 2m39s
renovate / renovate (push) Successful in 1m12s
2026-02-03 21:26:32 -06:00
alexlebens bf73905658 feat: release 2.3.0
test-build / build (push) Successful in 1m20s
renovate / renovate (push) Successful in 1m23s
release-image-gitea / build (push) Successful in 1m59s
release-image-harbor / build (push) Successful in 1m58s
release-image-gitea / release (push) Successful in 2m49s
release-image-harbor / release (push) Successful in 2m54s
2026-02-03 17:34:10 -06:00
alexlebens 56d841a335 feat: better reactive layout for small screen sizes 2026-02-03 17:32:38 -06:00
alexlebens 95432d9059 feat: add rounded option to hero component and use it for about page 2026-02-03 16:56:03 -06:00
alexlebens c2bf64c6cc fix: remove description 2026-02-03 16:55:38 -06:00
alexlebens 1f3fed93a1 feat: reorganize blog layout 2026-02-03 16:42:17 -06:00
alexlebens 754f6a22f0 feat: remove hardcoded descriptions 2026-02-03 16:18:33 -06:00
alexlebens 4203b63893 feat: remove mdx 2026-02-03 16:16:29 -06:00
alexlebens 4d7886b93c fix: clean up comments 2026-02-03 16:07:45 -06:00
alexlebens c7d3ca7252 feat: remove hardcoded descriptions 2026-02-03 16:06:31 -06:00
alexlebens a0f83c874c fix: add comments 2026-02-03 16:00:14 -06:00
alexlebens 22860c4714 feat: add docs link to footer 2026-02-03 15:58:45 -06:00
alexlebens 9b8a7077a7 chore(deps): update deps 2026-02-03 15:56:55 -06:00
alexlebens 8bfc744bdb chore: update README 2026-02-03 15:56:45 -06:00
alexlebens d386afa15e Merge pull request 'chore(deps): update dependency motion to v12.30.0' (#317) from renovate/motion-12.x-lockfile into main
renovate / renovate (push) Successful in 1m11s
test-build / build (push) Successful in 2m18s
Reviewed-on: #317
2026-02-03 00:17:38 +00:00
alexlebens 3fe324d4c2 Merge pull request 'chore(deps): update dependency @eslint-react/eslint-plugin to v2.9.3' (#316) from renovate/eslint-react-eslint-plugin-2.x-lockfile into main
renovate / renovate (push) Successful in 1m58s
test-build / build (push) Has been cancelled
Reviewed-on: #316
2026-02-03 00:15:11 +00:00
renovate-bot a02d417c83 chore(deps): update dependency motion to v12.30.0
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 3m18s
2026-02-03 00:09:13 +00:00
renovate-bot 0d53376c80 chore(deps): update dependency @eslint-react/eslint-plugin to v2.9.3
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 3m11s
2026-02-03 00:08:35 +00:00
alexlebens a5abfe0d1c Merge pull request 'chore(deps): update dependency eslint-plugin-react-refresh to ^0.5.0' (#315) from renovate/eslint-plugin-react-refresh-0.x into main
test-build / build (push) Successful in 1m46s
renovate / renovate (push) Successful in 2m34s
Reviewed-on: #315
2026-02-03 00:07:00 +00:00
renovate-bot 3fcf9a0703 chore(deps): update dependency eslint-plugin-react-refresh to ^0.5.0
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m38s
2026-02-03 00:04:06 +00:00
alexlebens 00b63a5bea revert: release 2.2.5
test-build / build (push) Successful in 2m29s
release-image-harbor / build (push) Successful in 1m23s
release-image-gitea / build (push) Successful in 2m39s
release-image-gitea / release (push) Successful in 5m45s
release-image-harbor / release (push) Successful in 7m49s
renovate / renovate (push) Successful in 4m11s
2026-02-01 21:50:36 -06:00
alexlebens d9860106b1 chore(deps): update pnpm 2026-02-01 21:49:52 -06:00
alexlebens 83940a28ab Merge pull request 'chore(deps): update dependency shiki to v3.22.0' (#314) from renovate/shiki-monorepo into main
renovate / renovate (push) Successful in 50s
test-build / build (push) Has been cancelled
Reviewed-on: #314
2026-02-02 03:48:48 +00:00
renovate-bot 4baa2bed51 chore(deps): update dependency shiki to v3.22.0
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Failing after 1m16s
2026-02-01 00:02:59 +00:00
renovate-bot 19a9588919 Merge pull request 'chore(deps): update dependency preline to v4.0.1' (#313) from renovate/preline-4.x-lockfile into main
test-build / build (push) Successful in 4m47s
renovate / renovate (push) Successful in 3m6s
2026-01-31 00:02:07 +00:00
renovate-bot 3c8d3992cf chore(deps): update dependency preline to v4.0.1
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 1m51s
2026-01-31 00:01:39 +00:00
alexlebens fb8f642c52 fix: update lock
renovate / renovate (push) Successful in 2m14s
test-build / build (push) Successful in 2m16s
release-image-harbor / build (push) Successful in 1m36s
release-image-gitea / build (push) Successful in 3m47s
release-image-harbor / release (push) Successful in 3m25s
release-image-gitea / release (push) Successful in 3m15s
2026-01-30 17:59:51 -06:00
alexlebens fde397386c revert: release 2.2.4
test-build / build (push) Failing after 24s
renovate / renovate (push) Has been cancelled
2026-01-30 17:58:08 -06:00
alexlebens b7f76c5847 feat: add shiki to markdown rendering for code highlighting 2026-01-30 17:56:57 -06:00
118 changed files with 7305 additions and 15556 deletions
-136
View File
@@ -1,136 +0,0 @@
name: release-image-gitea
on:
push:
tags:
- 2.*
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 10.x
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24.13.0
cache: pnpm
- name: Install Dependencies
run: pnpm install
- name: Lint Code
run: pnpm lint
- name: Build Project
run: pnpm build
release:
runs-on: ubuntu-js
needs: build
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.REPOSITORY_HOST }}
username: ${{ gitea.actor }}
password: ${{ secrets.REPOSITORY_TOKEN }}
- name: Create Kubeconfig
run: |
mkdir $HOME/.kube
echo "${{ secrets.KUBECONFIG_BUILDX }}" > $HOME/.kube/config
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
with:
driver: kubernetes
driver-opts: |
namespace=gitea
qemu.install=true
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["harbor.alexlebens.net/proxy-hub.docker/"]
- name: Available Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Extract Metadata
id: meta
uses: docker/metadata-action@v5
with:
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=long
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
images: |
${{ vars.REPOSITORY_HOST }}/${{ gitea.repository }}
- name: Get Version Info
id: version
run: |
echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
echo "commit=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
if git describe --tags --exact-match HEAD 2>/dev/null; then
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
COMMIT_SHA=${{ steps.version.outputs.commit }}
IS_RELEASE=${{ steps.version.outputs.is_release }}
file: ./Dockerfile
- name: ntfy Success
uses: niniyas/ntfy-action@master
if: success()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Success - Site Profile'
priority: 3
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,successfully,completed
details: 'Image for Site Profile has been released!'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Image for Site Profile has failed to be released.'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yml", "clear": true}]'
image: true
-136
View File
@@ -1,136 +0,0 @@
name: release-image-harbor
on:
push:
tags:
- 2.*
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 10.x
- name: Set up Node.js
uses: actions/setup-node@v6
with:
node-version: 24.13.0
cache: pnpm
- name: Install Dependencies
run: pnpm install
- name: Lint Code
run: pnpm lint
- name: Build Project
run: pnpm build
release:
runs-on: ubuntu-js
needs: build
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.REGISTRY_HOST }}
username: ${{ vars.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_SECRET }}
- name: Create Kubeconfig
run: |
mkdir $HOME/.kube
echo "${{ secrets.KUBECONFIG_BUILDX }}" > $HOME/.kube/config
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v3
with:
driver: kubernetes
driver-opts: |
namespace=gitea
qemu.install=true
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["harbor.alexlebens.net/proxy-hub.docker/"]
- name: Available Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Extract Metadata
id: meta
uses: docker/metadata-action@v5
with:
tags: |
type=ref,event=branch
type=ref,event=tag
type=sha,format=long
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
images: |
${{ vars.REGISTRY_HOST }}/images/site-profile
- name: Get Version Info
id: version
run: |
echo "version=$(git describe --tags --always)" >> $GITHUB_OUTPUT
echo "commit=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
if git describe --tags --exact-match HEAD 2>/dev/null; then
echo "is_release=true" >> $GITHUB_OUTPUT
else
echo "is_release=false" >> $GITHUB_OUTPUT
fi
- name: Build and Push Image
uses: docker/build-push-action@v6
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
VERSION=${{ steps.version.outputs.version }}
COMMIT_SHA=${{ steps.version.outputs.commit }}
IS_RELEASE=${{ steps.version.outputs.is_release }}
file: ./Dockerfile
- name: ntfy Success
uses: niniyas/ntfy-action@master
if: success()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Success - Site Profile'
priority: 3
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,successfully,completed
details: 'Image for Site Profile has been released!'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Image for Site Profile has failed to be released.'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yml", "clear": true}]'
image: true
+340
View File
@@ -0,0 +1,340 @@
name: release-image
on:
push:
branches:
- release
workflow_dispatch:
jobs:
build:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24.14.0
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.10
- name: Install Dependencies
run: bun install --frozen-lockfile
- name: Cache Astro Build Cache
uses: actions/cache@v5
with:
path: |
.astro
node_modules/.vite
key: ${{ runner.os }}-astro-${{ hashFiles('**/*.astro', 'astro.config.mjs') }}
restore-keys: |
${{ runner.os }}-astro-
- name: Lint Code
run: bun run lint
- name: Build Project
run: bun run build
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Test Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'During release tests failed for building Site Profile'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yaml", "clear": true}]'
image: true
guarddog:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install GuardDog
run: |
python3 -m pip install --upgrade pip
python3 -m pip install guarddog
- name: Run GuardDog
run: |
guarddog npm scan ./
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Security Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'During release guarddog scan failed for Site Profile'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yaml", "clear": true}]'
image: true
semantic-release:
needs: [ build, guarddog ]
runs-on: ubuntu-js
outputs:
new-release-published: ${{ steps.semantic.outputs.new-release-published }}
new-release-version: ${{ steps.semantic.outputs.new-release-version }}
new-release-git-tag: ${{ steps.semantic.outputs.new-release-git-tag }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
fetch-depth: 0
token: ${{ secrets.BOT_TOKEN }}
- name: Set up Node
uses: actions/setup-node@v6
with:
node-version: 24.14.0
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.10
- name: Install Dependencies
run: bun install --frozen-lockfile
- name: Run Semantic Release
id: semantic
env:
GITEA_TOKEN: ${{ secrets.BOT_TOKEN }}
GITHUB_TOKEN: ${{ secrets.BOT_TOKEN }}
NODE_PATH: ${{ github.workspace }}/node_modules
run: |
bun run semantic-release
release-harbor:
runs-on: ubuntu-js
needs: semantic-release
if: ${{ needs.semantic-release.outputs.new-release-published == 'true' }}
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Login to Harbor Registry
uses: docker/login-action@v4
with:
registry: ${{ vars.REGISTRY_HOST }}
username: ${{ vars.REGISTRY_USER }}
password: ${{ secrets.REGISTRY_SECRET }}
- name: Login to Docker
uses: docker/login-action@v4
with:
registry: ${{ vars.DH_REGISTRY }}
username: ${{ secrets.DH_USERNAME }}
password: ${{ secrets.DH_TOKEN }}
- name: Create Kubeconfig
run: |
mkdir $HOME/.kube
echo "${{ secrets.KUBECONFIG_BUILDX }}" > $HOME/.kube/config
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v4
with:
driver: kubernetes
driver-opts: |
namespace=gitea
qemu.install=true
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["harbor.alexlebens.net/proxy-hub.docker/"]
- name: Available Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Extract Metadata
id: meta
uses: docker/metadata-action@v6
with:
images: |
${{ vars.REGISTRY_HOST }}/images/site-profile
tags: |
type=ref,event=branch
type=sha,format=long
type=raw,value=latest,enable=${{ needs.semantic-release.outputs.new-release-published == 'true' }}
type=semver,pattern={{version}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}},value=${{ needs.semantic-release.outputs.new-release-version }}
- name: Build and Push Image
uses: docker/build-push-action@v7
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
APP_VERSION=${{ needs.semantic-release.outputs.new-release-version }}
COMMIT_SHA=${{ github.sha }}
IS_RELEASE=true
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
- name: ntfy Success
uses: niniyas/ntfy-action@master
if: success()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Success - Site Profile'
priority: 3
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,successfully,completed
details: 'Harbor Image for Site Profile has been released!'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Harbor Image for Site Profile has failed to be released.'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yml", "clear": true}]'
image: true
release-gitea:
runs-on: ubuntu-js
needs: [ semantic-release, release-harbor ]
if: |
always() &&
needs.semantic-release.outputs.new-release-published == 'true'
steps:
- name: Checkout
uses: actions/checkout@v6
with:
ref: release
- name: Login to Gitea Registry
uses: docker/login-action@v4
with:
registry: ${{ vars.REPOSITORY_HOST }}
username: ${{ gitea.actor }}
password: ${{ secrets.REPOSITORY_TOKEN }}
- name: Login to Docker
uses: docker/login-action@v4
with:
registry: ${{ vars.DH_REGISTRY }}
username: ${{ secrets.DH_USERNAME }}
password: ${{ secrets.DH_TOKEN }}
- name: Create Kubeconfig
run: |
mkdir $HOME/.kube
echo "${{ secrets.KUBECONFIG_BUILDX }}" > $HOME/.kube/config
- name: Set up Docker Buildx
id: buildx
uses: docker/setup-buildx-action@v4
with:
driver: kubernetes
driver-opts: |
namespace=gitea
qemu.install=true
buildkitd-config-inline: |
[registry."docker.io"]
mirrors = ["harbor.alexlebens.net/proxy-hub.docker/"]
- name: Available Platforms
run: echo ${{ steps.buildx.outputs.platforms }}
- name: Extract Metadata
id: meta
uses: docker/metadata-action@v6
with:
images: |
${{ vars.REPOSITORY_HOST }}/${{ gitea.repository }}
tags: |
type=ref,event=branch
type=sha,format=long
type=raw,value=latest,enable=${{ needs.semantic-release.outputs.new-release-published == 'true' }}
type=semver,pattern={{version}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}}.{{minor}},value=${{ needs.semantic-release.outputs.new-release-version }}
type=semver,pattern={{major}},value=${{ needs.semantic-release.outputs.new-release-version }}
- name: Build and Push Image
uses: docker/build-push-action@v7
with:
context: .
push: true
platforms: linux/amd64
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
APP_VERSION=${{ needs.semantic-release.outputs.new-release-version }}
COMMIT_SHA=${{ github.sha }}
IS_RELEASE=true
file: ./Dockerfile
cache-from: type=gha
cache-to: type=gha,mode=max
- name: ntfy Success
uses: niniyas/ntfy-action@master
if: success()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Success - Site Profile'
priority: 3
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,successfully,completed
details: 'Gitea Image for Site Profile has been released!'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Release Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Gitea Image for Site Profile has failed to be released.'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=release-image.yaml", "clear": true}]'
image: true
+3 -1
View File
@@ -25,8 +25,10 @@ jobs:
RENOVATE_ENDPOINT: ${{ vars.INSTANCE_URL }} RENOVATE_ENDPOINT: ${{ vars.INSTANCE_URL }}
RENOVATE_REPOSITORIES: alexlebens/site-profile RENOVATE_REPOSITORIES: alexlebens/site-profile
RENOVATE_GIT_AUTHOR: Renovate Bot <renovate-bot@alexlebens.net> RENOVATE_GIT_AUTHOR: Renovate Bot <renovate-bot@alexlebens.net>
RENOVATE_REDIS_URL: ${{ vars.RENOVATE_REDIS_URL }}
LOG_LEVEL: info LOG_LEVEL: info
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }} RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_GIT_PRIVATE_KEY: ${{ secrets.RENOVATE_GIT_PRIVATE_KEY }} RENOVATE_GIT_PRIVATE_KEY: ${{ secrets.RENOVATE_GIT_PRIVATE_KEY }}
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }} RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
RENOVATE_REDIS_URL: ${{ vars.RENOVATE_REDIS_URL }} RENOVATE_REGISTRY_ALIASES: '{"dhi.io": "dhi.io"}'
RENOVATE_HOST_RULES: '[{"matchHost":"dhi.io","hostType":"docker","username":"${{ secrets.RENOVATE_DHI_USER }}","password":"${{ secrets.RENOVATE_DHI_TOKEN }}"}]'
+58 -11
View File
@@ -4,6 +4,9 @@ on:
push: push:
branches: branches:
- main - main
paths-ignore:
- '.gitea/workflows/**'
- '**.md'
pull_request: pull_request:
branches: branches:
@@ -16,25 +19,34 @@ jobs:
- name: Checkout - name: Checkout
uses: actions/checkout@v6 uses: actions/checkout@v6
- name: Set up pnpm - name: Set up Node
uses: pnpm/action-setup@v4
with:
version: 10.x
- name: Set up Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v6
with: with:
node-version: 24.13.0 node-version: 24.14.0
cache: pnpm
- name: Set up Bun
uses: oven-sh/setup-bun@v2
with:
bun-version: 1.3.10
- name: Install Dependencies - name: Install Dependencies
run: pnpm install run: bun install --frozen-lockfile
- name: Cache Astro Build Cache
uses: actions/cache@v5
with:
path: |
.astro
node_modules/.vite
key: ${{ runner.os }}-astro-${{ hashFiles('**/*.astro', 'astro.config.mjs') }}
restore-keys: |
${{ runner.os }}-astro-
- name: Lint Code - name: Lint Code
run: pnpm lint run: bun run lint
- name: Build Project - name: Build Project
run: pnpm build run: bun run build
- name: ntfy Failed - name: ntfy Failed
uses: niniyas/ntfy-action@master uses: niniyas/ntfy-action@master
@@ -50,3 +62,38 @@ jobs:
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png' icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]' actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]'
image: true image: true
guarddog:
runs-on: ubuntu-js
steps:
- name: Checkout
uses: actions/checkout@v6
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: '3.12'
- name: Install GuardDog
run: |
python3 -m pip install --upgrade pip
python3 -m pip install guarddog
- name: Run GuardDog
run: |
guarddog npm scan ./
- name: ntfy Failed
uses: niniyas/ntfy-action@master
if: failure()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Security Failure - Site Profile'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Guarddog scan failed for Site Profile'
icon: 'https://cdn.jsdelivr.net/gh/selfhst/icons/png/gitea.png'
actions: '[{"action": "view", "label": "Open Gitea", "url": "https://gitea.alexlebens.dev/alexlebens/site-profile/actions?workflow=test-build.yaml", "clear": true}]'
image: true
-2
View File
@@ -10,8 +10,6 @@ node_modules/
npm-debug.log* npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
pnpm-debug.log*
# environment variables # environment variables
.env .env
-3
View File
@@ -1,3 +0,0 @@
registry=https://registry.npmjs.org/
engine-strict=true
save-exact=true
+18
View File
@@ -0,0 +1,18 @@
{
"branches": ["release"],
"tagFormat": "${version}",
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
"@semantic-release/changelog",
"semantic-release-export-data",
["@semantic-release/npm", { "npmPublish": false }],
["@semantic-release/git", {
"assets": ["package.json", "CHANGELOG.md"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}],
["@saithodev/semantic-release-gitea", {
"giteaUrl": "https://gitea.alexlebens.dev"
}]
]
}
View File
+17 -18
View File
@@ -1,36 +1,35 @@
ARG REGISTRY=docker.io ARG REGISTRY=dhi.io
FROM ${REGISTRY}/node:24.13.0-alpine3.22 AS base FROM ${REGISTRY}/bun:1.3.10-alpine3.22-dev AS builder
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app WORKDIR /app
COPY package.json pnpm-lock.yaml ./ COPY package.json bun.lock ./
FROM base AS prod-deps FROM builder AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
bun install --production --frozen-lockfile
FROM prod-deps AS build-deps FROM builder AS build-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile RUN --mount=type=cache,id=bun,target=/root/.bun/install/cache \
bun install --frozen-lockfile
FROM build-deps AS build FROM build-deps AS build
COPY . . COPY . .
RUN pnpm run build RUN bun run build
RUN pnpm prune --prod
FROM base AS runtime FROM ${REGISTRY}/bun:1.3.10-alpine3.22 AS runtime
WORKDIR /app
COPY --from=prod-deps /app/node_modules /app/node_modules 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
ENV NODE_ENV=production
ENV HOST=0.0.0.0 ENV HOST=0.0.0.0
ENV SITE_URL=https://www.alexlebens.dev
ENV DIRECTUS_URL=https://directus.alexlebens.dev
ENV PORT=4321 ENV PORT=4321
LABEL version="2.2.3" LABEL version=$APP_VERSION
LABEL description="Astro based personal website" LABEL description="Astro based personal website"
EXPOSE $PORT EXPOSE $PORT
CMD ["node", "./dist/server/entry.mjs"] CMD ["bun", "run", "./dist/server/entry.mjs"]
+8 -14
View File
@@ -2,29 +2,23 @@
Personal site used for information about myself and blog. Personal site used for information about myself and blog.
## Features
- 🐈 Simple And Beautiful ## Development
- 🖥️ Responsive And Light/Dark mode
- 🐛 SiteMap & RSS Feed
- 🐝 Category Support
- 🐜 SEO and Responsiveness
- 🪲 Markdown And MDX
- 🏂🏾 Page Compression & Image Optimization
### Development Commands
With dependencies installed, you can utilize the following npm scripts to manage your project's development lifecycle: With dependencies installed, you can utilize the following npm scripts to manage your project's development lifecycle:
- `pnpm run dev`: Starts a local development server with hot reloading enabled. - `bun run build`: Bundles your site into static files for production.
- `pnpm run preview`: Serves your build output locally for preview before deployment. - `bun run dev`: Starts a local development server with hot reloading enabled.
- `pnpm run build`: Bundles your site into static files for production.
For detailed help with Astro CLI commands, visit [Astro's documentation](https://docs.astro.build/en/reference/cli-reference/). For detailed help with Astro CLI commands, visit [Astro's documentation](https://docs.astro.build/en/reference/cli-reference/).
## Thanks ## Thanks
Thanks https://github.com/mearashadowfax/ScrewFast, https://github.com/godruoyi/gblog/tree/gblog-template Based the site on: [gblog-template](https://github.com/godruoyi/gblog/tree/gblog-template) and [ScrewFast](https://github.com/mearashadowfax/ScrewFast)
Using Icons from [Icons8](https://icons8.com/)
## License ## License
+7 -31
View File
@@ -1,28 +1,24 @@
import { defineConfig, passthroughImageService, sharpImageService } from 'astro/config'; import { defineConfig } from 'astro/config';
import mdx from '@astrojs/mdx';
import node from '@astrojs/node'; import node from '@astrojs/node';
import partytown from '@astrojs/partytown';
import react from '@astrojs/react'; 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 rehypePrettyCode from 'rehype-pretty-code';
import { transformerCopyButton } from '@rehype-pretty/transformers';
const getSiteURL = () => { import { getSiteURL } from './src/support/url';
if (process.env.SITE_URL) {
return `https://${process.env.SITE_URL}`;
}
return 'http://localhost:4321';
};
export default defineConfig({ export default defineConfig({
site: getSiteURL(), site: getSiteURL(),
image: { image: {
remotePatterns: [
{ protocol: 'https', hostname: '*.alexlebens.net' },
{ protocol: 'https', hostname: '*.jsdelivr.net' },
{ protocol: 'https', hostname: '*.icons8.com' },
],
service: { service: {
entrypoint: 'astro/assets/services/sharp', entrypoint: 'astro/assets/services/sharp',
} }
@@ -31,8 +27,6 @@ export default defineConfig({
prefetch: true, prefetch: true,
integrations: [ integrations: [
mdx(),
partytown(),
react(), react(),
sitemap(), sitemap(),
icon({ icon({
@@ -67,24 +61,6 @@ export default defineConfig({
markdown: { markdown: {
syntaxHighlight: false, syntaxHighlight: false,
rehypePlugins: [
[
rehypePrettyCode,
{
theme: {
light: 'github-light',
dark: 'github-dark-dimmed',
},
keepBackground: false,
transformers: [
transformerCopyButton({
visibility: 'always',
feedbackDuration: 2500,
}),
],
},
],
],
}, },
plugins: { plugins: {
+3958
View File
File diff suppressed because it is too large Load Diff
+38 -31
View File
@@ -1,7 +1,7 @@
{ {
"name": "site-profile", "name": "site-profile",
"type": "module", "type": "module",
"version": "2.2.3", "version": "3.6.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,66 +19,73 @@
}, },
"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}\"",
"lint:fix": "eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\"" "lint:fix": "eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\""
}, },
"dependencies": { "dependencies": {
"@astrojs/check": "^0.9.6", "@astrojs/check": "^0.9.7",
"@astrojs/mdx": "^4.3.13", "@astrojs/node": "^10.0.1",
"@astrojs/node": "^9.5.2", "@astrojs/react": "^5.0.0",
"@astrojs/partytown": "^2.1.4", "@astrojs/rss": "^4.0.17",
"@astrojs/react": "^4.4.2", "@astrojs/sitemap": "^3.7.1",
"@astrojs/rss": "^4.0.15", "@directus/sdk": "^21.2.0",
"@astrojs/sitemap": "^3.7.0",
"@directus/sdk": "^21.0.0",
"@giscus/react": "^3.1.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.68", "@iconify-json/simple-icons": "^1.2.73",
"@playform/compress": "^0.2.1", "@playform/compress": "^0.2.1",
"@rehype-pretty/transformers": "^0.13.2", "@swup/astro": "^1.8.0",
"@swup/astro": "1.7.0", "@tailwindcss/postcss": "^4.2.1",
"@tailwindcss/postcss": "^4.1.18", "@tailwindcss/vite": "^4.2.1",
"@tailwindcss/vite": "^4.1.18", "@types/react": "^19.2.14",
"@types/react": "^19.2.10",
"@types/unist": "^3.0.3", "@types/unist": "^3.0.3",
"astro": "^5.17.1", "astro": "^6.0.4",
"astro-compressor": "^1.2.0",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"dayjs": "^1.11.20",
"markdown-it": "^14.1.1",
"marked": "^17.0.4",
"marked-shiki": "^1.2.1",
"mdast-util-to-string": "^4.0.0", "mdast-util-to-string": "^4.0.0",
"motion": "^12.29.2", "photoswipe": "^5.4.4",
"preline": "^4.0.0", "preline": "^4.1.2",
"react": "^19.2.4", "react": "^19.2.4",
"react-dom": "^19.2.4", "react-dom": "^19.2.4",
"reading-time": "^1.5.0", "reading-time": "^1.5.0",
"rehype-pretty-code": "^0.14.1",
"sharp": "^0.34.5", "sharp": "^0.34.5",
"sharp-ico": "^0.1.5", "sharp-ico": "^0.1.5",
"shiki": "^3.21.0", "shiki": "^4.0.2",
"tailwindcss": "^4.1.18", "tailwindcss": "^4.2.1",
"ultrahtml": "^1.6.0" "ultrahtml": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@eslint-react/eslint-plugin": "^2.8.1", "@eslint-react/eslint-plugin": "^2.13.0",
"@saithodev/semantic-release-gitea": "^2.1.0",
"@semantic-release/changelog": "^6.0.3",
"@semantic-release/commit-analyzer": "^13.0.1",
"@semantic-release/git": "^10.0.1",
"@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",
"astro-icon": "^1.1.5", "@types/markdown-it": "^14.1.2",
"eslint": "^9.39.2", "eslint": "^10.0.3",
"eslint-config-prettier": "^10.1.8", "eslint-config-prettier": "^10.1.8",
"eslint-plugin-astro": "^1.5.0", "eslint-plugin-astro": "^1.6.0",
"eslint-plugin-format": "^1.3.1", "eslint-plugin-format": "^2.0.1",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^7.0.1", "eslint-plugin-react-hooks": "^7.0.1",
"eslint-plugin-react-refresh": "^0.4.26", "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",
"timeago.js": "^4.0.2", "semantic-release": "^25.0.3",
"typescript": "5.9.3", "semantic-release-export-data": "^1.2.0",
"typescript-eslint": "8.54.0" "typescript": "^5.9.3",
"typescript-eslint": "^8.57.0"
} }
} }
-12431
View File
File diff suppressed because it is too large Load Diff
-2
View File
@@ -1,2 +0,0 @@
onlyBuiltDependencies:
- swup
BIN
View File
Binary file not shown.

Before

Width:  |  Height:  |  Size: 381 KiB

-4
View File
@@ -1,4 +0,0 @@
User-agent: *
Allow: /
Sitemap: https://www.alexlebens.dev/sitemap-index.xml
+7 -12
View File
@@ -3,10 +3,11 @@ import { getImage } from 'astro:assets';
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus'; import directus from '@lib/directus';
import { SEO } from '@/config';
import brandSrc from '@images/brand_logo.png'; import brandSrc from '@images/brand_logo.png';
import faviconSvgSrc from '@images/favicon_icon.svg'; import faviconSvgSrc from '@images/favicon_icon.svg';
import faviconSrc from '@images/favicon_icon.png'; import faviconSrc from '@images/favicon_icon.png';
import { SEO } from '@/config';
interface Props { interface Props {
title: string; title: string;
@@ -18,6 +19,7 @@ interface Props {
} }
const canonicalURL = Astro.url.href; const canonicalURL = Astro.url.href;
let { let {
title, title,
description, description,
@@ -27,14 +29,14 @@ let {
structuredData = SEO.structuredData, structuredData = SEO.structuredData,
} = Astro.props; } = Astro.props;
const global = await directus.request(readSingleton('site_global'));
let card = 'summary_large_image'; let card = 'summary_large_image';
if (!ogImage) { if (!ogImage) {
ogImage = brandSrc; ogImage = brandSrc;
card = 'summary'; card = 'summary';
} }
const global = await directus.request(readSingleton('site_global'));
const faviconSvg = await getImage({ src: faviconSvgSrc, format: 'svg' }); const faviconSvg = await getImage({ src: faviconSvgSrc, format: 'svg' });
const appleTouchIcon = await getImage({ src: faviconSrc, width: 180, height: 180, format: 'png' }); const appleTouchIcon = await getImage({ src: faviconSrc, width: 180, height: 180, format: 'png' });
const socialImageRes = await getImage({ src: ogImage, width: 1200, height: 600 }); const socialImageRes = await getImage({ src: ogImage, width: 1200, height: 600 });
@@ -62,12 +64,12 @@ if (!socialImage.startsWith('http')) {
<meta http-equiv="X-UA-Compatible" content="ie=edge" /> <meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="mobile-web-app-capable" content="yes" /> <meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#facc15" /> <meta name="theme-color" content="#facc15" />
<meta name="robots" content="index, follow" />
<!-- Open Graph --> <!-- Open Graph -->
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" /> <meta property="og:locale" content="en_US" />
<meta property="og:url" content={Astro.url} /> <meta property="og:url" content={Astro.url} />
<meta property="og:type" content="website" />
<meta property="og:title" content={ogTitle} /> <meta property="og:title" content={ogTitle} />
<meta property="og:site_name" content={global.name} /> <meta property="og:site_name" content={global.name} />
<meta property="og:description" content={ogDescription} /> <meta property="og:description" content={ogDescription} />
@@ -76,17 +78,10 @@ if (!socialImage.startsWith('http')) {
<meta content="600" property="og:image:height" /> <meta content="600" property="og:image:height" />
<meta content="image/png" property="og:image:type" /> <meta content="image/png" property="og:image:type" />
<!-- Twitter -->
<meta property="twitter:card" content={card} />
<meta property="twitter:url" content={Astro.url} />
<meta property="twitter:domain" content={Astro.url} />
<meta property="twitter:title" content={ogTitle} />
<meta property="twitter:description" content={ogDescription} />
<meta property="twitter:image" content={socialImage} />
<!-- Links --> <!-- Links -->
<link href={canonicalURL} rel="canonical" /> <link href={canonicalURL} rel="canonical" />
<link rel="sitemap" href="/sitemap-index.xml" /> <link rel="sitemap" href="/sitemap-index.xml" />
<link rel="alternate" type="application/rss+xml" title={title} href="/rss.xml" />
<!--<link href="/manifest.json" rel="manifest" />--> <!--<link href="/manifest.json" rel="manifest" />-->
<link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" /> <link href="/favicon.ico" rel="icon" sizes="any" type="image/x-icon" />
<link href={faviconSvg.src} rel="icon" type="image/svg+xml" sizes="any" /> <link href={faviconSvg.src} rel="icon" type="image/svg+xml" sizes="any" />
+50 -63
View File
@@ -1,92 +1,80 @@
--- ---
import { Image } from 'astro:assets';
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import BrandLogo from '@components/images/BrandLogo.astro';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BrandLogo from '@components/ui/logos/BrandLogo.astro';
import Image from '@components/ui/images/Image.astro';
import { NavigationLinks, FooterLinks } from '@/config'; import { NavigationLinks, FooterLinks } from '@/config';
import footerImg from '@images/flowers.png'; import footerImg from '@images/flowers.png';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
const currentYear = new Date().getFullYear(); const currentYear = new Date().getFullYear();
--- ---
<footer <footer
class="w-full overflow-hidden bg-stone-300/40 dark:bg-stone-800/20" class="bg-background-accent w-full overflow-hidden"
transition:animate="none" transition:animate="none"
> >
<div class="relative px-4 pt-16 pb-12 sm:px-6"> <div class="relative px-4 sm:px-6 pt-16 pb-12">
<div class="mx-auto max-w-[85rem]"> <div class="max-w-340 mx-auto">
<div class="grid grid-cols-1 gap-10 md:grid-cols-12"> <div class="grid grid-cols-1 md:grid-cols-12 gap-10">
<!-- Brand section --> <!-- Brand section -->
<div class="col-span-1 md:col-span-3"> <div class="col-span-1 md:col-span-3">
<a href="/" class="group inline-block"> <a href="/" class="group inline-block">
<div class="flex items-center"> <div class="flex items-center">
<div class="mx-auto aspect-square overflow-hidden rounded-lg"> <div class="mx-auto aspect-square overflow-hidden">
<BrandLogo class="max-h-[40px] max-w-[40px] rounded-full" /> <BrandLogo class="rounded-lg max-h-10 max-w-10"/>
</div> </div>
<span class="text-header text-lg lg:text-2xl font-semibold leading-tight tracking-tight text-balance ml-3">
<span class="ml-3 text-xl font-bold text-neutral-800 dark:text-neutral-200">
{global.name} {global.name}
</span> </span>
</div> </div>
</a> </a>
<p class="text-primary text-sm lg:text-base text-pretty leading-relaxed mt-4">
<p class="mt-4 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
{global.about} {global.about}
</p> </p>
</div> </div>
<!-- Left links --> <!-- Left links -->
<div class="col-span-1 md:col-span-2"> <div class="col-span-1 md:col-span-2">
<h3 <h3 class="relative inline-block text-header after:bg-main text-sm uppercase font-semibold tracking-wider pb-2 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-['']">
class="after:bg-steel dark:after:bg-bermuda relative inline-block pb-2 text-sm font-semibold tracking-wider text-neutral-800 uppercase after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-[''] dark:text-neutral-100" Site
>
Blog
</h3> </h3>
<ul class="mt-4 space-y-3"> <ul class="mt-4 space-y-3">
{ {NavigationLinks.map((link) => (
NavigationLinks.map((link) => (
<li> <li>
<a <a
href={link.url} href={link.url}
class="group flex items-center text-base text-neutral-600 transition-colors hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200" class="inline-flex items-center text-secondary hover:text-secondary-hover text-base transition-all duration-300 overflow-hidden"
> >
<span class="relative inline-block overflow-hidden"> {link.name}
<span class="relative z-10">{link.name}</span>
</span>
</a> </a>
</li> </li>
)) ))}
}
</ul> </ul>
</div> </div>
<!-- Right links --> <!-- Right links -->
<div class="col-span-1 md:col-span-3"> <div class="col-span-1 md:col-span-3">
<h3 <h3 class="relative inline-block text-header after:bg-main text-sm uppercase font-semibold tracking-wider pb-2 after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-['']">
class="after:bg-steel dark:after:bg-bermuda relative inline-block pb-2 text-sm font-semibold tracking-wider text-neutral-800 uppercase after:absolute after:bottom-0 after:left-0 after:h-0.5 after:w-8 after:content-[''] dark:text-neutral-100"
>
Other Other
</h3> </h3>
<ul class="mt-4 space-y-3"> <ul class="mt-4 space-y-3">
{ {FooterLinks.map((link) => (
FooterLinks.map((link) => (
<li> <li>
<a <a
href={link.url} href={link.url}
class="group flex items-center text-base text-neutral-600 transition-colors hover:text-neutral-800 dark:text-neutral-400 dark:hover:text-neutral-200" class="inline-flex items-center text-secondary hover:text-secondary-hover text-base transition-all duration-300 overflow-hidden"
> >
<span class="relative inline-block overflow-hidden"> {link.name}
<span class="relative z-10">{link.name}</span>
</span>
</a> </a>
</li> </li>
)) ))}
}
</ul> </ul>
</div> </div>
<!-- Right image --> <!-- Right image -->
<div class="col-span-3 mt-10 flex justify-center md:mt-0"> <div class="flex justify-center col-span-4 mt-10 md:mt-0">
<div class="-mt-10 hidden max-h-[460px] max-w-[220px] scale-80 md:block"> <div class="md:block max-h-115 max-w-55 -mt-10 scale-80 hidden">
<Image <Image
src={footerImg} src={footerImg}
alt={global.footer_image_alt} alt={global.footer_image_alt}
@@ -96,44 +84,43 @@ const currentYear = new Date().getFullYear();
format="webp" format="webp"
quality="low" quality="low"
widths={[440]} widths={[440]}
disableBlur={true} inferSize={true}
/> />
</div> </div>
</div> </div>
</div> </div>
<!-- Bottom section --> <!-- Bottom section -->
<div class="mt-12 border-t border-neutral-400/30 pt-8 dark:border-neutral-600/50"> <div class="border-t border-divider pt-8 mt-12">
<div class="flex flex-col items-center justify-between gap-4 md:flex-row"> <div class="flex flex-col md:flex-row items-center justify-between gap-4">
<p class="text-sm text-neutral-600 dark:text-neutral-400"> <p class="text-secondary text-sm">
&copy; {currentYear} All rights reserved. &copy; {currentYear} All rights reserved.
</p> </p>
<div class="flex items-center">
<div class="flex items-center space-x-2"> <span class="text-secondary text-sm">
<span class="text-xs text-neutral-500 dark:text-neutral-400">Built with </span> Weather provided by
</span>
<a
href="https://open-meteo.com/"
target="_blank"
rel="noopener noreferrer"
class="group inline-flex items-center text-secondary hover:text-secondary-hover text-sm transition-all duration-300"
>
<span class="relative underline ml-1">
Open-Meteo.
</span>
</a>
<div class="ml-4"/>
<span class="text-secondary text-sm">
Built with
</span>
<a <a
href="https://astro.build" href="https://astro.build"
target="_blank" target="_blank"
rel="noopener noreferrer" rel="noopener noreferrer"
class="group inline-flex items-center text-xs text-neutral-600 transition-colors hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100" class="group inline-flex items-center text-secondary hover:text-secondary-hover text-sm transition-all duration-300"
> >
<svg class="mr-1 h-4 w-4 text-[#FF5D01]" viewBox="0 0 36 36" fill="none"> <span class="relative underline ml-1">
<path Astro.
fill-rule="evenodd"
clip-rule="evenodd"
d="M8.833 22.958c.622-1.185 1.832-1.918 3.18-1.918 2.292 0 4.145 1.86 4.145 4.153 0 1.34-.626 2.54-1.601 3.303 1.223-1.299 1.97-3.048 1.97-4.971 0-3.994-3.243-7.233-7.242-7.233-2.818 0-5.26 1.6-6.469 3.933.78-2.912 3.428-5.06 6.577-5.06 3.75 0 6.79 3.035 6.79 6.78 0 2.606-1.468 4.868-3.616 6.002a4.163 4.163 0 0 0 2.285-3.724c0-2.293-1.853-4.153-4.145-4.153-1.348 0-2.558.733-3.18 1.918l1.306-3.03Z"
fill="currentColor"></path>
<path
fill-rule="evenodd"
clip-rule="evenodd"
d="M22.155 12.056c-.622 1.185-1.832 1.918-3.18 1.918-2.292 0-4.145-1.86-4.145-4.153 0-1.34.626-2.54 1.601-3.303-1.223 1.299-1.97 3.048-1.97 4.971 0 3.994 3.243 7.233 7.242 7.233 2.818 0 5.26-1.6 6.469-3.933-.78 2.912-3.428 5.06-6.577 5.06-3.75 0-6.79-3.035-6.79-6.78 0-2.606 1.468-4.868 3.616-6.002a4.163 4.163 0 0 0-2.285 3.724c0 2.293 1.853 4.153 4.145 4.153 1.348 0 2.558-.733 3.18-1.918l-1.306 3.03Z"
fill="currentColor"></path>
</svg>
<span class="relative">
Astro
<span
class="absolute bottom-0 left-0 h-0.5 w-0 bg-[#FF5D01] transition-all duration-300 group-hover:w-full"
>
</span>
</span> </span>
</a> </a>
</div> </div>
+27 -28
View File
@@ -1,6 +1,6 @@
--- ---
import BrandLogo from '@components/ui/logos/BrandLogo.astro'; import BrandLogo from '@components/images/BrandLogo.astro';
import ThemeToggle from '@components/ui/buttons/ThemeToggle.astro'; import ThemeToggleButton from '@components/buttons/ThemeToggleButton.astro';
import { NavigationLinks } from '@/config'; import { NavigationLinks } from '@/config';
const pathname = new URL(Astro.request.url).pathname; const pathname = new URL(Astro.request.url).pathname;
@@ -9,31 +9,30 @@ const currentPath = pathname.slice(1);
<header <header
id="nav" id="nav"
class="sticky inset-x-0 top-4 z-50 flex w-full flex-wrap text-sm transition-none md:flex-nowrap md:justify-start" class="fixed flex flex-wrap md:flex-nowrap md:justify-start inset-x-0 top-0 w-full z-50"
> >
<nav <nav
class="relative mx-2 w-full rounded-[36px] border border-neutral-100 bg-neutral-100 px-4 py-3 md:flex md:items-center md:justify-between md:px-6 lg:px-8 dark:border-neutral-700/40 dark:bg-neutral-800/80" class="nav-base relative md:flex md:items-center md:justify-between rounded-[36px] transition-all duration-300 w-full px-4 py-3 mx-2 md:mx-8 mt-4"
aria-label="Global" aria-label="Global"
> >
<div class="flex items-center justify-between"> <div class="flex items-center justify-between ml-0">
<a <a
class="h-[42px] flex-none rounded-lg text-xl font-bold ring-neutral-500 outline-none focus-visible:ring dark:ring-neutral-200 dark:focus:outline-none" class="flex-none rounded-full h-10.5"
href="/" href="/"
aria-label="Brand" aria-label="Brand"
> >
<BrandLogo class="h-full w-auto rounded-full object-cover" /> <BrandLogo class="h-full w-auto rounded-full object-cover"/>
</a> </a>
<div class="md:hidden mr-auto ml-4">
<div class="ml-auto md:hidden">
<button <button
type="button" type="button"
class="hs-collapse-toggle flex h-8 w-8 items-center justify-center rounded-full text-sm font-bold text-neutral-600 transition duration-300 hover:bg-neutral-200 disabled:pointer-events-none disabled:opacity-50 dark:text-neutral-400 dark:hover:bg-neutral-700 dark:focus:outline-none" class="hs-collapse-toggle flex items-center justify-center text-secondary text-sm font-bold hover:bg-neutral-200 dark:hover:bg-neutral-700 rounded-full transition duration-300 disabled:pointer-events-none disabled:opacity-50 h-8 w-8"
data-hs-collapse="#navbar-collapse-with-animation" data-hs-collapse="#navbar-collapse-with-animation"
aria-controls="navbar-collapse-with-animation" aria-controls="navbar-collapse-with-animation"
aria-label="Toggle navigation" aria-label="Toggle navigation"
> >
<svg <svg
class="hs-collapse-open:hidden h-[1.25rem] w-[1.25rem] flex-shrink-0" class="hs-collapse-open:hidden shrink-0 h-5 w-5"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -48,7 +47,7 @@ const currentPath = pathname.slice(1);
<line x1="3" x2="21" y1="18" y2="18"></line> <line x1="3" x2="21" y1="18" y2="18"></line>
</svg> </svg>
<svg <svg
class="hs-collapse-open:block hidden h-[1.25rem] w-[1.25rem] flex-shrink-0" class="hs-collapse-open:block shrink-0 h-5 w-5 hidden"
width="24" width="24"
height="24" height="24"
viewBox="0 0 24 24" viewBox="0 0 24 24"
@@ -63,34 +62,34 @@ const currentPath = pathname.slice(1);
</svg> </svg>
</button> </button>
</div> </div>
<div class="md:hidden ml-2 mr-2">
<span class="">
<ThemeToggleButton />
</span>
</div> </div>
</div>
<div class="flex md:flex-row items-center justify-between">
<div <div
id="navbar-collapse-with-animation" id="navbar-collapse-with-animation"
class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block" class="hs-collapse grow basis-full md:block transition-all duration-300 ml-2 mb-2 md:mb-0 hidden overflow-hidden md:overflow-visible"
> >
<div <div class="flex flex-col md:flex-row md:items-center md:justify-end gap-x-0 md:gap-x-4 lg:gap-x-7 gap-y-4 md:gap-y-0 md:ps-7 mr-2 mt-5 md:mt-0">
class="mt-5 flex flex-col gap-x-0 gap-y-4 md:mt-0 md:flex-row md:items-center md:justify-end md:gap-x-4 md:gap-y-0 md:ps-7 lg:gap-x-7" {NavigationLinks.map((item) => {
>
{
NavigationLinks.map((item) => {
const isActive = currentPath === (item.url === '/' ? '' : item.url.slice(1)); const isActive = currentPath === (item.url === '/' ? '' : item.url.slice(1));
return ( return (
<a <a
href={item.url} href={item.url}
class={`text-sm font-medium ${ class={`text-sm font-medium ${isActive ? 'text-active' : 'text-secondary hover:text-secondary-hover'}`}
isActive
? 'text-orange-500 dark:text-orange-300'
: 'text-neutral-600 hover:text-neutral-900 dark:text-neutral-400 dark:hover:text-neutral-100'
}`}
> >
{item.name} {item.name}
</a> </a>
); );
}) })}
} </div>
<span class="md:inline-block"> </div>
<ThemeToggle /> <div class="hidden md:flex ml-2">
<span class="">
<ThemeToggleButton />
</span> </span>
</div> </div>
</div> </div>
-61
View File
@@ -1,61 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import type { Post } from '@lib/directusTypes';
import { getDirectusImageURL } from '@lib/directusFunctions';
import Image from '@components/ui/images/Image.astro';
import { formatDate } from '@support/time';
interface Props {
post: Post;
}
const { post } = Astro.props;
const baseClasses = 'group group-hover smooth-reveal-cards rounded-xl flex flex-col';
const borderClasses = 'border border-stone-200/50 dark:border-stone-700/50';
const bgColorClasses =
'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
const shadowClasses = 'shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg';
---
<div class={`${baseClasses}`}>
<a
class={`rounded-xl duration-300 transition-all ${borderClasses} ${shadowClasses} ${bgColorClasses}`}
href={`/blog/${post.slug}/`}
data-astro-prefetch
>
<div
class="relative w-full flex-shrink-0 overflow-hidden rounded-t-xl before:absolute before:inset-x-0 before:z-[1] before:size-full"
>
<Image
class="h-auto w-full rounded-t-xl"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="eager"
format="webp"
width="800"
height="460"
/>
</div>
<div class="rounded-xl p-4 md:p-5">
<h3 class="text-xl font-bold text-neutral-600 dark:text-neutral-200">
{post.title}
</h3>
<div
class="group-hover:text-steel dark:group-hover:text-bermuda transition-text relative z-10 mx-auto flex min-h-[44px] items-center font-medium text-neutral-600 decoration-2 duration-300 sm:mx-0 sm:mt-4 dark:text-neutral-400"
>
<span class="relative inline-block overflow-hidden"> Read more </span>
<Icon
name="mdi:keyboard-arrow-right"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
<p class="ml-auto text-sm text-neutral-600 dark:text-neutral-400">
{formatDate(post.published_date)}
</p>
</div>
</div>
</a>
</div>
@@ -1,70 +0,0 @@
---
interface Props {
slug: string;
title: string;
description: string;
count: number;
publishDate: string;
}
const { slug, title, description, count, publishDate } = Astro.props;
const baseClasses =
'group group-hover rounded-xl flex h-full min-h-[220px] cursor-pointer flex-col overflow-hidden';
const bgColorClasses =
'bg-neutral-100/60 dark:bg-neutral-800/60 hover:bg-neutral-100 dark:hover:bg-neutral-800/90 ';
---
<a class={`rounded-xl`} href={`/categories/${slug}/`} data-astro-prefetch="false">
<div class={`${baseClasses}`}>
<div
class={`relative min-h-0 flex-grow overflow-hidden transition-all duration-300 ${bgColorClasses}`}
>
<div class="absolute inset-1 flex flex-col p-3 md:p-4 lg:p-5">
<div class="overflow-hidden">
<h2
class="group-hover:text-steel dark:group-hover:text-bermuda transition-text mb-4 text-4xl font-extrabold tracking-tight text-balance whitespace-nowrap text-neutral-800 duration-300 dark:text-neutral-200"
>
{title}
</h2>
<p class="mb-4 font-light text-neutral-600 sm:text-lg dark:text-neutral-400">
{description}
</p>
</div>
<div
class="mt-auto flex items-center justify-between pt-1 text-xs text-neutral-600 md:pt-2 dark:text-neutral-300"
>
<span class="inline-flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
class="mr-1"
>
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"></path>
</svg>
{count}
</span>
<span class="inline-flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="12"
height="12"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
class="mr-1"
>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
{publishDate}
</span>
</div>
</div>
</div>
</div>
</a>
@@ -1,29 +0,0 @@
---
import type { Post } from '@lib/directusTypes';
import BlogCard from '@components/blog/BlogCard.astro';
interface Props {
posts: Post[];
}
const { posts } = Astro.props;
---
<section class="mx-auto mb-10 max-w-[85rem] px-4 py-8 sm:px-6 lg:px-8 2xl:max-w-full">
<div class="text-left">
<h2
id="selected-articel"
class="smooth-reveal-2 mb-4 text-5xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
>
Older Articles
</h2>
</div>
<div class="flex flex-col md:flex-row md:space-x-12 lg:space-x-16">
<div class="w-full">
<div class="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">
{posts.map((b) => <BlogCard post={b} />)}
</div>
</div>
</div>
</section>
-44
View File
@@ -1,44 +0,0 @@
---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro';
import Image from '@components/ui/images/Image.astro';
interface Props {
title: string;
subTitle: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
img: any;
imgAlt: any;
}
const { title, subTitle, btnExists, btnTitle, btnURL, img, imgAlt } = Astro.props;
---
<section
class="mx-auto max-w-[85rem] items-center gap-8 px-4 py-10 sm:px-6 sm:py-16 md:grid md:grid-cols-2 lg:grid lg:grid-cols-2 lg:px-8 lg:py-14 xl:gap-16 2xl:max-w-full"
>
<Image
class="h-full w-full rounded-xl object-cover sm:max-h-[320px] md:max-h-[360px]"
src={img}
alt={imgAlt}
draggable="false"
loading="lazy"
width="850"
height="420"
/>
<div class="mt-4 md:mt-0">
<h2
class="mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
>
{title}
</h2>
<p
class="mb-4 max-w-prose font-light text-pretty text-neutral-600 sm:text-lg dark:text-neutral-300"
>
{subTitle}
</p>
{btnExists ? <PrimaryCTA title={btnTitle} url={btnURL} /> : null}
</div>
</section>
-45
View File
@@ -1,45 +0,0 @@
---
import type { Post } from '@lib/directusTypes';
import { getDirectusImageURL } from '@lib/directusFunctions';
import BlogLeftSection from '@components/blog/BlogLeftSection.astro';
import BlogRightSection from '@components/blog/BlogRightSection.astro';
interface Props {
posts: Post[];
}
const { posts } = Astro.props;
const blogPosts = posts.slice(0, 5);
---
<section class="smooth-reveal">
{
blogPosts.map((b, index) =>
index % 2 === 0 ? (
<BlogLeftSection
title={b.title}
subTitle={b.description}
btnExists={true}
btnTitle="Read More"
btnURL={`/blog/${b.slug}`}
img={getDirectusImageURL(b.image)}
imgAlt={b.image_alt}
/>
) : (
<BlogRightSection
title={b.title}
subTitle={b.description}
btnExists={true}
btnTitle="Read More"
btnURL={`/blog/${b.slug}`}
single={!b.image_second}
imgOne={getDirectusImageURL(b.image)}
imgOneAlt={b.image_alt}
imgTwo={getDirectusImageURL(b?.image_second)}
imgTwoAlt={b?.image_second_alt}
/>
)
)
}
</section>
@@ -1,87 +0,0 @@
---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro';
import Image from '@components/ui/images/Image.astro';
interface Props {
title: string;
subTitle: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
single?: boolean;
imgOne?: any;
imgOneAlt?: any;
imgTwo?: any;
imgTwoAlt?: any;
}
const {
title,
subTitle,
btnExists,
btnTitle,
btnURL,
single,
imgOne,
imgOneAlt,
imgTwo,
imgTwoAlt,
} = Astro.props;
---
<section
class="mx-auto max-w-[85rem] items-center gap-16 px-4 py-10 sm:px-6 lg:grid lg:grid-cols-2 lg:px-8 lg:py-14 2xl:max-w-full"
>
<div>
<h2
class="mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 dark:text-neutral-200"
>
{title}
</h2>
<p
class="mb-4 max-w-prose font-light text-pretty text-neutral-600 sm:text-lg dark:text-neutral-400"
>
{subTitle}
</p>
{btnExists ? <PrimaryCTA title={btnTitle} url={btnURL} /> : null}
</div>
{
single ? (
<div class="mt-8">
<Image
class="w-full rounded-lg"
src={imgOne}
alt={imgOneAlt}
format="webp"
loading="lazy"
width="850"
height="420"
/>
</div>
) : (
<div class="mt-8 grid grid-cols-2 gap-4">
<Image
class="w-full rounded-xl"
src={imgOne}
alt={imgOneAlt}
draggable="false"
format="webp"
loading="lazy"
width="400"
height="230"
/>
<Image
class="mt-4 w-full rounded-xl lg:mt-10"
src={imgTwo}
alt={imgTwoAlt}
draggable="false"
format="webp"
loading="lazy"
width="400"
height="230"
/>
</div>
)
}
</section>
@@ -1,13 +1,28 @@
--- ---
import Icon from '@components/ui/icons/icon.astro';
--- ---
<button <button
type="button" type="button"
class="focus-visible:ring-secondary group inline-flex items-center rounded-lg p-2.5 text-neutral-600 ring-neutral-500 transition duration-300 outline-none hover:bg-neutral-100 focus:outline-none focus-visible:ring-1 focus-visible:outline-none dark:text-neutral-400 dark:ring-neutral-200 dark:hover:bg-neutral-700" class="button-base button-bg-blue group inline-flex items-center rounded-lg p-2.5"
data-bookmark-button="bookmark-button" data-bookmark-button="bookmark-button"
> >
<Icon name="bookmark" /> <svg
class="h-6 w-6 fill-none transition duration-300"
height=24
width=24
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="1.5"
stroke-linecap="round"
stroke-linejoin="round"
>
<path
d="M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z"
class="fill-current text-neutral-500 transition duration-300 group-hover:text-red-400 group-hover:dark:text-red-400"
/>
</svg>
</button> </button>
<script> <script>
+30
View File
@@ -0,0 +1,30 @@
---
import { Icon } from 'astro-icon/components';
interface Props {
url?: string;
}
const { url } = Astro.props;
---
<a
class="button-base button-bg-gitea group inline-flex rounded-full gap-x-2"
href={url}
target="_blank"
rel="noopener noreferrer"
>
<div class="button-text-title flex relative items-center text-center">
<Icon
name="pajamas:gitea"
class="h-4 w-4 md:h-6 md:w-6"
/>
<span class="ml-2">
Continue to Gitea
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
</div>
</a>
+34
View File
@@ -0,0 +1,34 @@
---
---
<button
class="button-base button-bg-blue group inline-flex rounded-lg gap-x-2"
id="back-button"
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
<svg
class=" shrink-0 group-hover:-translate-x-1 transition duration-300 h-4 w-4"
height=24
width=24
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="m15 18-6-6 6-6"/>
</svg>
<span class="ml-2">
Go Back
</span>
</div>
</button>
<script>
document.getElementById('back-button')?.addEventListener('click', () => {
window.history.back();
});
</script>
+25
View File
@@ -0,0 +1,25 @@
---
import { Icon } from 'astro-icon/components';
interface Props {
url?: string;
}
const { url } = Astro.props;
---
<a
class="button-base button-bg-teal group inline-flex rounded-lg gap-x-2"
href={url}
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
<Icon
name="mdi:home-variant-outline"
class="card-hover-icon-scale h-3 w-3 md:h-5 md:w-5"
/>
<span class="ml-2">
Return Home
</span>
</div>
</a>
@@ -0,0 +1,29 @@
---
import { Icon } from 'astro-icon/components';
interface Props {
title?: string;
url?: string;
noArrow?: boolean;
}
const { title, url, noArrow } = Astro.props;
---
<a
class="button-base button-bg-teal group inline-flex rounded-lg gap-x-2"
href={url}
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
<span class="mr-2">
{title}
</span>
{noArrow ? null : (
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
)}
</div>
</a>
@@ -0,0 +1,20 @@
---
interface Props {
title?: string;
url?: string;
}
const { title, url } = Astro.props;
---
<a
class="button-base button-bg-neutral group inline-flex rounded-lg gap-x-2"
href={url}
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
<span>
{title}
</span>
</div>
</a>
@@ -0,0 +1,15 @@
---
import { Icon } from 'astro-icon/components';
---
<div class="button-base button-bg-teal inline-flex shrink-0 rounded-lg gap-x-2">
<div class="button-text-title flex relative items-center text-center">
<span class="mr-2">
Read More
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
</div>
</div>
@@ -0,0 +1,59 @@
---
import Logo from "@components/images/Logo.astro"
type SocialPlatform = {
name: string;
url: string;
iconLight: string;
iconDark: string;
};
interface Props {
pageTitle: string;
}
const { pageTitle } = Astro.props;
const socialPlatforms: SocialPlatform[] = [
{
name: 'Facebook',
url: `https://www.facebook.com/sharer/sharer.php?u=${Astro.url}`,
iconLight: 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/facebook.webp',
iconDark: 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/facebook.webp',
},
{
name: 'Twitter',
url: `https://x.com/intent/tweet?url=${Astro.url}&text=${pageTitle}`,
iconLight: 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/twitter.webp',
iconDark: 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/twitter.webp',
},
{
name: 'LinkedIn',
url: `https://www.linkedin.com/sharing/share-offsite/?url=${Astro.url}`,
iconLight: 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/linkedin.webp',
iconDark: 'https://cdn.jsdelivr.net/gh/selfhst/icons@main/webp/linkedin.webp',
},
];
---
<div class="inline-flex items-center gap-x-2">
{socialPlatforms.map((platform) => (
<a
class="button-base-hidden group inline-flex rounded-lg gap-x-2"
href={platform.url}
target="_blank"
rel="noopener noreferrer"
title={`Share on ${platform.name}`}
>
<div class="button-text-title-hidden flex relative items-center text-center">
<Logo
srcLight={platform.iconLight}
srcDark={platform.iconDark}
alt={platform.name}
width="24"
height="24"
/>
</div>
</a>
))}
</div>
@@ -5,14 +5,14 @@
<button <button
id="theme-toggle" id="theme-toggle"
data-theme-toggle data-theme-toggle
class="group dark:hover:bg-steel/30 relative touch-manipulation overflow-hidden rounded-full p-1.5 transition-all duration-300 hover:bg-yellow-300/20 focus:outline-hidden sm:p-2" class="group dark:hover:bg-steel/30 hover:bg-yellow-300/20 transition-all duration-300 relative rounded-full p-1.5 sm:p-2 touch-manipulation"
aria-label="Toggle dark mode" aria-label="Toggle dark mode"
> >
<div class="relative z-10 flex h-5 w-5 items-center justify-center"> <div class="relative flex h-5 w-5 items-center justify-center">
<!-- Sun icon --> <!-- Sun icon -->
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon-light absolute h-5 w-5 scale-100 rotate-0 text-neutral-600 transition-all duration-500 dark:scale-0 dark:-rotate-90 dark:text-neutral-400" class="icon-light absolute h-5 w-5 text-neutral-600 dark:text-neutral-400 scale-100 dark:scale-0 rotate-0 dark:-rotate-90 transition-all duration-500"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -29,7 +29,7 @@
<!-- Moon icon --> <!-- Moon icon -->
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon-dark absolute h-5 w-5 scale-0 rotate-90 text-neutral-600 transition-all duration-500 dark:scale-100 dark:rotate-0 dark:text-neutral-400" class="icon-dark absolute h-5 w-5 text-neutral-600 dark:text-neutral-400 scale-0 dark:scale-100 rotate-90 dark:rotate-0 transition-all duration-500"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -43,36 +43,32 @@
</button> </button>
<script is:inline> <script is:inline>
// Use a function to persist theme when using SPA transitions const applyTheme = () => {
// https://docs.astro.build/en/guides/view-transitions/#script-re-execution const isDark =
function applyTheme() { localStorage.theme === 'dark' ||
localStorage.theme === 'dark' (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
? document.documentElement.classList.add('dark') document.documentElement.classList.toggle('dark', isDark);
: document.documentElement.classList.remove('dark'); };
}
document.addEventListener('astro:after-swap', applyTheme);
applyTheme(); applyTheme();
document.addEventListener('astro:after-swap', applyTheme);
</script> </script>
<script> <script>
// Use a function to handle theme toggle to ensure it can be called from anywhere
function setupThemeToggle() { function setupThemeToggle() {
const themeToggles = document.querySelectorAll('[data-theme-toggle]'); const themeToggles = document.querySelectorAll('[data-theme-toggle]');
// Create theme switch overlay element if it doesn't exist // Create theme switch overlay element
if (!document.querySelector('.theme-switch-overlay')) { if (!document.querySelector('.theme-switch-overlay')) {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50'; overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50';
overlay.style.opacity = '0'; overlay.style.opacity = '0';
overlay.style.transition = 'opacity 0.3s ease-out'; overlay.style.transition = 'opacity 0.15s ease-out';
document.body.appendChild(overlay); document.body.appendChild(overlay);
} }
// Toggle theme when any theme toggle button is clicked
themeToggles.forEach((toggle) => { themeToggles.forEach((toggle) => {
// Add event listeners for both click and touch events
['click', 'touchend'].forEach((eventType) => { ['click', 'touchend'].forEach((eventType) => {
toggle.addEventListener( toggle.addEventListener(
eventType, eventType,
@@ -92,14 +88,10 @@
y = e.clientY - rect.top; y = e.clientY - rect.top;
} }
// Set the position variables for the radial gradient
document.documentElement.style.setProperty('--x', `${x}px`); document.documentElement.style.setProperty('--x', `${x}px`);
document.documentElement.style.setProperty('--y', `${y}px`); document.documentElement.style.setProperty('--y', `${y}px`);
// Get the overlay element
const overlay = document.querySelector('.theme-switch-overlay'); const overlay = document.querySelector('.theme-switch-overlay');
// Determine the new theme
const isDark = document.documentElement.classList.contains('dark'); const isDark = document.documentElement.classList.contains('dark');
const newTheme = isDark ? 'light' : 'dark'; const newTheme = isDark ? 'light' : 'dark';
@@ -110,7 +102,6 @@
overlay.style.opacity = '1'; overlay.style.opacity = '1';
} }
// Add transition class
document.documentElement.classList.add('theme-switching'); document.documentElement.classList.add('theme-switching');
// Force a reflow to ensure all elements update // Force a reflow to ensure all elements update
@@ -124,10 +115,7 @@
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
} }
// Store the preference
localStorage.setItem('theme', newTheme); localStorage.setItem('theme', newTheme);
// Dispatch a custom event for other components to react to
document.dispatchEvent( document.dispatchEvent(
new CustomEvent('themeChanged', { new CustomEvent('themeChanged', {
detail: { isDark: newTheme === 'dark' }, detail: { isDark: newTheme === 'dark' },
@@ -137,39 +125,17 @@
// Force another reflow to ensure all elements update // Force another reflow to ensure all elements update
document.body.offsetHeight; document.body.offsetHeight;
// Hide overlay after theme has changed
setTimeout(() => { setTimeout(() => {
if (overlay) { if (overlay) {
overlay.style.opacity = '0'; overlay.style.opacity = '0';
} }
// Remove transition class after animation completes
document.documentElement.classList.remove('theme-switching'); document.documentElement.classList.remove('theme-switching');
}, 300); }, 150);
}, 50); }, 50);
}, },
{ passive: false } { passive: false }
); );
}); });
// Add touch feedback
toggle.addEventListener(
'touchstart',
() => {
toggle.classList.add('active-touch');
},
{ passive: true }
);
toggle.addEventListener(
'touchend',
() => {
setTimeout(() => {
toggle.classList.remove('active-touch');
}, 150);
},
{ passive: true }
);
}); });
} }
@@ -201,61 +167,32 @@
</script> </script>
<style> <style>
/* Smooth transition for the entire page when theme changes */
:global(body) {
transition:
background-color 0.5s ease,
color 0.5s ease;
}
/* Theme transition overlay */
:global(.theme-switch-overlay) {
position: fixed;
inset: 0;
z-index: 9999;
pointer-events: none;
transition: opacity 0.3s ease-out;
}
/* Ensure theme transitions apply to all elements */
:global(.theme-switching *) {
transition-duration: 0.5s !important;
transition-property: background-color, border-color, color, fill, stroke !important;
}
/* Subtle hover animation */ /* Subtle hover animation */
#theme-toggle { #theme-toggle {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 0 0 rgba(0, 0, 0, 0); box-shadow: 0 0 0 rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent; /* Remove default mobile tap highlight */ -webkit-tap-highlight-color: transparent;
min-height: 32px; /* Ensure minimum touch target size */ min-height: 32px;
min-width: 32px; /* Ensure minimum touch target size */ min-width: 32px;
} }
/* Only apply hover effects on non-touch devices */
@media (hover: hover) { @media (hover: hover) {
#theme-toggle:hover { #theme-toggle:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
#theme-toggle:hover .icon-light:not(.dark .icon-light) { :global(:root:not(.dark)) #theme-toggle:hover .icon-light {
filter: drop-shadow-sm(0 0 2px rgba(251, 191, 36, 0.6)); filter: drop-shadow(0 0 2px rgba(251, 191, 36, 0.6));
transform: scale(1.1) rotate(15deg); transform: scale(1.1) rotate(15deg);
} }
#theme-toggle:hover .icon-dark:not(:not(.dark) .icon-dark) { :global(:root.dark) #theme-toggle:hover .icon-dark {
filter: drop-shadow-sm(0 0 2px rgba(129, 140, 248, 0.6)); filter: drop-shadow(0 0 2px rgba(129, 140, 248, 0.6));
transform: scale(1.1) rotate(-15deg); transform: scale(1.1) rotate(-15deg);
} }
} }
/* Touch feedback */
#theme-toggle.active-touch {
transform: scale(0.95);
transition: transform 0.15s ease-in-out;
}
/* Optimize animations for mobile */ /* Optimize animations for mobile */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.icon-light, .icon-light,
+47
View File
@@ -0,0 +1,47 @@
---
import { Image } from 'astro:assets';
import type { Post } from '@lib/directusTypes';
import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
post: Post;
}
const { post } = Astro.props;
---
<div class="smooth-reveal-cards group flex flex-col">
<a
class="card-base border-none! h-full flex flex-col"
href={`/blog/${post.slug}/`}
data-astro-prefetch
>
<div class="relative shrink-0 rounded-t-xl w-full overflow-hidden before:absolute before:inset-x-0 before:z-1 before:size-full">
<Image
class="rounded-t-xl h-64 w-full object-cover"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="eager"
format="webp"
inferSize={true}
/>
</div>
<div class="flex flex-col flex-1 rounded-xl p-4 md:p-5 mx-1 mb-2">
<div class="flex flex-row items-center mb-8">
<h3 class="card-text-title card-hover-text-title text-2xl">
{post.title}
</h3>
</div>
<div class="mt-auto">
<PostMetadataSnippet
enableCategoryLink={false}
post={post}
/>
</div>
</div>
</a>
</div>
+79
View File
@@ -0,0 +1,79 @@
---
import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
slug: string;
title: string;
description: string;
logoLight: string;
logoDark?: string;
count: number;
publishDate: string;
}
const { slug, title, description, logoLight, logoDark, count, publishDate } = Astro.props;
---
<div class="smooth-reveal-cards group h-full">
<a
class="card-base flex flex-col h-full min-h-55"
href={`/categories/${slug}/`}
data-astro-prefetch
>
<div class="relative grow overflow-hidden">
<div class="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="card-hover-icon-scale shrink-0 mr-3">
<Logo
srcLight={getDirectusImageURL(logoLight)}
srcDark={getDirectusImageURL(logoDark!)}
alt={`Logo of ${title}`}
/>
</div>
<h3 class="card-text-title-major card-hover-text-title whitespace-nowrap">
{title}
</h3>
</div>
<div>
<p class="card-text-description mb-4">
{description}
</p>
</div>
<div class="card-text-description flex items-center justify-between text-xs mt-auto pt-1 md:pt-2">
<span class="inline-flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
class="mr-1"
>
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"></path>
</svg>
{count}
</span>
<div class="inline-flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
class="mr-1"
>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
<span>
{publishDate}
</span>
</div>
</div>
</div>
</div>
</a>
</div>
+65
View File
@@ -0,0 +1,65 @@
---
import { Icon } from 'astro-icon/components';
import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
topic: string;
area: string;
date: string;
url: string;
logoUrlLight?: string;
logoUrlDark?: string;
logoIcon?: string;
}
const { topic, area, date, url, logoUrlLight, logoIcon } = Astro.props;
const logoUrlDark = Astro.props.logoUrlDark || logoUrlLight;
---
<div class="smooth-reveal group flex flex-col">
<a
class="card-base flex items-center"
href={url}
>
<div class="p-4 md:p-10">
<div class="flex items-center">
{logoUrlLight ? (
<div class="card-hover-icon-scale mr-5">
<Logo
srcLight={getDirectusImageURL(logoUrlLight)}
srcDark={getDirectusImageURL(logoUrlDark!)}
alt={`Logo of ${topic}`}
/>
</div>
) : logoIcon ? (
<div class="mr-5 text-header">
<Icon name={logoIcon} class="card-hover-icon-scale h-12 w-12" />
</div>
) : null}
<div class="grow text-left">
<span class="card-text-title block text-lg">
{topic}
</span>
<span class="card-text-description block mt-1 font-medium text-xs uppercase">
{area} - {new Date(date).getFullYear()}
</span>
</div>
</div>
<div class="ml-6 flex">
<div class="relative inline-block">
<div class="card-text-title card-hover-text-title flex relative mx-auto min-h-11 items-center font-semibold text-md sm:mx-0 sm:mt-4">
<span class="relative inline-block overflow-hidden">
Visit Page
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="translate-y-0.5 transition duration-300 group-hover:translate-x-1"
/>
</div>
</div>
</div>
</div>
</a>
</div>
+44
View File
@@ -0,0 +1,44 @@
---
import Logo from "@components/images/Logo.astro"
interface Props {
title?: string;
description?: string;
url?: string;
logoUrlLight?: string;
logoUrlDark?: string;
}
const { title, description, url, logoUrlLight }: Props = Astro.props;
const logoUrlDark = Astro.props.logoUrlDark || logoUrlLight;
---
<div class="smooth-reveal-2 group flex flex-col">
<a
class="card-base flex items-center h-30 w-100 md:w-75"
href={url}
data-astro-prefetch
>
<div class="p-5 w-full">
<div class="flex items-center">
{logoUrlLight && (
<div class="card-hover-icon-scale">
<Logo
srcLight={logoUrlLight}
srcDark={logoUrlDark}
alt={`Logo of ${title}`}
/>
</div>
)}
<div class="ms-5 grow text-left">
<span class="card-text-title card-hover-text-title block text-lg">
{title}
</span>
<p class="card-text-description block mt-1">
{description}
</p>
</div>
</div>
</div>
</a>
</div>
+72
View File
@@ -0,0 +1,72 @@
---
import { Icon } from 'astro-icon/components';
import Logo from '@components/images/Logo.astro';
interface Props {
title?: string;
description?: string;
url?: string;
logoUrlLight?: string;
logoUrlDark?: string;
highlights?: string[];
visitSource?: boolean;
}
const { title, description, url, logoUrlLight, logoUrlDark, highlights, visitSource } = Astro.props;
const visitText = visitSource ? 'Visit Source' : 'Visit Page';
const visitClass = visitSource ? 'card-hover-text-gitea' : 'card-hover-text-title';
---
<div class="smooth-reveal group flex flex-col">
<a
class="card-base flex items-center"
href={url}
>
<div class="p-4 md:p-10">
<div class="flex items-center mb-4">
{logoUrlLight && (
<div class="card-hover-icon-scale mr-5">
<Logo
srcLight={logoUrlLight}
srcDark={logoUrlDark}
alt={`Logo of ${title}`}
/>
</div>
)}
<div class="grow text-left">
<span class="card-text-title block text-lg">
{title}
</span>
<p class="card-text-description block mt-1">
{description}
</p>
</div>
</div>
{highlights && (
<ul class="card-text-description text-sm mt-1 flex flex-col list-disc gap-2 [&>li]:ml-4">
{highlights.map((highlight) => (
<li class="marker:text-accent">
{highlight}
</li>
))}
</ul>
)}
<div class="ml-6 flex">
<div class="relative inline-block">
<div class={`card-text-title ${visitClass} flex relative items-center font-semibold text-md min-h-11 mx-auto sm:mx-0 sm:mt-4`}>
{visitSource && <Icon name="pajamas:gitea" />}
<span class="relative inline-block overflow-hidden ml-2">
{visitText}
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="translate-y-0.5 transition duration-300 group-hover:translate-x-1"
/>
</div>
</div>
</div>
</div>
</a>
</div>
@@ -0,0 +1,53 @@
---
import { Image } from 'astro:assets';
import type { Post } from '@lib/directusTypes';
import ReadMoreButton from '@components/buttons/ReadMoreButton.astro';
import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
post: Post;
}
const { post } = Astro.props;
---
<div class="smooth-reveal flex flex-col px-4 py-10 mx-auto w-full">
<a
class="md:card-base-hidden group md:grid md:grid-cols-2 lg:grid lg:grid-cols-2 gap-8 xl:gap-16 w-full md:px-8 md:py-8"
href={`/blog/${post.slug}`}
data-astro-prefetch
>
<div class="h-full">
<Image
class="rounded-2xl rounded-b-none md:rounded-2xl md:shadow-2xl w-full h-full object-cover"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="lazy"
inferSize={true}
/>
</div>
<div class="flex flex-col justify-center bg-background-card md:bg-transparent group-hover:bg-neutral-100 md:group-hover:bg-transparent dark:group-hover:bg-neutral-800/90 md:dark:group-hover:bg-transparent rounded-b-2xl transition-all duration-300 p-6 gap-4">
<h2 class="card-text-header">
{post.title}
</h2>
<p class="card-text-title font-light text-pretty sm:text-lg max-w-prose">
{post.description}
</p>
<div class="flex md:flex-col-reverse lg:flex-row items-center md:items-start lg:items-center justify-between w-full md:gap-4">
<div class="hidden md:block shrink-0 mt-4">
<ReadMoreButton/>
</div>
<div class="mt-2 lg:mt-4">
<PostMetadataSnippet
post={post}
enableCategoryLink={false}
/>
</div>
</div>
</div>
</a>
</div>
@@ -0,0 +1,86 @@
---
import { Image } from 'astro:assets';
import type { Post } from '@lib/directusTypes';
import ReadMoreButton from '@components/buttons/ReadMoreButton.astro';
import PostMetadataSnippet from '@/components/snippets/PostMetadataSnippet.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
post: Post;
}
const { post } = Astro.props;
---
<div class="smooth-reveal flex flex-col px-4 py-10 mx-auto w-full">
<a
class="md:card-base-hidden group flex flex-col-reverse md:grid md:items-center md:grid-cols-2 lg:grid lg:grid-cols-2 md:gap-8 xl:gap-16 w-full md:px-8 md:py-8"
href={`/blog/${post.slug}`}
data-astro-prefetch
>
<div class="flex flex-col bg-background-card md:bg-transparent group-hover:bg-neutral-100 md:group-hover:bg-transparent dark:group-hover:bg-neutral-800/90 md:dark:group-hover:bg-transparent rounded-b-2xl transition-all duration-300 p-6 gap-4">
<h2 class="card-text-header">
{post.title}
</h2>
<p class="card-text-title font-light text-pretty sm:text-lg max-w-prose">
{post.description}
</p>
<div class="flex md:flex-col-reverse lg:flex-row items-center md:items-start lg:items-center justify-between w-full md:gap-4">
<div class="hidden md:block shrink-0 mt-4">
<ReadMoreButton/>
</div>
<div class="mt-2 lg:mt-4">
<PostMetadataSnippet
post={post}
enableCategoryLink={false}
/>
</div>
</div>
</div>
{!post.image_second ? (
<div class="h-full">
<Image
class="rounded-2xl rounded-b-none md:rounded-2xl md:shadow-2xl w-full h-full sm:max-h-80 md:max-h-90 object-cover"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="lazy"
inferSize={true}
/>
</div>
) : (
<div class="h-full">
<div class="md:hidden">
<Image
class="rounded-2xl rounded-b-none shadow-2xl w-full h-full sm:max-h-80 object-cover"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="lazy"
inferSize={true}
/>
</div>
<div class="hidden md:flex md:items-start">
<Image
class="rounded-xl z-10 shadow-2xl w-3/5 h-full object-cover"
src={getDirectusImageURL(post.image)}
alt={post.image_alt}
draggable="false"
loading="lazy"
inferSize={true}
/>
<Image
class="rounded-xl shadow-2xl w-3/5 h-full -ml-16 mt-12 object-cover"
src={getDirectusImageURL(post.image_second)}
alt={post.image_second_alt}
draggable="false"
loading="lazy"
inferSize={true}
/>
</div>
</div>
)}
</a>
</div>
@@ -0,0 +1,77 @@
---
import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
url: string;
title: string;
description: string;
logoLight: string;
logoDark?: string;
count: number;
publishDate: string;
}
const { url, title, description, logoLight, logoDark, count, publishDate } = Astro.props;
---
<div class="smooth-reveal-cards flex flex-col mx-auto w-full">
<a
class="card-base flex flex-col h-full min-h-55"
href={url}
data-astro-prefetch
>
<div class="relative grow overflow-hidden">
<div class="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="card-hover-icon-scale shrink-0 mr-3">
<Logo
srcLight={getDirectusImageURL(logoLight)}
srcDark={getDirectusImageURL(logoDark!)}
alt={`Logo of ${title}`}
/>
</div>
<h3 class="card-text-title-major card-hover-text-title whitespace-nowrap">
{title}
</h3>
</div>
<div>
<p class="card-text-description mb-4">
{description}
</p>
</div>
<div class="card-text-description flex items-center justify-between text-xs mt-auto pt-1 md:pt-2">
<span class="inline-flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
class="mr-1"
>
<path d="M4 19.5v-15A2.5 2.5 0 0 1 6.5 2H20v20H6.5a2.5 2.5 0 0 1 0-5H20"></path>
</svg>
{count}
</span>
<span class="inline-flex items-center">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
class="mr-1"
>
<circle cx="12" cy="12" r="10"></circle>
<polyline points="12 6 12 12 16 14"></polyline>
</svg>
{publishDate}
</span>
</div>
</div>
</div>
</a>
</div>
@@ -0,0 +1,59 @@
---
import { Icon } from 'astro-icon/components';
import { Image } from 'astro:assets';
import { getDirectusImageURL } from '@/support/url';
interface Props {
title: string;
subTitle: string;
url: string;
img?: string;
imgAlt?: string;
}
const { title, subTitle, url, img, imgAlt } = Astro.props;
---
<div class="smooth-reveal flex flex-col mx-auto w-full">
<a
class="md:card-base-hidden group items-center md:grid md:grid-cols-2 lg:grid lg:grid-cols-2 gap-8 xl:gap-16 w-full md:px-8 md:py-8"
href={url}
data-astro-prefetch
>
{img && (
<div>
<Image
class="rounded-2xl rounded-b-none md:rounded-2xl md:shadow-2xl w-full h-full sm:max-h-80 md:max-h-90 object-cover"
src={getDirectusImageURL(img)}
alt={imgAlt}
draggable="false"
loading="lazy"
width="850"
height="420"
inferSize={true}
/>
</div>
)}
<div class="bg-background-card md:bg-transparent group-hover:bg-neutral-100 md:group-hover:bg-transparent dark:group-hover:bg-neutral-800/90 md:dark:group-hover:bg-transparent rounded-b-2xl transition-all duration-300 p-6">
<h2 class="card-text-header mb-2">
{title}
</h2>
<p class="card-text-title font-light text-pretty sm:text-lg max-w-prose mb-8">
{subTitle}
</p>
<div class="flex items-center justify-between w-full">
<div class="button-base button-bg-teal inline-flex rounded-lg gap-x-2">
<div class="button-text-title flex relative items-center text-center">
<span class="mr-2">
Read More
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
</div>
</div>
</div>
</a>
</div>
+44
View File
@@ -0,0 +1,44 @@
---
import ReadMoreButton from '@components/buttons/ReadMoreButton.astro';
import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
title: string;
subTitle: string;
url: string;
logoLight: string;
logoDark?: string;
}
const { title, subTitle, url, logoLight, logoDark} = Astro.props;
---
<div class="smooth-reveal w-full mx-auto">
<a
class="card-base group flex flex-row items-center justify-between w-full p-8 gap-6 md:gap-8"
href={url}
data-astro-prefetch
>
<div class="flex flex-row items-center ml-4">
<div class="card-hover-icon-scale shrink-0 mr-3">
<Logo
srcLight={getDirectusImageURL(logoLight)}
srcDark={getDirectusImageURL(logoDark!)}
alt={`Logo of ${title}`}
/>
</div>
<div class="flex flex-col gap-3 text-left ml-4">
<h2 class="card-text-header text-2xl md:text-3xl">
{title}
</h2>
<p class="card-text-title font-light text-pretty text-lg max-w-3xl">
{subTitle}
</p>
</div>
</div>
<div class="hidden md:block shrink-0 mr-4">
<ReadMoreButton/>
</div>
</a>
</div>
+35
View File
@@ -0,0 +1,35 @@
---
interface Props {
dayName: string;
label: string;
icon: string;
temp: number;
}
const { dayName, label, icon, temp } = Astro.props;
---
<div class="smooth-reveal-2 group flex flex-col">
<div class="card-base w-32 sm:w-40">
<div class="p-5 text-center">
<span class="card-text-description block font-bold text-xs uppercase tracking-widest">
{dayName}
</span>
<div class="flex justify-center my-2">
<img
src={`https://openweathermap.org/img/wn/${icon}@2x.png`}
alt={label}
class="card-hover-icon-scale h-12 w-12"
/>
</div>
<div class="mt-2">
<span class="card-text-title card-hover-text-title block text-2xl">
{temp}°F
</span>
<span class="card-text-description mt-1 block text-xs capitalize">
{label}
</span>
</div>
</div>
</div>
</div>
@@ -1,11 +1,18 @@
--- ---
import { Image } from 'astro:assets';
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import Image from '@components/ui/images/Image.astro';
import logo from '@images/brand_logo.png'; import logo from '@images/brand_logo.png';
import directus from '@lib/directus'; import directus from '@lib/directus';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
--- ---
<Image src={logo} alt={global.name} {...Astro.props} draggable="false" loading="eager" /> <Image
src={logo}
alt={global.name}
draggable="false"
loading="eager"
inferSize={true}
{...Astro.props}
/>
+25
View File
@@ -0,0 +1,25 @@
---
import { Image } from 'astro:assets';
const { srcLight, srcDark, alt, style, width, height } = Astro.props;
---
<div class="grid grid-cols-1 grid-rows-1">
<Image
src={srcLight}
alt={alt}
class:list={['col-start-1 row-start-1 transition-all duration-300 ease-in-out opacity-100 scale-100 dark:opacity-0 dark:scale-65', style]}
width={width}
height={height}
inferSize={true}
/>
<Image
src={srcDark}
alt={alt}
class:list={['col-start-1 row-start-1 transition-all duration-300 ease-in-out opacity-0 scale-65 dark:opacity-100 dark:scale-100', style]}
width={width}
height={height}
inferSize={true}
/>
</div>
+22
View File
@@ -0,0 +1,22 @@
---
import ImageTheme from '@components/images/ImageTheme.astro';
const {
srcLight,
srcDark,
alt,
width = 48,
height = 48,
} = Astro.props;
---
<ImageTheme
srcLight={srcLight}
srcDark={srcDark}
alt={alt}
style=`color: transparent; width: ${width}px; height: ${height}px; object-fit: contain; max-height: 100%; max-width: 100%;`
draggable="false"
loading="lazy"
width={width}
height={height}
/>
@@ -0,0 +1,30 @@
---
import { readItems } from '@directus/sdk';
import type { Application } from '@lib/directusTypes';
import HighlightsCard from '@components/cards/HighlightsCard.astro';
import directus from '@lib/directus';
const applications = ((await directus.request(
readItems('site_applications' as any, {
fields: ['*'],
sort: ['-isActive'],
})
)) as unknown) as Application[];
---
<section class:list={['mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14', Astro.props.className]}>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:gap-8 print:flex print:flex-col">
{applications.map((application: Application) => (
<HighlightsCard
title={application.name}
description={application.description}
url={application.url}
logoUrlLight={application.logoUrl}
logoUrlDark={application.logoUrl}
highlights={application.highlights}
/>
))}
</div>
</section>
@@ -0,0 +1,113 @@
---
import { readItems, readSingleton } from '@directus/sdk';
import type { Post } from '@lib/directusTypes';
import CategoryCard from '@components/cards/CategoryCard.astro';
import LargeCategoryCard from '@components/cards/LargeCategoryCard.astro';
import directus from '@lib/directus';
import { formatFromNow } from '@support/time';
const global = await directus.request(readSingleton('site_global'));
const posts = await directus.request(
readItems('posts', {
filter: { published: { _eq: true } },
fields: ['*', { category: ['*'] }],
sort: ['-published_date'],
})
);
const layoutPattern = [
{ col: 2, row: 2 },
{ col: 2, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 2 },
{ col: 2, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
{ col: 1, row: 1 },
];
const postMap: Map<string, Post[]> = posts
.sort((a: Post, b: Post) => b.published_date.valueOf() - a.published_date.valueOf())
.reduce((acc, obj) => {
const categorySlug = obj.category?.slug;
if (!categorySlug) return acc;
let posts = acc.get(categorySlug);
if (!posts) {
posts = [];
}
posts.push(obj);
acc.set(categorySlug, posts);
return acc;
}, new Map<string, Post[]>());
const categories = (await directus.request(readItems('categories')))
.sort((a, b) => {
const aCount = postMap.get(a.slug)?.length ?? 0;
const bCount = postMap.get(b.slug)?.length ?? 0;
return bCount - aCount;
})
.map((c, index) => {
const posts = postMap.get(c.slug);
const pattern = layoutPattern[index % layoutPattern.length];
const smColSpan = Math.min(pattern.col, 1);
const mdColSpan = Math.min(pattern.col, 2);
const lgColSpan = Math.min(pattern.col, 4);
const rowSpan = pattern.row;
const rowSpanClass = rowSpan > 1 ? `row-span-${rowSpan}` : 'row-span-1';
const gridItemClass = `col-span-${smColSpan} md:col-span-${mdColSpan} lg:col-span-${lgColSpan} ${rowSpanClass}`;
return {
...c,
posts,
gridItemClass,
layoutPattern: {
smCol: smColSpan,
mdCol: mdColSpan,
lgCol: lgColSpan,
row: rowSpan,
index,
},
};
});
---
<section class="mx-auto px-4 py-10 sm:px-6 lg:px-8 lg:py-14 lg:pt-10 2xl:max-w-full">
<div class="grid grid-flow-row-dense grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-4">
{categories.map((category) => {
return (
<div
class={category.gridItemClass}
style={category.layoutPattern.row > 1 ? 'grid-row: span 2 / span 2;' : ''}
>
<CategoryCard
slug={category.slug}
title={category.title}
description={category.description}
logoLight={category.logoLight}
logoDark={category.logoDark}
count={postMap.get(category.slug)?.length ?? 0}
publishDate={formatFromNow(postMap.get(category.slug)?.[0]?.published_date)}
/>
</div>
);
})}
<div class="col-span-full mt-8">
<LargeCategoryCard
title="All Posts"
description="Here you can forgoe the organization and browse everything I've posted"
url="/all"
logoLight={global.all_logoLight}
logoDark={global.all_logoDark}
count={posts.length}
publishDate={formatFromNow(posts[0]?.published_date)}
/>
</div>
</div>
</section>
@@ -0,0 +1,65 @@
---
import { readItems } from '@directus/sdk';
import type { Education, Certificate} from '@lib/directusTypes';
import EducationCard from '@components/cards/EducationCard.astro';
import directus from '@lib/directus';
const educations = ((await directus.request(
readItems('site_education' as any, {
fields: ['*'],
sort: ['-graduationDate'],
})
)) as unknown) as Education[];
const certificates = ((await directus.request(
readItems('site_certificate' as any, {
fields: ['*'],
sort: ['-issuerDate'],
})
)) as unknown) as Certificate[];
---
<section class:list={['flex flex-col gap-4', Astro.props.className]}>
<h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-5">
Education
</h3>
<div class="mx-8">
<h4 class="smooth-reveal card-text-header-minor pt-5">
College
</h4>
<div class="grid md:grid-cols-2 sm:grid-cols-1 gap-4 py-3">
{educations.map((education: Education) => (
<EducationCard
topic={education.institution}
area={education.area}
date={education.graduationDate}
url={education.url}
logoUrlLight={education.logo}
logoUrlDark={education.logoDark}
/>
))}
</div>
</div>
{certificates.length > 0 && (
<div class="mx-8">
<h4 class="smooth-reveal card-text-header-minor pt-8">
Certificates
</h4>
<div class="grid md:grid-cols-2 sm:grid-cols-1 gap-4 py-3">
{certificates.map((certificate: Certificate) => (
<EducationCard
topic={certificate.name}
area={certificate.issuer}
date={certificate.issuerDate}
url={certificate.url}
logoUrlLight={certificate.logo}
logoUrlDark={certificate.logoDark}
/>
))}
</div>
</div>
)}
</section>
@@ -0,0 +1,159 @@
---
import { Icon } from 'astro-icon/components';
import { readItems } from '@directus/sdk';
import type { Experience } from '@lib/directusTypes';
import directus from '@lib/directus';
const experiences = ((await directus.request(
readItems('site_experience'as any, {
fields: ['*'],
sort: ['-endDate'],
})
)) as unknown) as Experience[];
---
<section class:list={['flex flex-col gap-8', Astro.props.className]}>
<h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-10">
Experience
</h3>
<ul class="flex flex-col w-full ml-8 pr-8">
{experiences.map((experience: Experience) => {
const startYear = new Date(experience.startDate).getFullYear();
const endYear = experience.endDate != null ? new Date(experience.endDate).getFullYear() : 'Present';
return (
<li class="relative">
<div class="smooth-reveal group relative grid sm:grid-cols-18 sm:gap-8 md:gap-6 pb-16">
<header class="relative sm:col-span-3 text-header font-semibold text-lg mt-1">
<time datetime={experience.startDate} data-title={experience.startDate}>
{startYear}
</time>
{' '}-{' '}
<time datetime={experience.endDate} data-title={experience.endDate}>
{endYear}
</time>
</header>
<div class="relative flex flex-col sm:col-span-12 pb-6">
<div class="absolute bg-accent -translate-x-[1.71rem] rounded-full h-2 w-2 mt-3"/>
<h3>
<div
class="inline-flex items-center text-2xl leading-tight font-semibold"
aria-label="{position} - {company}"
>
<span class="text-header">
{experience.position} <span>@</span>
{experience.url ? (
<a
class="hover:text-main"
href={experience.url}
title={`Ver ${experience.name}`}
target="_blank"
>
{experience.name}
</a>
) : (
<span>{experience.name}</span>
)}
</span>
</div>
</h3>
{(experience.location || experience.location_type) && (
<div class="text-secondary text-sm">
{experience.location} {experience.location && experience.location_type && '-'} {experience.location_type}
</div>
)}
<div class="text-md mt-4 flex flex-col gap-4" x-data="{ expanded: false }">
{experience.summary && (
<div class="flex flex-col gap-1">
<h4 class="text-header font-semibold">
Summary:
</h4>
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
<li class="marker:text-main">
{experience.summary}
</li>
</ul>
</div>
)}
{(experience.responsibilities || experience.achievements) && (
<div class="relative flex flex-col gap-4" :class="expanded ? '' : 'mask-[linear-gradient(to_bottom,black_50%,transparent)]'" x-show="expanded" x-collapse.min.50px>
{experience.responsibilities && (
<div class="flex flex-col gap-1">
<h4 class="text-header font-semibold">
Responsibilities:
</h4>
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
{experience.responsibilities.map(responsibility => (
<li class="marker:text-main">
{responsibility}
</li>
))}
</ul>
</div>
)}
{experience.achievements && (
<div class="flex flex-col gap-1">
<h4 class="text-header font-semibold">
Achievements:
</h4>
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
{experience.achievements.map(achievement => (
<li class="marker:text-main">
{achievement}
</li>
))}
</ul>
</div>
)}
</div>
<button @click="expanded = ! expanded" class="group/more flex items-center justify-center text-primary hover:text-primary-hover text-xs underline transition-all gap-1.5 w-fit cursor-pointer">
<span x-text="expanded ? 'Show less' : 'Show more'">
Show more
</span>
<svg
class="group-hover/more:translate-y-0.5 ease-out duration-300 h-4 w-4"
:class="{ 'rotate-180': expanded }"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</button>
<ul
class="flex print:hidden flex-wrap gap-2 mt-2"
aria-label="Technologies used"
>
{experience.skills && experience.skills.map(skill => {
const iconName = skill.toLowerCase();
const skillName = skill.split(':')[1].replace(/^language-/, '').replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
return (
<li class="flex items-center bg-steel/20 dark:bg-bermuda/20 text-neutral-800 dark:text-neutral-200 text-xs rounded-md border border-solid border-steel/20 dark:border-bermuda/20 gap-1 px-2 py-0.5">
<Icon name={`${iconName}`} class="h-4 w-4" /> <span>{skillName}</span>
</li>
)
})}
</ul>
)}
</div>
</div>
</div>
</li>
);
})}
</ul>
</section>
<!-- Alpine Plugins -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<!-- Alpine Core -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
@@ -0,0 +1,45 @@
---
import { readSingleton } from '@directus/sdk';
import FeaturesCard from '@components/cards/FeaturesCard.astro';
import directus from '@lib/directus';
const global = await directus.request(readSingleton('site_global'));
---
<section class="max-w-340 2xl:max-w-full px-4 py-10 mx-auto mb-2 md:mb-8">
<div class="flex items-center justify-center">
<div class="max-w-5xl">
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 lg:gap-6">
<FeaturesCard
title="Cloud Engineer"
description="Full stack and cloud engineer."
url="/about"
logoUrlLight="https://directus.alexlebens.net/assets/8e674299-2dfc-4ea3-8193-a1c4050bf06f?key=system-large-contain&v=2026-02-26T21%3A23%3A22.457Z"
logoUrlDark="https://directus.alexlebens.net/assets/cdd6efce-3231-4213-8b1e-cae9c1b3a18f?key=system-large-contain&v=2026-03-03T19%3A30%3A01.263Z"
/>
<FeaturesCard
title="Homelab"
description="Tinkering, testing, deploying, etc, etc ..."
url="/categories/homelab/"
logoUrlLight="https://directus.alexlebens.net/assets/4ccb3d9f-e203-4c2b-bfad-600f1aa5b7a4?key=system-large-contain&v=2026-02-26T21%3A25%3A30.853Z"
logoUrlDark="https://directus.alexlebens.net/assets/33fb6b45-1834-44a0-bc04-692be4f6d4ce?key=system-large-contain&v=2026-03-03T19%3A30%3A37.961Z"
/>
<FeaturesCard
title="Documentation"
description="Reference and guides for my homelab."
url="https://docs.alexlebens.dev"
logoUrlLight="https://directus.alexlebens.net/assets/5f5faad9-2c36-40a5-a519-f631d39ab388?key=system-large-contain&v=2026-03-03T19%3A49%3A43.620Z"
logoUrlDark="https://directus.alexlebens.net/assets/c49b40a9-ddf2-4fa2-ba35-6039682b93de?key=system-large-contain&v=2026-03-03T19%3A50%3A14.893Z"
/>
<FeaturesCard
title="Email"
description={`Send me a message.`}
url=`mailto:${global.email}`
logoUrlLight="https://directus.alexlebens.net/assets/4b1c771e-c709-4094-b018-0197b4747829?key=system-large-contain&v=2026-03-03T19%3A48%3A36.432Z"
logoUrlDark="https://directus.alexlebens.net/assets/cdf2ea11-fcfc-41f1-a701-43084fb06efa?key=system-large-contain&v=2026-03-03T19%3A47%3A28.109Z"
/>
</div>
</div>
</div>
</section>
@@ -1,22 +1,20 @@
--- ---
import GiteaBtn from '@components/ui/buttons/GiteaBtn.astro'; import GiteaButton from '@components/buttons/GiteaButton.astro';
const { title, subTitle, url } = Astro.props;
const btnTitle = 'Continue to Gitea';
interface Props { interface Props {
title: string; title: string;
subTitle?: string; subTitle?: string;
url?: string; url?: string;
} }
const { title, subTitle, url } = Astro.props;
--- ---
<section class="lg:px- relative mx-auto mb-20 max-w-[85rem] px-4 pt-30 pb-30 sm:px-6"> <section class="relative max-w-340 pt-30 pb-30 px-4 sm:px-6 lg:px- mx-auto mb-2 md:mb-10">
<div <!-- Animated shapes -->
class="smooth-reveal absolute top-[55%] left-0 scale-90 md:top-[20%] xl:top-[25%] xl:left-[10%]" <div class="smooth-reveal absolute top-[55%] left-0 scale-90 md:top-[20%] xl:top-[25%] xl:left-[10%]">
>
<svg <svg
class="animate-hover animate-hover-1" class="gitea-animate-hover gitea-animate-hover-1"
width="64" width="64"
height="64" height="64"
fill="none" fill="none"
@@ -46,7 +44,7 @@ interface Props {
</div> </div>
<div class="smooth-reveal absolute top-0 left-[85%] scale-75"> <div class="smooth-reveal absolute top-0 left-[85%] scale-75">
<svg <svg
class="animate-hover animate-hover-2" class="gitea-animate-hover gitea-animate-hover-2"
width="64" width="64"
height="64" height="64"
fill="none" fill="none"
@@ -80,11 +78,9 @@ interface Props {
d="M10.5 19H9M15 19h-1.5"></path> d="M10.5 19H9M15 19h-1.5"></path>
</svg> </svg>
</div> </div>
<div <div class="smooth-reveal absolute bottom-[5%] left-[60%] scale-[.6] xl:bottom-[15%] xl:left-[35%]">
class="smooth-reveal absolute bottom-[5%] left-[60%] scale-[.6] xl:bottom-[15%] xl:left-[35%]"
>
<svg <svg
class="animate-hover animate-hover-3" class="gitea-animate-hover gitea-animate-hover-3"
width="64" width="64"
height="64" height="64"
fill="none" fill="none"
@@ -106,59 +102,54 @@ interface Props {
></path> ></path>
</svg> </svg>
</div> </div>
<!-- Hero Section Heading --> <!-- Heading -->
<div class="smooth-reveal-2 mx-auto mt-5 max-w-xl text-center"> <div class="smooth-reveal-2 mx-auto mt-5 max-w-xl text-center">
<h2 <h1 class="card-text-header block">
class="block text-4xl leading-tight font-bold tracking-tight text-balance text-neutral-800 md:text-5xl lg:text-5xl dark:text-neutral-200"
>
{title} {title}
</h2> </h1>
</div> </div>
<!-- Hero Section Sub-heading --> <!-- Sub-heading -->
<div class="smooth-reveal-2 mx-auto mt-5 max-w-3xl text-center"> <div class="smooth-reveal-2 mx-auto mt-5 max-w-3xl text-center">
{ {subTitle && (
subTitle && ( <p class="card-text-header-description">
<p class="text-lg text-pretty text-neutral-600 dark:text-neutral-400">{subTitle}</p> {subTitle}
) </p>
} )}
</div> </div>
<!-- Github Button --> <!-- Gitea Button -->
{ {url && (
url && ( <div class="smooth-reveal-2 flex justify-center mt-8 gap-3">
<div class="smooth-reveal-2 mt-8 flex justify-center gap-3"> <GiteaButton url={url}/>
<GiteaBtn url={url} title={btnTitle} />
</div> </div>
) )}
}
</section> </section>
<style> <style>
@keyframes animate-hover { @keyframes gitea-animate-hover {
from { from {
transform: translateY(15px); transform: translateY(15px);
} }
to { to {
transform: translateY(-15px); transform: translateY(-15px);
} }
} }
.animate-hover { .gitea-animate-hover {
animation: animate-hover ease-in-out; animation: gitea-animate-hover ease-in-out;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-direction: alternate; animation-direction: alternate;
} }
.animate-hover-1 { .gitea-animate-hover-1 {
animation-duration: 5s; animation-duration: 5s;
} }
.animate-hover-2 { .gitea-animate-hover-2 {
animation-duration: 5.5s; animation-duration: 5.5s;
} }
.animate-hover-3 { .gitea-animate-hover-3 {
animation-duration: 6s; animation-duration: 6s;
} }
</style> </style>
@@ -0,0 +1,47 @@
---
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import Logo from '@components/images/Logo.astro';
import { getDirectusImageURL } from '@/support/url';
interface Props {
title: string;
subTitle: string;
logoExists?: boolean;
logoLight?: string;
logoDark?: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
}
const { title, subTitle, logoExists, logoLight, logoDark, btnExists, btnTitle, btnURL } = Astro.props;
---
<section class="mx-auto mt-10 px-4 sm:px-6 lg:px-8 lg:pt-10 2xl:max-w-full">
<div class="flex-wrap md:flex md:items-center md:justify-between">
<div class="w-full md:w-auto">
<div class="smooth-reveal flex flex-row items-center mb-4">
{logoExists ? (
<div class="shrink-0 mr-5">
<Logo
srcLight={getDirectusImageURL(logoLight!)}
srcDark={getDirectusImageURL(logoDark!)}
alt={`Logo of ${title}`}
/>
</div>
) : null}
<h1 class="card-text-header block lg:text-6xl">
{title}
</h1>
</div>
<p class="smooth-reveal card-text-header-description mt-4">
{subTitle}
</p>
{btnExists ? (
<div class="smooth-reveal mt-4 md:mt-8">
<GoLinkPrimaryButton title={btnTitle} url={btnURL}/>
</div>
) : null}
</div>
</div>
</section>
+100
View File
@@ -0,0 +1,100 @@
---
import { Image } from 'astro:assets';
import { readItems } from '@directus/sdk';
import type { HeaderImage } from '@lib/directusTypes';
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import GoLinkSecondaryButton from '@components/buttons/GoLinkSecondaryButton.astro';
import directus from '@lib/directus';
import { getDirectusImageURL } from '@/support/url';
interface Props {
title: string;
subTitle?: string;
primaryBtn?: string;
primaryBtnURL?: string;
secondaryBtn?: string;
secondaryBtnURL?: string;
}
const { title, subTitle, primaryBtn, primaryBtnURL, secondaryBtn, secondaryBtnURL } = Astro.props;
const imagesData = ((await directus.request(
readItems('header_images', {
fields: ['*'],
})
)) as unknown) as HeaderImage[];
const images = await Promise.all(imagesData.map(async (img) => ({
...img,
src: await getDirectusImageURL(img.image)
})));
---
<section class="grid md:grid-cols-2 md:items-center gap-4 md:gap-8 max-w-340 2xl:max-w-full px-4 py-14 sm:px-6 lg:px-8">
<div class="md:ml-12">
<h1 class="smooth-reveal card-text-header block lg:text-7xl">
<Fragment set:html={title} />
</h1>
{subTitle && (
<p class="smooth-reveal card-text-header-description lg:w-4/5 mt-6">
{subTitle}
</p>
)}
<div class="smooth-reveal grid sm:inline-flex mt-7 w-full gap-3">
{primaryBtn && <GoLinkPrimaryButton title={primaryBtn} url={primaryBtnURL} />}
{secondaryBtn && <GoLinkSecondaryButton title={secondaryBtn} url={secondaryBtnURL} />}
</div>
</div>
<div class="smooth-reveal-fade md:block w-full hidden md:mr-12">
<div
class="flex justify-center w-full top-12 md:ml-4 overflow-hidden no-js-fallback"
id="hero-image-container"
>
{images.map((img, index) => (
<div
class="hero-image hidden justify-center w-full h-full"
data-index={index}
>
<Image
class="h-full w-105 scale-100 object-cover object-center"
src={img.src}
alt={img.image_alt}
draggable="false"
loading="eager"
format="webp"
widths={[840]}
inferSize={true}
/>
</div>
))}
</div>
<style>
.no-js-fallback .hero-image:first-child {
display: flex !important;
}
</style>
<script is:inline>
document.getElementById('hero-image-container')?.classList.remove('no-js-fallback');
</script>
</div>
</section>
<script>
document.addEventListener('astro:page-load', () => {
const container = document.getElementById('hero-image-container');
if (container) {
const images = container.querySelectorAll('.hero-image');
images.forEach(img => {
img.classList.remove('flex');
img.classList.add('hidden');
});
if (images.length > 0) {
const randomIndex = Math.floor(Math.random() * images.length);
images[randomIndex].classList.remove('hidden');
images[randomIndex].classList.add('flex');
}
}
});
</script>
@@ -0,0 +1,32 @@
---
import { readItems } from '@directus/sdk';
import type { Project } from '@lib/directusTypes';
import HighlightsCard from '@components/cards/HighlightsCard.astro';
import directus from '@lib/directus';
const projects = ((await directus.request(
readItems('site_projects' as any, {
fields: ['*'],
sort: ['-isActive'],
})
)) as unknown) as Project[];
---
<section class:list={['flex flex-col gap-y-8', Astro.props.className]}>
<h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-5">
Projects
</h3>
<div class="grid grid-cols-1 gap-6 md:grid-cols-2 lg:gap-8 print:flex print:flex-col">
{projects.map((project: Project) => (
<HighlightsCard
title={project.name}
description={project.description}
url={project.source}
highlights={project.highlights}
visitSource={true}
/>
))}
</div>
</section>
@@ -0,0 +1,46 @@
---
import { readSingleton } from '@directus/sdk';
import type { Post } from '@lib/directusTypes';
import BlogCard from '@components/cards/BlogCard.astro';
import LargeLinkCard from '@components/cards/LargeLinkCard.astro';
import directus from '@lib/directus';
interface Props {
posts: Post[];
title: string;
subTitle?: string;
}
const global = await directus.request(readSingleton('site_global'));
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">
<div class="text-center max-w-2xl mx-auto mb-10 lg:mb-14">
<h1 class="smooth-reveal card-text-header block">
{title}
</h1>
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
<span class="card-text-header-description">
{subTitle}
</span>
</div>
</div>
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
{posts.map((b) =>
<BlogCard post={b} />
)}
<div class="col-span-full">
<LargeLinkCard
title="All Posts"
subTitle="Catch up on everything I've written"
url="/all"
logoLight={global.all_logoLight}
logoDark={global.all_logoDark}
/>
</div>
</div>
</section>
@@ -0,0 +1,20 @@
---
import type { Post } from '@lib/directusTypes';
import LargeBlogLeftCard from '@components/cards/LargeBlogLeftCard.astro';
import LargeBlogRightCard from '@components/cards/LargeBlogRightCard.astro';
interface Props {
posts: Post[];
}
const { posts } = Astro.props;
---
<section class="smooth-reveal flex flex-col gap-4 md:mb-20">
{posts.map((post, index) => index % 2 === 0 ? (
<LargeBlogLeftCard post={post}/>
) : (
<LargeBlogRightCard post={post}/>
))}
</section>
@@ -6,78 +6,53 @@ import type { Skill } from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
const skills = await directus.request( const skills = ((await directus.request(
readItems('site_skills', { readItems('site_skills' as any, {
fields: ['*'], fields: ['*'],
sort: ['-date_created'], sort: ['-date_created'],
}) })
); )) as unknown) as Skill[];
const baseClasses = 'mx-2 min-w-[220px] sm:mx-4 sm:min-w-[280px]';
const borderClasses =
'border border-neutral-100 hover:border-neutral-200 dark:border-stone-500/20 dark:hover:border-neutral-800';
const bgColorClasses = 'bg-neutral-100/80 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
const hoverClasses = 'hover:-translate-y-2 hover:scale-105 ';
const shadowClasses = 'shadow-xs hover:shadow-lg';
--- ---
<section class:list={['flex flex-col gap-4', Astro.props.className]}> <section class:list={['flex flex-col gap-4', Astro.props.className]}>
<h3 <h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-5">
class="relative flex w-full items-center gap-3 pb-4 text-5xl text-neutral-800 dark:text-neutral-200"
>
Skills Skills
</h3> </h3>
<div class=""> <div>
<div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8"> <div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8 mask-[linear-gradient(to_right,transparent,black_10%,black_90%,transparent)]">
<!-- Main slider container --> <!-- Main slider container -->
<div class="slider-track animate-slide flex"> <div class="slider-track animate-slide flex">
{ {[...skills, ...skills, ...skills].map((skill: Skill) => {
[...skills, ...skills, ...skills].map((skill: Skill) => {
return ( return (
<div <div class="skill-card card-base transform hover:-translate-y-2 hover:scale-105 transition-all duration-300 mx-2 min-w-55 sm:mx-4 sm:min-w-70">
class={`skill-card transform rounded-xl transition-all duration-300 ${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${shadowClasses}`}
>
<div class="p-4 sm:p-6"> <div class="p-4 sm:p-6">
<div class="mb-4 flex items-center justify-between sm:mb-6"> <div class="flex items-center justify-between mb-4 sm:mb-6">
<div class="flex items-center gap-2 sm:gap-4"> <div class="flex items-center gap-2 sm:gap-4">
<div class="flex transform items-center justify-center rounded-lg text-neutral-800 transition-transform group-hover:rotate-12 dark:text-neutral-200"> <div class="flex items-center justify-center rounded-lg text-primary">
<Icon name={skill.icon} class="h-8 w-8 sm:h-12 sm:w-12" /> <Icon name={skill.icon} class="h-8 w-8 sm:h-12 sm:w-12" />
</div> </div>
<h3 class="text-base font-semibold text-neutral-900 sm:text-xl dark:text-neutral-100"> <h3 class="text-neutral-900 dark:text-neutral-100 text-base font-semibold sm:text-xl">
{skill.title} {skill.title}
</h3> </h3>
</div> </div>
<span class="rounded-full bg-neutral-200 px-2 py-0.5 font-mono text-xs text-neutral-700 sm:px-2.5 sm:py-1 sm:text-sm dark:bg-neutral-800 dark:text-neutral-300"> <span class=" bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 font-mono text-xs sm:text-sm rounded-full px-2 sm:px-2.5 py-0.5 sm:py-1">
{skill.level}% {skill.level}%
</span> </span>
</div> </div>
<div class="relative bg-stone-500/20 dark:bg-stone-500/20 rounded-full h-1.5 sm:h-2 w-full overflow-hidden">
<div class="relative h-1.5 w-full overflow-hidden rounded-full bg-stone-500/20 sm:h-2 dark:bg-stone-500/20">
<div <div
class="progress-bar-animate from-steel via-bermuda to-steel absolute top-0 left-0 h-full rounded-full bg-gradient-to-r transition-all duration-1000" class="progress-bar-animate bg-linear-to-r from-steel via-bermuda to-steel absolute top-0 left-0 h-full rounded-full transition-all duration-1000"
style={`width: ${skill.level}%`} style={`width: ${skill.level}%`}
/> />
</div> </div>
<div class="flex justify-between text-secondary font-mono text-[10px] mt-1 sm:mt-2 sm:text-xs">
<div class="mt-1 flex justify-between font-mono text-[10px] text-neutral-600 sm:mt-2 sm:text-xs dark:text-neutral-400">
<span>Beginner</span> <span>Beginner</span>
<span>Advanced</span> <span>Advanced</span>
</div> </div>
</div> </div>
</div> </div>
); );
}) })}
}
</div>
<!-- Gradient overlays for smooth fade effect -->
<div
class="absolute top-0 bottom-0 left-0 z-10 w-12 bg-gradient-to-r from-neutral-200 to-transparent sm:w-24 dark:from-stone-700"
>
</div>
<div
class="absolute top-0 right-0 bottom-0 z-10 w-12 bg-gradient-to-l from-neutral-200 to-transparent sm:w-24 dark:from-stone-700"
>
</div> </div>
</div> </div>
</div> </div>
@@ -85,7 +60,6 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
<script> <script>
document.addEventListener('astro:page-load', () => { document.addEventListener('astro:page-load', () => {
// Create seamless infinite scrolling effect
function setupInfiniteScroll() { function setupInfiniteScroll() {
const cards = document.querySelectorAll('.skill-card'); const cards = document.querySelectorAll('.skill-card');
if (!cards.length) return; if (!cards.length) return;
@@ -93,7 +67,6 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
setupInfiniteScroll(); setupInfiniteScroll();
// Add hover effects to cards - only on non-touch devices
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
const cards = document.querySelectorAll('.skill-card'); const cards = document.querySelectorAll('.skill-card');
@@ -144,7 +117,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
</script> </script>
<style> <style>
/* Tech Stack Slider */ /* Specific css to enable sliding effect */
.slider-track { .slider-track {
width: fit-content; width: fit-content;
animation: scroll 40s linear infinite; animation: scroll 40s linear infinite;
@@ -155,7 +128,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
transform: translateX(0); transform: translateX(0);
} }
100% { 100% {
transform: translateX(calc(-220px * 6 - 16px * 6)); /* Card width + margin for mobile */ transform: translateX(calc(-220px * 6 - 16px * 6));
} }
} }
@@ -169,7 +142,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
transform: translateX(0); transform: translateX(0);
} }
100% { 100% {
transform: translateX(calc(-280px * 6 - 32px * 6)); /* Card width + margin for desktop */ transform: translateX(calc(-280px * 6 - 32px * 6));
} }
} }
} }
@@ -0,0 +1,39 @@
---
import WeatherCard from '@components/cards/WeatherCard.astro';
import { getFiveDayForecast } from '@support/weather';
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props;
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">
<div class="text-center max-w-2xl mx-auto mb-10 lg:mb-14">
<h1 class="smooth-reveal card-text-header block">
Weather in my Area
</h1>
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
<span class="card-text-header-description">
Five day forecast for {cityName}
</span>
</div>
</div>
{error ? (
<div class="card-base p-10 text-accent text-center">
Sorry, {error.toLowerCase()}
</div>
) : (
<div class="flex flex-wrap justify-center gap-4 lg:gap-6">
{forecastDays.map((forecastDay, index) => (
<div class={index === 3 ? "hidden min-[800px]:block" : index >= 4 ? "hidden min-[1100px]:block" : ""}>
<WeatherCard
dayName={forecastDay.dayName}
label={forecastDay.label}
icon={forecastDay.icon}
temp={forecastDay.temp}
/>
</div>
))}
</div>
)}
</section>
@@ -0,0 +1,81 @@
---
import getReadingTime from 'reading-time';
import type { Post } from '@lib/directusTypes';
import Logo from '@components/images/Logo.astro';
import { formatShortDate, formatDate } from '@support/time';
import { getDirectusImageURL } from '@/support/url';
interface Props {
post: Post;
enableCategoryLink?: boolean;
dateFormat?: 'short' | 'long';
}
const { post, enableCategoryLink = true, dateFormat = 'short' } = Astro.props;
const readingTime = getReadingTime(post.content || '');
---
<ol class="flex items-center justify-start card-text-description text-sm whitespace-nowrap gap-2 sm:gap-0 overflow-hidden">
{post.category && (
<li class="inline-flex items-center">
{enableCategoryLink ? (
<a
class="inline-flex items-center hover:card-hover-text-description overflow-hidden"
href={`/categories/${post.category.slug}`}
data-astro-prefetch
>
<div class="flex flex-row items-center shrink-0">
<div class="mr-2">
<Logo
srcLight={getDirectusImageURL(post.category.logoLight)}
srcDark={getDirectusImageURL(post.category.logoDark)}
alt={`Logo of ${post.category.title}`}
width={18}
height={18}
/>
</div>
{post.category.title}
</div>
</a>
) : (
<div class="inline-flex items-center overflow-hidden">
<div class="flex flex-row items-center shrink-0">
<div class="mr-2">
<Logo
srcLight={getDirectusImageURL(post.category.logoLight)}
srcDark={getDirectusImageURL(post.category.logoDark)}
alt={`Logo of ${post.category.title}`}
width={18}
height={18}
/>
</div>
{post.category.title}
</div>
</div>
)}
</li>
)}
<li class="inline-flex items-center">
<span class="shrink-0 mx-2">
/
</span>
</li>
<li class="inline-flex items-center">
<span class="shrink-0 overflow-hidden">
{dateFormat === 'short' ? formatShortDate(post.published_date) : formatDate(post.published_date)}
</span>
</li>
<li class="inline-flex items-center">
<span class="shrink-0 mx-2">
/
</span>
</li>
<li class="inline-flex items-center">
<span class="shrink-0 overflow-hidden">
{readingTime.minutes.toPrecision(1)} minutes
</span>
</li>
</ol>
-32
View File
@@ -1,32 +0,0 @@
---
import { Icon } from 'astro-icon/components';
const { title, url } = Astro.props;
interface Props {
title?: string;
url?: string;
}
const baseClasses =
'group group-hover inline-flex items-center justify-center gap-x-3 rounded-full px-4 py-3 text-center text-sm font-medium text-neutral-200';
const borderClasses = 'border border-transparent';
const bgColorClasses =
'bg-gitea-primary hover:bg-gitea-secondary dark:bg-gitea-secondary dark:hover:bg-gitea-primary';
const shadowClasses = 'shadow-sm';
const fontSizeClasses = '2xl:text-base';
---
<a
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${shadowClasses} ${fontSizeClasses} `}
href={url}
target="_blank"
rel="noopener noreferrer"
>
<Icon name="pajamas:gitea" class="h-4 w-4 md:h-6 md:w-6" />
{title}
<Icon
name="mdi:keyboard-arrow-right"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
</a>
-35
View File
@@ -1,35 +0,0 @@
---
import Icon from '@components/ui/icons/icon.astro';
const { title, noArrow } = Astro.props;
interface Props {
title?: string;
url?: string;
noArrow?: boolean;
addHome?: boolean;
}
const baseClasses =
'group inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-50 ring-neutral-500 transition duration-300 focus-visible:ring outline-none';
const borderClasses = 'border border-transparent';
const bgColorClasses = 'bg-steel hover:bg-sky-800 active:bg-orange-500 dark:focus:outline-none';
const disableClasses = 'disabled:pointer-events-none disabled:opacity-50';
const fontSizeClasses = '2xl:text-base';
const ringClasses = 'dark:ring-neutral-200';
---
<button
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses}`}
id="back-button"
data-astro-prefetch
>
{noArrow ? null : <Icon name="arrowLeft" />}
{title}
</button>
<script>
document.getElementById('back-button')?.addEventListener('click', () => {
window.history.back();
});
</script>
@@ -1,45 +0,0 @@
---
import { Icon } from 'astro-icon/components';
const { title, url, noArrow, addHome, addClass } = Astro.props;
interface Props {
title?: string;
url?: string;
noArrow?: boolean;
addHome?: boolean;
addClass?: string;
}
const baseClasses =
'group inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-100 transition duration-300 ';
const borderClasses = 'border border-transparent';
const bgColorClasses = 'bg-bermuda hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda';
const disableClasses = 'disabled:pointer-events-none disabled:opacity-50';
const fontSizeClasses = '2xl:text-base';
const ringClasses = 'dark:ring-neutral-200';
---
<a
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses} ${addClass}`}
href={url}
data-astro-prefetch
>
{
addHome ? (
<Icon
name="mdi:home-variant-outline"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
) : null
}
{title}
{
noArrow ? null : (
<Icon
name="mdi:keyboard-arrow-right"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
)
}
</a>
@@ -1,26 +0,0 @@
---
const { title, url } = Astro.props;
interface Props {
title?: string;
url?: string;
}
const baseClasses =
'inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-center text-sm font-medium text-neutral-600 shadow-sm outline-none ring-neutral-500 focus-visible:ring transition duration-300';
const borderClasses = 'border border-neutral-200';
const bgColorClasses = 'bg-neutral-300';
const hoverClasses = 'hover:bg-neutral-400/50 hover:text-neutral-600 active:text-neutral-700';
const disableClasses = 'disabled:pointer-events-none disabled:opacity-50';
const fontSizeClasses = '2xl:text-base';
const ringClasses = 'ring-neutral-500';
const darkClasses =
'dark:border-neutral-700 dark:bg-neutral-700 dark:text-neutral-300 dark:ring-neutral-200 dark:hover:bg-neutral-600 dark:focus:outline-none';
---
<a
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses} ${darkClasses}`}
href={url}
>
{title}
</a>
-150
View File
@@ -1,150 +0,0 @@
---
import Icon from '@components/ui/icons/icon.astro';
const { pageTitle, title = 'Share' } = Astro.props;
interface Props {
pageTitle: string;
title?: string;
}
type SocialPlatform = {
name: string;
url: string;
svg: string;
};
const socialPlatforms: SocialPlatform[] = [
{
name: 'Facebook',
url: `https://www.facebook.com/share.php?u=${Astro.url}&title=${pageTitle}`,
svg: 'facebook',
},
{
name: 'X',
url: `https://twitter.com/home/?status=${pageTitle}${Astro.url}`,
svg: 'x',
},
{
name: 'LinkedIn',
url: `https://www.linkedin.com/shareArticle?mini=true&url=${Astro.url}&title=${pageTitle}`,
svg: 'linkedIn',
},
];
---
<div class="hs-dropdown relative inline-flex [--auto-close:inside] [--placement:top-left]">
<button
id="hs-dropup"
type="button"
class="hs-dropdown-toggle inline-flex items-center gap-x-2 rounded-lg px-4 py-3 text-sm font-medium text-neutral-600 ring-neutral-500 transition duration-300 outline-none hover:bg-neutral-100 hover:text-neutral-700 focus-visible:ring dark:text-neutral-400 dark:ring-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:outline-none"
>
<Icon name="share" />
{title}
</button>
<div
class="hs-dropdown-menu duration hs-dropdown-open:opacity-100 z-10 hidden w-72 divide-y divide-neutral-200 rounded-lg bg-neutral-50 p-2 opacity-0 shadow-md transition-[opacity,margin] dark:divide-neutral-700 dark:border dark:border-neutral-700 dark:bg-neutral-800"
aria-labelledby="hs-dropup"
>
<div class="py-2 first:pt-0 last:pb-0">
{
socialPlatforms.map((platform) => (
<a
class="flex items-center gap-x-3.5 rounded-lg px-3 py-2 text-sm text-neutral-700 hover:bg-neutral-200 focus:bg-neutral-100 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700"
href={platform.url}
>
<Icon name={platform.svg} />
Share on {platform.name}
</a>
))
}
</div>
<div class="py-2 first:pt-0 last:pb-0">
<button
type="button"
class="js-clipboard hover:text-dark focus-visible:ring-secondary group inline-flex w-full items-center gap-x-3.5 rounded-lg px-3 py-2 text-sm text-neutral-700 hover:bg-neutral-200 focus:bg-neutral-100 focus:outline-none focus-visible:ring-1 focus-visible:outline-none dark:text-neutral-300 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700"
data-clipboard-success-text="Copied"
>
<svg
class="js-clipboard-default h-4 w-4 transition group-hover:rotate-6"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="8" height="4" x="8" y="2" rx="1" ry="1"></rect>
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
</svg>
<svg
class="js-clipboard-success hidden h-4 w-4 text-neutral-700 dark:text-neutral-300"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span class="js-clipboard-success-text">Copy link</span>
</button>
</div>
</div>
</div>
<!--Import the necessary Dropdown and Clipboard plugins-->
<!--https://preline.co/plugins/html/dropdown.html-->
<!--<script is:inline src="/scripts/vendor/preline/dropdown/index.js"></script>-->
<!-- https://clipboardjs.com/ -->
<!--<script is:inline src="/scripts/vendor/clipboard.min.js"></script>-->
<script is:inline>
(function () {
window.addEventListener('load', () => {
const $clipboards = document.querySelectorAll('.js-clipboard');
$clipboards.forEach((el) => {
const clipboard = new ClipboardJS(el, {
text: () => {
return window.location.href;
},
});
clipboard.on('success', () => {
const $default = el.querySelector('.js-clipboard-default');
const $success = el.querySelector('.js-clipboard-success');
const $successText = el.querySelector('.js-clipboard-success-text');
const successText = el.dataset.clipboardSuccessText || '';
let oldSuccessText;
if ($successText) {
oldSuccessText = $successText.textContent;
$successText.textContent = successText;
}
if ($default && $success) {
$default.style.display = 'none';
$success.style.display = 'block';
}
setTimeout(() => {
if ($successText && oldSuccessText) {
$successText.textContent = oldSuccessText;
}
if ($default && $success) {
$success.style.display = '';
$default.style.display = '';
}
}, 800);
});
});
});
})();
</script>
@@ -1,45 +0,0 @@
---
import { Icon } from 'astro-icon/components';
interface Props {
title?: string;
description?: string;
url?: string;
icon?: string;
}
const { title, description, url, icon } = Astro.props;
const baseClasses = 'smooth-reveal-2 group group-hover flex flex-col ';
const borderClasses = 'border border-neutral-100 dark:border-stone-500/20';
const bgColorClasses =
'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
const shadowClasses = 'shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg';
---
<div class={`${baseClasses}`}>
<a
class={`rounded-xl duration-300 transition-all h-30 ${borderClasses} ${bgColorClasses} ${shadowClasses}`}
href={url}
data-astro-prefetch
>
<div class="p-4 md:p-5">
<div class="flex">
<Icon
name={icon}
class="group-hover:text-steel dark:group-hover:text-bermuda h-6 w-6 text-neutral-600 transition-all duration-300 md:h-8 md:w-8 dark:text-neutral-200"
/>
<div class="ms-5 grow">
<span
class="group-hover:text-steel dark:group-hover:text-bermuda block text-lg font-bold text-neutral-600 transition-all duration-300 dark:text-neutral-300"
>
{title}
</span>
<span class="mt-1 block text-neutral-500 dark:text-neutral-400">
{description}
</span>
</div>
</div>
</div>
</a>
</div>
-39
View File
@@ -1,39 +0,0 @@
---
import { Icons } from './icons.ts';
interface Path {
d: string;
class?: string;
}
const { name } = Astro.props;
const icon = (Icons as any)[name] || {};
const paths: Path[] = icon.paths || [];
---
{
icon ? (
<svg
class={icon.class}
height={icon.height}
viewBox={icon.viewBox}
width={icon.width}
fill={icon.fill}
clip-rule={icon.clipRule}
fill-rule={icon.fillRule}
stroke={icon.stroke}
stroke-width={icon.strokeWidth}
stroke-linecap={icon.strokeLinecap}
stroke-linejoin={icon.strokeLinejoin}
>
<title>{icon.title}</title>
<circle cx={icon.circleCx} cy={icon.circleCy} r={icon.circleR} />
{paths.map((path) => (
<path d={path.d} class={path.class || ''} />
))}
</svg>
) : (
'Icon not found'
)
}
-573
View File
@@ -1,573 +0,0 @@
export const Icons = {
groups: {
paths: [
{
d: 'm150-400 82-80-82-82-80 82 80 80Zm573-10 87-140 88 140H723Zm-243-70q-50 0-85-35t-35-85q0-51 35-85.5t85-34.5q51 0 85.5 34.5T600-600q0 50-34.5 85T480-480Zm.351-180Q455-660 437.5-642.851t-17.5 42.5Q420-575 437.351-557.5t43 17.5Q506-540 523-557.351t17-43Q540-626 522.851-643t-42.5-17ZM480-600ZM0-240v-53q0-39.464 42-63.232T150.398-380q12.158 0 23.38.5T196-377.273q-8 17.273-12 34.842-4 17.57-4 37.431v65H0Zm240 0v-65q0-65 66.5-105T480-450q108 0 174 40t66 105v65H240Zm570-140q67.5 0 108.75 23.768T960-293v53H780v-65q0-19.861-3.5-37.431Q773-360 765-377.273q11-1.727 22.171-2.227 11.172-.5 22.829-.5Zm-330.2-10Q400-390 350-366q-50 24-50 61v5h360v-6q0-36-49.5-60t-130.7-24Zm.2 90Z',
},
],
class: 'mt-1 h-8 w-8 flex-shrink-0 fill-orange-400 dark:fill-orange-300',
width: 48,
height: 48,
viewBox: '0 -960 960 960',
},
books: {
paths: [
{
d: 'M343-420h225v-60H343v60Zm0-90h395v-60H343v60Zm0-90h395v-60H343v60Zm-83 400q-24 0-42-18t-18-42v-560q0-24 18-42t42-18h560q24 0 42 18t18 42v560q0 24-18 42t-42 18H260Zm0-60h560v-560H260v560ZM140-80q-24 0-42-18t-18-42v-620h60v620h620v60H140Zm120-740v560-560Z',
},
],
class: 'mt-1 h-8 w-8 flex-shrink-0 fill-orange-400 dark:fill-orange-300',
width: 48,
height: 48,
viewBox: '0 -960 960 960',
},
verified: {
paths: [
{
d: 'm346-60-76-130-151-31 17-147-96-112 96-111-17-147 151-31 76-131 134 62 134-62 77 131 150 31-17 147 96 111-96 112 17 147-150 31-77 130-134-62-134 62Zm27-79 107-45 110 45 67-100 117-30-12-119 81-92-81-94 12-119-117-28-69-100-108 45-110-45-67 100-117 28 12 119-81 94 81 92-12 121 117 28 70 100Zm107-341Zm-43 133 227-225-45-41-182 180-95-99-46 45 141 140Z',
},
],
class: 'mt-1 h-8 w-8 flex-shrink-0 fill-orange-400 dark:fill-orange-300',
width: 48,
height: 48,
viewBox: '0 -960 960 960',
},
frame: {
paths: [
{
d: 'M480-480q-51 0-85.5-34.5T360-600q0-50 34.5-85t85.5-35q50 0 85 35t35 85q0 51-35 85.5T480-480Zm-.351-60Q505-540 522.5-557.149t17.5-42.5Q540-625 522.649-642.5t-43-17.5Q454-660 437-642.649t-17 43Q420-574 437.149-557t42.5 17ZM240-240v-76q0-27 17.5-47.5T300-397q42-22 86.943-32.5 44.942-10.5 93-10.5Q528-440 573-429.5t87 32.5q25 13 42.5 33.5T720-316v76H240Zm240-140q-47.546 0-92.773 13T300-328v28h360v-28q-42-26-87.227-39-45.227-13-92.773-13Zm0-220Zm0 300h180-360 180ZM140-80q-24 0-42-18t-18-42v-172h60v172h172v60H140ZM80-648v-172q0-24 18-42t42-18h172v60H140v172H80ZM648-80v-60h172v-172h60v172q0 24-18 42t-42 18H648Zm172-568v-172H648v-60h172q24 0 42 18t18 42v172h-60Z',
},
],
class: 'mt-1 h-8 w-8 flex-shrink-0 fill-orange-400 dark:fill-orange-300',
width: 48,
height: 48,
viewBox: '0 -960 960 960',
},
tools: {
paths: [
{
d: 'M764-80q-6 0-11-2t-10-7L501-331q-5-5-7-10t-2-11q0-6 2-11t7-10l85-85q5-5 10-7t11-2q6 0 11 2t10 7l242 242q5 5 7 10t2 11q0 6-2 11t-7 10l-85 85q-5 5-10 7t-11 2Zm0-72 43-43-200-200-43 43 200 200ZM195-80q-6 0-11.5-2T173-89l-84-84q-5-5-7-10.5T80-195q0-6 2-11t7-10l225-225h85l38-38-175-175h-57L80-779l99-99 125 125v57l175 175 130-130-67-67 56-56H485l-18-18 128-128 18 18v113l56-56 169 169q15 15 23.5 34.5T870-600q0 20-6.5 38.5T845-528l-85-85-56 56-52-52-211 211v84L216-89q-5 5-10 7t-11 2Zm0-72 200-200v-43h-43L152-195l43 43Zm0 0-43-43 22 21 21 22Zm569 0 43-43-43 43Z',
},
],
class:
'mt-2 h-6 w-6 flex-shrink-0 fill-neutral-700 hs-tab-active:fill-orange-400 dark:fill-neutral-300 dark:hs-tab-active:fill-orange-300 md:h-7 md:w-7',
width: 48,
height: 48,
viewBox: '0 -960 960 960',
},
dashboard: {
paths: [
{
d: 'M510-570v-270h330v270H510ZM120-450v-390h330v390H120Zm390 330v-390h330v390H510Zm-390 0v-270h330v270H120Zm60-390h210v-270H180v270Zm390 330h210v-270H570v270Zm0-450h210v-150H570v150ZM180-180h210v-150H180v150Zm210-330Zm180-120Zm0 180ZM390-330Z',
},
],
class:
'mt-2 h-6 w-6 flex-shrink-0 fill-neutral-700 hs-tab-active:fill-orange-400 dark:fill-neutral-300 dark:hs-tab-active:fill-orange-300 md:h-7 md:w-7',
width: 48,
height: 48,
viewBox: '0 -960 960 960',
},
house: {
paths: [
{
d: 'M2.25 12l8.954-8.955c.44-.439 1.152-.439 1.591 0L21.75 12M4.5 9.75v10.125c0 .621.504 1.125 1.125 1.125H9.75v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21h4.125c.621 0 1.125-.504 1.125-1.125V9.75M8.25 21h8.25',
},
],
class: 'h-4 w-4 flex-shrink-0 md:h-5 md:w-5',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
home: {
paths: [
{
d: 'M8.25 21v-4.875c0-.621.504-1.125 1.125-1.125h2.25c.621 0 1.125.504 1.125 1.125V21m0 0h4.5V3.545M12.75 21h7.5V10.75M2.25 21h1.5m18 0h-18M2.25 9l4.5-1.636M18.75 3l-1.5.545m0 6.205 3 1m1.5.5-1.5-.5M6.75 7.364V3h-3v18m3-13.636 10.5-3.819',
},
],
class:
'h-6 w-6 flex-shrink-0 group-hover:text-steel dark:group-hover:text-steel transition-all duration-200 text-neutral-600 dark:text-neutral-300 md:h-7 md:w-7',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
arrowUp: {
paths: [
{
d: 'm5 12 7-7 7 7',
},
{
d: 'M12 19V5',
},
],
class: 'h-5 w-5 flex-shrink-0 text-orange-400 dark:text-orange-300',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
checkCircle: {
paths: [
{
d: 'M10 18a8 8 0 100-16 8 8 0 000 16zM13.707 8.293a1 1 0 00-1.414-1.414L9 10.586l-1.293-1.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z',
},
],
class: 'h-5 w-5 shrink-0',
viewBox: '0 0 20 20',
fill: 'currentColor',
fillRule: 'evenodd',
clipRule: 'evenodd',
},
bookmark: {
paths: [
{
d: 'M21 8.25c0-2.485-2.099-4.5-4.688-4.5-1.935 0-3.597 1.126-4.312 2.733-.715-1.607-2.377-2.733-4.313-2.733C5.1 3.75 3 5.765 3 8.25c0 7.22 9 12 9 12s9-4.78 9-12Z',
class:
'fill-current text-neutral-500 transition duration-300 group-hover:text-red-400 group-hover:dark:text-red-400',
},
],
class: 'h-6 w-6 fill-none transition duration-300',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
arrowRight: {
paths: [
{
d: 'm9 18 6-6-6-6',
},
],
class: 'h-4 w-4 flex-shrink-0 transition duration-300 group-hover:translate-x-1',
width: 20,
height: 20,
viewBox: '0 0 22 22',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
arrowLeft: {
paths: [
{
d: 'm15 18-6-6 6-6',
},
],
class: 'h-4 w-4 flex-shrink-0 transition duration-300 group-hover:-translate-x-1',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
facebook: {
paths: [
{
d: 'M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z',
},
],
class: 'size-4 flex-shrink-0 fill-current',
viewBox: '0 0 24 24',
stroke: 'currentColor',
},
x: {
paths: [
{
d: 'M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z',
},
],
class: 'size-4 flex-shrink-0 fill-current',
viewBox: '0 0 24 24',
stroke: 'currentColor',
},
linkedIn: {
paths: [
{
d: 'M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z',
},
],
class: 'size-4 flex-shrink-0 fill-current',
viewBox: '0 0 24 24',
stroke: 'currentColor',
},
share: {
paths: [
{
d: 'M7.217 10.907a2.25 2.25 0 1 0 0 2.186m0-2.186c.18.324.283.696.283 1.093s-.103.77-.283 1.093m0-2.186 9.566-5.314m-9.566 7.5 9.566 5.314m0 0a2.25 2.25 0 1 0 3.935 2.186 2.25 2.25 0 0 0-3.935-2.186Zm0-12.814a2.25 2.25 0 1 0 3.933-2.185 2.25 2.25 0 0 0-3.933 2.185Z',
},
],
class: 'h-4 w-4 group-hover:text-neutral-700',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
github: {
paths: [
{
d: 'M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.012 8.012 0 0 0 16 8c0-4.42-3.58-8-8-8z',
},
],
class: 'w-4.5 h-4.5 transition flex-shrink-0 text-neutral-700 duration-300',
width: 16,
height: 16,
viewBox: '0 0 16 16',
fill: 'currentColor',
},
gitea: {
paths: [
{
d: 'M4.209 4.603c-.247 0-.525.02-.84.088-.333.07-1.28.283-2.054 1.027C-.403 7.25.035 9.685.089 10.052c.065.446.263 1.687 1.21 2.768 1.749 2.141 5.513 2.092 5.513 2.092s.462 1.103 1.168 2.119c.955 1.263 1.936 2.248 2.89 2.367 2.406 0 7.212-.004 7.212-.004s.458.004 1.08-.394c.535-.324 1.013-.893 1.013-.893s.492-.527 1.18-1.73c.21-.37.385-.729.538-1.068 0 0 2.107-4.471 2.107-8.823-.042-1.318-.367-1.55-.443-1.627-.156-.156-.366-.153-.366-.153s-4.475.252-6.792.306c-.508.011-1.012.023-1.512.027v4.474l-.634-.301c0-1.39-.004-4.17-.004-4.17-1.107.016-3.405-.084-3.405-.084s-5.399-.27-5.987-.324c-.187-.011-.401-.032-.648-.032zm.354 1.832h.111s.271 2.269.6 3.597C5.549 11.147 6.22 13 6.22 13s-.996-.119-1.641-.348c-.99-.324-1.409-.714-1.409-.714s-.73-.511-1.096-1.52C1.444 8.73 2.021 7.7 2.021 7.7s.32-.859 1.47-1.145c.395-.106.863-.12 1.072-.12zm8.33 2.554c.26.003.509.127.509.127l.868.422-.529 1.075a.686.686 0 0 0-.614.359.685.685 0 0 0 .072.756l-.939 1.924a.69.69 0 0 0-.66.527.687.687 0 0 0 .347.763.686.686 0 0 0 .867-.206.688.688 0 0 0-.069-.882l.916-1.874a.667.667 0 0 0 .237-.02.657.657 0 0 0 .271-.137 8.826 8.826 0 0 1 1.016.512.761.761 0 0 1 .286.282c.073.21-.073.569-.073.569-.087.29-.702 1.55-.702 1.55a.692.692 0 0 0-.676.477.681.681 0 1 0 1.157-.252c.073-.141.141-.282.214-.431.19-.397.515-1.16.515-1.16.035-.066.218-.394.103-.814-.095-.435-.48-.638-.48-.638-.467-.301-1.116-.58-1.116-.58s0-.156-.042-.27a.688.688 0 0 0-.148-.241l.516-1.062 2.89 1.401s.48.218.583.619c.073.282-.019.534-.069.657-.24.587-2.1 4.317-2.1 4.317s-.232.554-.748.588a1.065 1.065 0 0 1-.393-.045l-.202-.08-4.31-2.1s-.417-.218-.49-.596c-.083-.31.104-.691.104-.691l2.073-4.272s.183-.37.466-.497a.855.855 0 0 1 .35-.077z',
},
],
class: 'w-6 h-6 transition flex-shrink-0 duration-300',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'currentColor',
},
arrowRightStatic: {
paths: [
{
d: 'm9 18 6-6-6-6',
},
],
class: 'size-4 flex-shrink-0',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
openInNew: {
paths: [
{
d: 'm4.5 19.5 15-15m0 0H8.25m11.25 0v11.25',
},
],
class: 'ml-0.5 w-3 h-3 md:w-4 md:h-4 inline pb-0.5',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '3',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
accordionNotActive: {
paths: [
{
d: 'm6 9 6 6 6-6',
},
],
class:
'block h-5 w-5 flex-shrink-0 text-neutral-600 group-hover:text-neutral-500 hs-accordion-active:hidden dark:text-neutral-400',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
accordionActive: {
paths: [
{
d: 'm18 15-6-6-6 6',
},
],
class:
'hidden h-5 w-5 flex-shrink-0 text-neutral-600 group-hover:text-neutral-500 hs-accordion-active:block dark:text-neutral-400',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
xFooter: {
paths: [
{
d: 'M18.901 1.153h3.68l-8.04 9.19L24 22.846h-7.406l-5.8-7.584-6.638 7.584H.474l8.6-9.83L0 1.154h7.594l5.243 6.932ZM17.61 20.644h2.039L6.486 3.24H4.298Z',
},
],
class: 'h-4 w-4 flex-shrink-0 fill-current text-neutral-700 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'currentColor',
title: 'Twitter',
},
facebookFooter: {
paths: [
{
d: 'M9.101 23.691v-7.98H6.627v-3.667h2.474v-1.58c0-4.085 1.848-5.978 5.858-5.978.401 0 .955.042 1.468.103a8.68 8.68 0 0 1 1.141.195v3.325a8.623 8.623 0 0 0-.653-.036 26.805 26.805 0 0 0-.733-.009c-.707 0-1.259.096-1.675.309a1.686 1.686 0 0 0-.679.622c-.258.42-.374.995-.374 1.752v1.297h3.919l-.386 2.103-.287 1.564h-3.246v8.245C19.396 23.238 24 18.179 24 12.044c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.628 3.874 10.35 9.101 11.647Z',
},
],
class: 'h-4 w-4 flex-shrink-0 fill-current text-neutral-700 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'currentColor',
title: 'Facebook',
},
githubFooter: {
paths: [
{
d: 'M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12',
},
],
class: 'h-4 w-4 flex-shrink-0 fill-current text-neutral-700 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'currentColor',
title: 'GitHub',
},
googleFooter: {
paths: [
{
d: 'M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z',
},
],
class: 'h-4 w-4 flex-shrink-0 fill-current text-neutral-700 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'currentColor',
title: 'Google',
},
slackFooter: {
paths: [
{
d: 'M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z',
},
],
class: 'h-4 w-4 flex-shrink-0 fill-current text-neutral-700 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'currentColor',
title: 'Slack',
},
quotation: {
paths: [
{
d: 'M7.39762 10.3C7.39762 11.0733 7.14888 11.7 6.6514 12.18C6.15392 12.6333 5.52552 12.86 4.76621 12.86C3.84979 12.86 3.09047 12.5533 2.48825 11.94C1.91222 11.3266 1.62421 10.4467 1.62421 9.29999C1.62421 8.07332 1.96459 6.87332 2.64535 5.69999C3.35231 4.49999 4.33418 3.55332 5.59098 2.85999L6.4943 4.25999C5.81354 4.73999 5.26369 5.27332 4.84476 5.85999C4.45201 6.44666 4.19017 7.12666 4.05926 7.89999C4.29491 7.79332 4.56983 7.73999 4.88403 7.73999C5.61716 7.73999 6.21938 7.97999 6.69067 8.45999C7.16197 8.93999 7.39762 9.55333 7.39762 10.3ZM14.6242 10.3C14.6242 11.0733 14.3755 11.7 13.878 12.18C13.3805 12.6333 12.7521 12.86 11.9928 12.86C11.0764 12.86 10.3171 12.5533 9.71484 11.94C9.13881 11.3266 8.85079 10.4467 8.85079 9.29999C8.85079 8.07332 9.19117 6.87332 9.87194 5.69999C10.5789 4.49999 11.5608 3.55332 12.8176 2.85999L13.7209 4.25999C13.0401 4.73999 12.4903 5.27332 12.0713 5.85999C11.6786 6.44666 11.4168 7.12666 11.2858 7.89999C11.5215 7.79332 11.7964 7.73999 12.1106 7.73999C12.8437 7.73999 13.446 7.97999 13.9173 8.45999C14.3886 8.93999 14.6242 9.55333 14.6242 10.3Z',
},
],
class:
'absolute start-0 top-0 h-16 w-16 -translate-x-6 -translate-y-8 transform text-neutral-300 dark:text-neutral-700',
width: 16,
height: 16,
viewBox: '0 0 16 16',
fill: 'currentColor',
},
question: {
paths: [
{
d: 'M9.879 7.519c1.171-1.025 3.071-1.025 4.242 0 1.172 1.025 1.172 2.687 0 3.712-.203.179-.43.326-.67.442-.745.361-1.45.999-1.45 1.827v.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Zm-9 5.25h.008v.008H12v-.008Z',
},
],
class: 'mt-1.5 h-6 w-6 flex-shrink-0 text-neutral-600 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
chatBubble: {
paths: [
{
d: 'M20.25 8.511c.884.284 1.5 1.128 1.5 2.097v4.286c0 1.136-.847 2.1-1.98 2.193-.34.027-.68.052-1.02.072v3.091l-3-3c-1.354 0-2.694-.055-4.02-.163a2.115 2.115 0 0 1-.825-.242m9.345-8.334a2.126 2.126 0 0 0-.476-.095 48.64 48.64 0 0 0-8.048 0c-1.131.094-1.976 1.057-1.976 2.192v4.286c0 .837.46 1.58 1.155 1.951m9.345-8.334V6.637c0-1.621-1.152-3.026-2.76-3.235A48.455 48.455 0 0 0 11.25 3c-2.115 0-4.198.137-6.24.402-1.608.209-2.76 1.614-2.76 3.235v6.226c0 1.621 1.152 3.026 2.76 3.235.577.075 1.157.14 1.74.194V21l4.155-4.155',
},
],
class: 'mt-1.5 h-6 w-6 flex-shrink-0 text-neutral-600 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
mapPin: {
paths: [
{
d: 'M15 10.5a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z',
},
{
d: 'M19.5 10.5c0 7.142-7.5 11.25-7.5 11.25S4.5 17.642 4.5 10.5a7.5 7.5 0 1 1 15 0Z',
},
],
class: 'mt-1.5 h-6 w-6 flex-shrink-0 text-neutral-600 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
envelopeOpen: {
paths: [
{
d: 'M21.75 9v.906a2.25 2.25 0 0 1-1.183 1.981l-6.478 3.488M2.25 9v.906a2.25 2.25 0 0 0 1.183 1.981l6.478 3.488m8.839 2.51-4.66-2.51m0 0-1.023-.55a2.25 2.25 0 0 0-2.134 0l-1.022.55m0 0-4.661 2.51m16.5 1.615a2.25 2.25 0 0 1-2.25 2.25h-15a2.25 2.25 0 0 1-2.25-2.25V8.844a2.25 2.25 0 0 1 1.183-1.981l7.5-4.039a2.25 2.25 0 0 1 2.134 0l7.5 4.039a2.25 2.25 0 0 1 1.183 1.98V19.5Z',
},
],
class: 'mt-1.5 h-6 w-6 flex-shrink-0 text-neutral-600 dark:text-neutral-400',
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
earth: {
paths: [
{
d: 'm20.893 13.393-1.135-1.135a2.252 2.252 0 0 1-.421-.585l-1.08-2.16a.414.414 0 0 0-.663-.107.827.827 0 0 1-.812.21l-1.273-.363a.89.89 0 0 0-.738 1.595l.587.39c.59.395.674 1.23.172 1.732l-.2.2c-.212.212-.33.498-.33.796v.41c0 .409-.11.809-.32 1.158l-1.315 2.191a2.11 2.11 0 0 1-1.81 1.025 1.055 1.055 0 0 1-1.055-1.055v-1.172c0-.92-.56-1.747-1.414-2.089l-.655-.261a2.25 2.25 0 0 1-1.383-2.46l.007-.042a2.25 2.25 0 0 1 .29-.787l.09-.15a2.25 2.25 0 0 1 2.37-1.048l1.178.236a1.125 1.125 0 0 0 1.302-.795l.208-.73a1.125 1.125 0 0 0-.578-1.315l-.665-.332-.091.091a2.25 2.25 0 0 1-1.591.659h-.18c-.249 0-.487.1-.662.274a.931.931 0 0 1-1.458-1.137l1.411-2.353a2.25 2.25 0 0 0 .286-.76m11.928 9.869A9 9 0 0 0 8.965 3.525m11.928 9.868A9 9 0 1 1 8.965 3.525',
},
],
class: 'w-4 h-4 flex-shrink-0',
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '1.5',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
party: {
paths: [
{
d: 'M5.8 11.3 2 22l10.7-3.79',
},
{
d: 'M4 3h.01',
},
{
d: 'M22 8h.01',
},
{
d: 'M15 2h.01',
},
{
d: 'M22 20h.01',
},
{
d: 'm22 2-2.24.75a2.9 2.9 0 0 0-1.96 3.12v0c.1.86-.57 1.63-1.45 1.63h-.38c-.86 0-1.6.6-1.76 1.44L14 10',
},
{
d: 'm22 13-.82-.33c-.86-.34-1.82.2-1.98 1.11v0c-.11.7-.72 1.22-1.43 1.22H17',
},
{
d: 'm11 2 .33.82c.34.86-.2 1.82-1.11 1.98v0C9.52 4.9 9 5.52 9 6.23V7',
},
{
d: 'M11 13c1.93 1.93 2.83 4.17 2 5-.83.83-3.07-.07-5-2-1.93-1.93-2.83-4.17-2-5 .83-.83 3.07.07 5 2Z',
},
],
class:
'w-6 h-6 group-hover:text-steel dark:group-hover:text-steel transition-all duration-200 text-neutral-600 dark:text-neutral-300',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
email: {
paths: [
{
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',
},
],
class:
'w-8 h-8 group-hover:text-steel dark:group-hover:text-steel transition-all duration-200 text-neutral-600 dark:text-neutral-300',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
sun: {
paths: [
{
d: 'M12 1v2M12 21v2M4.2 4.2l1.4 1.4M18.4 18.4l1.4 1.4M1 12h2M21 12h2M4.2 19.8l1.4-1.4M18.4 5.6l1.4-1.4',
},
],
circleCx: '12',
circleCy: '12',
circleR: '5',
class:
'icon-light absolute h-5 w-5 scale-100 rotate-0 text-neutral-800 transition-all duration-500 dark:scale-0 dark:-rotate-90 dark:text-neutral-200',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
moon: {
paths: [
{
d: 'M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z',
},
],
class:
'icon-dark absolute h-5 w-5 scale-0 rotate-90 text-neutral-800 transition-all duration-500 dark:scale-100 dark:rotate-0 dark:text-neutral-200',
width: 24,
height: 24,
viewBox: '0 0 24 24',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
arrow: {
paths: [
{
d: 'M5.22 14.78a.75.75 0 001.06 0l7.22-7.22v5.69a.75.75 0 001.5 0v-7.5a.75.75 0 00-.75-.75h-7.5a.75.75 0 000 1.5h5.69l-7.22 7.22a.75.75 0 000 1.06z',
},
],
class:
'icon-dark absolute h-5 w-5 scale-0 rotate-90 text-neutral-800 transition-all duration-500 dark:scale-100 dark:rotate-0 dark:text-neutral-200',
width: 16,
height: 16,
viewBox: '0 0 20 20',
fill: 'none',
strokeWidth: '2',
strokeLinecap: 'round',
strokeLinejoin: 'round',
stroke: 'currentColor',
},
};
-17
View File
@@ -1,17 +0,0 @@
---
import { Image } from 'astro:assets';
import { ImageMetadata } from 'astro';
import { blurStyle } from '@support/image';
interface FsPathImage extends ImageMetadata {
fsPath?: string;
}
const props = Astro.props;
const image = props.src as FsPathImage;
const showBlur = !props.disableBlur;
const blurCSS = image.fsPath && showBlur ? await blurStyle(image.fsPath) : {};
---
<Image {...props} style={blurCSS} inferSize={true} />
-129
View File
@@ -1,129 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import { readItems } from '@directus/sdk';
import type { Education } from '@lib/directusTypes';
import directus from '@lib/directus';
const education = await directus.request(
readItems('site_education', {
fields: ['*'],
sort: ['-graduationDate'],
})
);
const certificate = await directus.request(
readItems('site_certificate', {
fields: ['*'],
sort: ['-issuerDate'],
})
);
const baseClasses = ' rounded-xl flex flex-col';
const borderClasses = 'border border-neutral-100 dark:border-stone-500/20';
const bgColorClasses =
'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
const shadowClasses = 'shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg';
---
<section class:list={['order-first flex flex-col gap-4', Astro.props.className]}>
<h3
class="smooth-reveal-1 relative flex w-full items-center gap-3 pb-5 text-5xl text-neutral-800 dark:text-neutral-200"
>
Education
</h3>
<div class="ml-8">
<h4 class="smooth-reveal-1 pt-5 text-2xl font-semibold text-neutral-800 dark:text-neutral-200">
University
</h4>
<ul class="space-y-4 py-3">
{
education.map(({ institution, area, url }) => {
return (
<div class="smooth-reveal-cards mt-4 grid grid-cols-3 gap-4 rounded-xl">
<div>
<div
class={`p-4 transition-all duration-300 md:p-5 ${shadowClasses} ${bgColorClasses} ${baseClasses} ${borderClasses}`}
>
<h3 class="flex flex-row text-lg font-bold text-neutral-800 dark:text-neutral-200">
<Icon
name="mdi:university-outline"
class="mr-2 h-3 w-3 translate-y-1 md:h-5 md:w-5"
/>
{institution}
</h3>
<p class="mt-2 ml-7 text-xs font-medium text-neutral-600 uppercase dark:text-neutral-400">
{area}
</p>
<div class="ml-6 flex">
<a
class="group group-hover relative inline-block gap-x-1 rounded-lg border border-transparent disabled:pointer-events-none disabled:opacity-50"
href={url}
>
<div class="group-hover:text-steel dark:group-hover:text-bermuda transition-text relative z-10 mx-auto flex min-h-[44px] items-center text-sm font-semibold text-neutral-600 decoration-2 duration-300 sm:mx-0 sm:mt-4 dark:text-neutral-300">
<span class="relative inline-block overflow-hidden"> Visit Page </span>
<Icon
name="mdi:keyboard-arrow-right"
class="translate-y-0.5 transition duration-300 group-hover:translate-x-1"
/>
</div>
</a>
</div>
</div>
</div>
</div>
);
})
}
</ul>
</div>
{
certificate.length > 0 && (
<div class="ml-8">
<h4 class="smooth-reveal-1 pt-8 text-2xl font-semibold text-neutral-800 dark:text-neutral-200">
Certificates
</h4>
<ul class="space-y-4 py-3">
{certificate.map(({ name, issuer, url }) => {
return (
<div class="smooth-reveal-cards mt-4 grid grid-cols-3 gap-4 rounded-xl">
<div>
<div
class={`p-4 transition-all duration-300 md:p-5 ${shadowClasses} ${bgColorClasses} ${baseClasses} ${borderClasses}`}
>
<h3 class="flex flex-row text-lg font-bold text-neutral-800 dark:text-neutral-200">
<Icon
name="mdi:script-text-outline"
class="mr-2 h-3 w-3 translate-y-1 md:h-5 md:w-5"
/>
{name}
</h3>
<p class="mt-2 ml-7 text-xs font-medium text-neutral-600 uppercase dark:text-neutral-400">
{issuer}
</p>
<div class="ml-6 flex">
<a
class="group group-hover relative inline-block gap-x-1 rounded-lg border border-transparent disabled:pointer-events-none disabled:opacity-50"
href={url}
>
<div class="group-hover:text-steel dark:group-hover:text-bermuda transition-text relative z-10 mx-auto flex min-h-[44px] items-center text-sm font-semibold text-neutral-600 decoration-2 duration-300 sm:mx-0 sm:mt-4 dark:text-neutral-300">
<span class="relative inline-block overflow-hidden"> Visit Page </span>
<Icon
name="mdi:keyboard-arrow-right"
class="translate-y-0.5 transition duration-300 group-hover:translate-x-1"
/>
</div>
</a>
</div>
</div>
</div>
</div>
);
})}
</ul>
</div>
)
}
</section>
-152
View File
@@ -1,152 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import { readItems } from '@directus/sdk';
import type { Experience } from '@lib/directusTypes';
import directus from '@lib/directus';
const experiences = await directus.request(
readItems('site_experience', {
fields: ['*'],
sort: ['-endDate'],
})
);
---
<section
class:list={['flex flex-col gap-4', Astro.props.className]}
>
<h3 class="relative smooth-reveal-1 flex w-full items-center gap-3 pb-10 text-5xl text-neutral-800 dark:text-neutral-200">Experience</h3>
<ul class="ml-8 w-full flex flex-col">
{
experiences.map(
(experience: Experience) => {
const startYear = new Date(experience.startDate).getFullYear();
const endYear = experience.endDate != null ? new Date(experience.endDate).getFullYear() : 'Present';
return (
<li class="relative">
<div class="group smooth-reveal-2 relative grid pb-1 transition-all sm:grid-cols-18 sm:gap-8 md:gap-6 lg:hover:!opacity-100">
<header class="relative mt-1 text-lg font-semibold sm:col-span-3 text-neutral-800 dark:text-neutral-200">
<time datetime={experience.startDate} data-title={experience.startDate}>
{startYear}
</time>{' '}
-{' '}
<time datetime={experience.endDate} data-title={experience.endDate}>
{endYear}
</time>
</header>
<div class="relative flex flex-col pb-6 before:absolute before:mt-8 before:-ml-6 before:h-full before:w-px before:bg-stone-400 sm:col-span-12">
<div class="absolute mt-4 h-2 w-2 -translate-x-[1.71rem] rounded-full bg-stone-400" />
<h3>
<div
class="inline-flex items-center text-2xl leading-tight font-semibold"
aria-label="{position} - {company}"
>
<span class="text-neutral-800 dark:text-neutral-200">
{experience.position} <span>@</span>
{experience.url ? (
<a
class="hover:text-steel dark:hover:text-bermuda"
href={experience.url}
title={`Ver ${experience.name}`}
target="_blank"
>
{experience.name}
</a>
) : (
<span>{experience.name}</span>
)}
</span>
</div>
</h3>
{(experience.location || experience.location_type) && (
<div class="text-sm text-neutral-600 dark:text-neutral-400">
{experience.location} {experience.location && experience.location_type && '-'} {experience.location_type}
</div>
)}
<div class="text-md mt-4 flex flex-col gap-4" x-data="{ expanded: false }">
{experience.summary && (
<div class="flex flex-col gap-1">
<h4 class="font-semibold text-neutral-800 dark:text-neutral-200">Summary:</h4>
<ul class="flex list-disc flex-col gap-2 text-neutral-700 dark:text-neutral-400 [&>li]:ml-4">
{Array.isArray(experience.summary) ? (
experience.summary.map((item) => ({ item }))
) : (
<li class="marker:text-steel dark:marker:text-bermuda">{experience.summary}</li>
)}
</ul>
</div>
)}
{(experience.responsibilities || experience.achievements) && (
<div class="relative flex flex-col gap-4 max-sm:!h-auto md:after:absolute md:after:bottom-0 md:after:h-12 md:after:w-full md:after:bg-gradient-to-t md:after:from-neutral-200 dark:md:after:from-stone-700 md:after:content-[''] " :class="expanded ? 'after:hidden' : ''" x-show="expanded" x-collapse.min.50px>
{experience.responsibilities && (
<div class="flex flex-col gap-1">
<h4 class="font-semibold text-neutral-800 dark:text-neutral-200">Responsibilities:</h4>
<ul class="text-neutral-700 dark:text-neutral-400 [&>li]:ml-4 flex list-disc flex-col gap-2">
{experience.responsibilities.map(responsibility => (
<li class="marker:text-steel dark:marker:text-bermuda">{responsibility}</li>
))}
</ul>
</div>
)}
{experience.achievements && (
<div class="flex flex-col gap-1">
<h4 class="font-semibold text-neutral-800 dark:text-neutral-200">Achievements:</h4>
<ul class="text-neutral-700 dark:text-neutral-400 [&>li]:ml-4 flex list-disc flex-col gap-2">
{experience.achievements.map(achievement => (
<li class="marker:text-steel dark:marker:text-bermuda">{achievement}</li>
))}
</ul>
</div>
)}
</div>
<button @click="expanded = ! expanded" class="group/more w-fit cursor-pointer items-center justify-center gap-1.5 text-xs underline text-neutral-700 dark:text-neutral-300 hover:text-neutral-900 dark:hover:text-neutral-400 transition-all flex">
<span x-text="expanded ? 'Show less' : 'Show more'">Show more</span>
<svg
class="h-4 w-4 duration-200 ease-out group-hover/more:translate-y-0.5"
:class="{ 'rotate-180': expanded }"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</button>
<ul class="flex print:hidden flex-wrap gap-2" aria-label="Technologies used">
{experience.skills && experience.skills.map(skill => {
const iconName = skill.toLowerCase();
return (
<li class="bg-steel/20 border-steel/20 text-neutral-800 dark:bg-bermuda/20 dark:border-bermuda/20 dark:text-neutral-200 flex gap-1 items-center border-solid border rounded-md px-2 py-0.5 text-xs">
<Icon name={`mdi:${iconName}`} /> <span>{skill}</span>
</li>
)
})}
</ul>
)}
</div>
</div>
</div>
</li>
);
}
)
}
</ul>
</section>
<!-- Alpine Plugins -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<!-- Alpine Core -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
@@ -1,37 +0,0 @@
---
import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus';
import FeaturesCard from '@components/ui/cards/FeaturesCard.astro';
const global = await directus.request(readSingleton('site_global'));
---
<section class="mx-auto mb-20 max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
<div
class="flex flex-col items-center justify-center gap-y-2 sm:flex-row sm:gap-x-12 sm:gap-y-0 lg:gap-x-24"
>
<div class="mx-auto max-w-5xl px-4 sm:px-6 lg:px-8">
<div class="grid gap-3 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3">
<FeaturesCard
title="Cloud Engineer"
description="Full stack and cloud engineer."
url="/about"
icon="mdi:cloud-outline"
/>
<FeaturesCard
title="Homelab"
description="Tinkering, testing, deploying, etc, etc ..."
url="/categories/homelab/"
icon="mdi:home-variant-outline"
/>
<FeaturesCard
title="Email"
description={`Send me a message.`}
url=`mailto:${global.email}`
icon="mdi:email-fast"
/>
</div>
</div>
</div>
</section>
@@ -1,35 +0,0 @@
---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro';
interface Props {
title: string;
subTitle: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
}
const { title, subTitle, btnExists, btnTitle, btnURL } = Astro.props;
---
<section class="mx-auto mt-10 px-4 sm:px-6 lg:px-8 lg:pt-10 2xl:max-w-full">
<div class="flex-wrap md:flex md:items-center md:justify-between">
<div class="w-full md:w-auto">
<h1
class="smooth-reveal block text-4xl font-bold tracking-tight text-balance text-neutral-800 md:text-5xl lg:text-6xl dark:text-neutral-200"
>
{title}
</h1>
<p class="smooth-reveal mt-4 text-lg text-pretty text-neutral-600 dark:text-neutral-400">
{subTitle}
</p>
{
btnExists ? (
<div class="smooth-reveal mt-4 md:mt-8">
<PrimaryCTA title={btnTitle} url={btnURL} />
</div>
) : null
}
</div>
</div>
</section>
@@ -1,63 +0,0 @@
---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro';
import SecondaryCTA from '@components/ui/buttons/SecondaryCTA.astro';
import Image from '@components/ui/images/Image.astro';
const { title, subTitle, primaryBtn, primaryBtnURL, secondaryBtn, secondaryBtnURL, src, alt } =
Astro.props;
interface Props {
title: string;
subTitle?: string;
primaryBtn?: string;
primaryBtnURL?: string;
secondaryBtn?: string;
secondaryBtnURL?: string;
src?: any;
alt?: string;
}
---
<section
class="mx-auto grid max-w-[85rem] gap-4 px-4 py-14 sm:px-6 md:grid-cols-2 md:items-center md:gap-8 lg:px-8 2xl:max-w-full"
>
<div>
<h1
class="smooth-reveal block text-3xl font-bold tracking-tight text-balance text-neutral-800 sm:text-4xl lg:text-7xl lg:leading-tight dark:text-neutral-200"
>
<Fragment set:html={title} />
</h1>
{
subTitle && (
<p class="smooth-reveal mt-6 text-lg leading-relaxed text-pretty text-neutral-700 lg:w-4/5 dark:text-neutral-300">
{subTitle}
</p>
)
}
<div class="smooth-reveal mt-7 grid w-full gap-3 sm:inline-flex">
{primaryBtn && <PrimaryCTA title={primaryBtn} url={primaryBtnURL} />}
{secondaryBtn && <SecondaryCTA title={secondaryBtn} url={secondaryBtnURL} />}
</div>
</div>
<div class="smooth-reveal-fade hidden w-full md:block">
<div class="top-12 flex w-full justify-center overflow-hidden md:ml-4">
{
src && alt && (
<Image
src={src}
alt={alt}
class="h-full w-[420px] scale-100 object-cover object-center"
draggable="false"
loading="eager"
format="webp"
quality="low"
widths={[840]}
disableBlur={true}
/>
)
}
</div>
</div>
</section>
@@ -1,35 +0,0 @@
---
import { readItems } from '@directus/sdk';
import directus from '@lib/directus';
import type { Post } from '@lib/directusTypes';
import BlogCard from '@components/blog/BlogCard.astro';
const posts = await directus.request(
readItems('posts', {
filter: { published: { _eq: true } },
fields: ['*'],
sort: ['-published_date'],
})
);
const recentPosts = posts
.sort((a: Post, b: Post) => (new Date(b.published_date).getTime()) - (new Date(a.published_date).getTime()))
.slice(0, 3);
---
<section class="mx-auto mb-20 max-w-[85rem] px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
<div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h1
class="smooth-reveal block text-4xl font-bold text-neutral-800 md:text-5xl md:leading-tight lg:text-5xl dark:text-neutral-200"
>
Latest Posts
</h1>
<p class="smooth-reveal mt-1 text-pretty text-neutral-600 dark:text-neutral-300">
More recent posts.
</p>
</div>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{recentPosts.map((b) => <BlogCard post={b} />)}
</div>
</section>
-75
View File
@@ -1,75 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import { readItems } from '@directus/sdk';
import type { Project } from '@lib/directusTypes';
import directus from '@lib/directus';
const projects = await directus.request(
readItems('site_projects', {
fields: ['*'],
sort: ['-isActive'],
})
);
const baseClasses = 'smooth-reveal-cards rounded-xl flex flex-col';
const borderClasses = 'border border-neutral-100 dark:border-stone-500/20';
const bgColorClasses =
'bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
const shadowClasses = 'shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg';
---
<section class:list={['flex flex-col gap-4', Astro.props.className]}>
<h3
class="relative flex w-full items-center gap-3 pb-10 text-5xl text-neutral-800 dark:text-neutral-200"
>
Projects
</h3>
<div class="ml-8 grid grid-cols-1 gap-3 md:grid-cols-2 print:flex print:flex-col">
{
projects.map((project: Project) => {
return (
<div class={`${baseClasses}`}>
<div
class={`rounded-xl transition-all duration-300 ${borderClasses} ${bgColorClasses} ${shadowClasses}`}
>
<div class="p-4 md:p-10">
<h3 class="text-lg font-bold text-gray-800 dark:text-white">{project.name}</h3>
<p class="mt-2 text-gray-500 dark:text-neutral-400">{project.description}</p>
<ul class="mt-1 flex list-disc flex-col gap-2 text-sm text-gray-500 dark:text-neutral-400 [&>li]:ml-4">
{project.highlights.map((highlight) => {
return <li class="marker:text-yellow-500">{highlight}</li>;
})}
</ul>
<div class="flex">
<a
class="group group-hover relative inline-block gap-x-1 rounded-lg border border-transparent disabled:pointer-events-none disabled:opacity-50"
href={project.url}
>
<div class="group-hover:text-steel dark:group-hover:text-bermuda transition-text text-md relative z-10 mx-auto flex min-h-[44px] items-center font-semibold text-neutral-600 decoration-2 duration-300 sm:mx-0 sm:mt-4 dark:text-neutral-300">
<span class="relative inline-block overflow-hidden"> Visit Page </span>
<Icon
name="mdi:keyboard-arrow-right"
class="translate-y-0.5 transition duration-300 group-hover:translate-x-1"
/>
</div>
</a>
<a
class="group group-hover relative ml-auto inline-block gap-x-1 rounded-lg border border-transparent disabled:pointer-events-none disabled:opacity-50"
href={project.source}
>
<div class="group-hover:text-gitea-primary dark:group-hover:text-gitea-primary transition-text text-md relative z-10 mx-auto flex min-h-[44px] items-center font-semibold text-neutral-600 decoration-2 duration-300 sm:mx-0 sm:mt-4 dark:text-neutral-300">
<span class="relative inline-block overflow-hidden"> Source </span>
<Icon name="pajamas:gitea" class="ml-2 translate-y-0.5" />
</div>
</a>
</div>
</div>
</div>
</div>
);
})
}
</div>
</section>
+3 -25
View File
@@ -9,40 +9,18 @@ export interface NavigationLink {
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
export const WorkInformation = [
{
name: 'Tech Startup',
position: 'Junior Web Developer',
location_type: 'On site',
location: 'Auckland, New Zealand',
url: 'https://techstartup.com',
startDate: '2024-01-01',
endDate: null,
summary:
'Developing and maintaining web applications using JavaScript, HTML, and CSS. Collaborating with the team to implement new features and fix bugs.',
highlights: ['Improved website performance by optimizing code'],
responsibilities: [
'Collaborated with senior developers to design and implement web applications using modern JavaScript frameworks.',
'Assisted in debugging and optimizing front-end code to ensure smooth user experiences.',
'Participated in code reviews and contributed to improving coding standards within the team.',
],
achievements: [
'Developing and maintaining web applications using JavaScript, HTML, and CSS. Collaborating with the team to implement new features and fix bugs.',
],
skills: ['React', 'Tailwind', 'GitHub'],
},
];
export const NavigationLinks: NavigationLink[] = [ export const NavigationLinks: NavigationLink[] = [
{ name: 'Home', url: '/' }, { name: 'Home', url: '/' },
{ name: 'Blog', url: '/blog/' }, { name: 'Blog', url: '/blog/' },
{ name: 'Categories', url: '/categories/' }, { name: 'Categories', url: '/categories/' },
{ name: 'Apps', url: '/apps/' },
{ name: 'About Me', url: '/about/' }, { name: 'About Me', url: '/about/' },
]; ];
export const FooterLinks: NavigationLink[] = [ export const FooterLinks: NavigationLink[] = [
{ name: 'RSS', url: '/rss.xml' }, { name: 'RSS', url: '/rss.xml' },
{ name: 'Gitea', url: '/https://gitea.alexlebens.dev' }, { name: 'Gitea', url: 'https://gitea.alexlebens.dev' },
{ name: 'Docs', url: 'https://docs.alexlebens.dev' },
]; ];
export const SEO = { export const SEO = {
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Books 📖'
description: 'Books I have read or listened to'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Cloud ☁️'
description: "Its just someone else's server"
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Homelab 🏠'
description: 'What happens when rack servers find a home'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Kubernetes ☸️'
description: 'The container orchestration system'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Life 🏃🏻'
description: 'Just random musings on everyday stuff'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Minnesota 🌳'
description: 'Land of 10,000 Lakes'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'PostgreSQL'
description: 'PostgreSQL is an open-source relational database management system (RDBMS)'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Python 🐍'
description: 'Generally my go to language'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'Tool 🪜'
description: 'Usually just the software kind'
---
-4
View File
@@ -1,4 +0,0 @@
---
title: 'What Is?'
description: 'A series on discovery'
---
-12
View File
@@ -1,12 +0,0 @@
import { defineCollection, z } from 'astro:content';
const categoryCollection = defineCollection({
type: 'content',
schema: () =>
z.object({
title: z.string(),
description: z.string(),
}),
});
export const collections = { categories: categoryCollection };
Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 76 KiB

+107 -27
View File
@@ -2,10 +2,10 @@
import { ClientRouter } from 'astro:transitions'; import { ClientRouter } from 'astro:transitions';
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus';
import BaseHead from '@components/BaseHead.astro'; import BaseHead from '@components/BaseHead.astro';
import Footer from '@components/Footer.astro'; import Footer from '@components/Footer.astro';
import Header from '@components/Header.astro'; import Header from '@components/Header.astro';
import directus from '@lib/directus';
import '@styles/global.css'; import '@styles/global.css';
@@ -20,12 +20,16 @@ interface Props {
const { title, description = 'Alex Lebens', ogImage, lang = 'en', structuredData } = Astro.props; const { title, description = 'Alex Lebens', ogImage, lang = 'en', structuredData } = Astro.props;
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
const normalizeTitle = !title ? global.name : `${title} | ${global.name}`; const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
--- ---
<html lang={lang}> <html lang={lang}>
<head> <head>
<title>{normalizeTitle}</title> <title>
{normalizeTitle}
</title>
<BaseHead <BaseHead
title={normalizeTitle} title={normalizeTitle}
description={description} description={description}
@@ -34,7 +38,9 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
ogDescription={description} ogDescription={description}
structuredData={structuredData} structuredData={structuredData}
/> />
<ClientRouter fallback="swap" /> <ClientRouter fallback="swap" />
<script is:inline> <script is:inline>
const theme = (() => { const theme = (() => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) { if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
@@ -53,41 +59,115 @@ const normalizeTitle = !title ? global.name : `${title} | ${global.name}`;
} }
window.localStorage.setItem('theme', theme); window.localStorage.setItem('theme', theme);
</script> </script>
<!-- Rybbit Tracking Snippet -->
<script
src="https://rybbit.alexlebens.dev/api/script.js"
data-site-id={global.rybbit_site_id}
defer
/>
</head> </head>
<body class="bg-stone-200 selection:bg-yellow-400 selection:text-neutral-700 dark:bg-stone-700">
<!-- <div class="fixed inset-0 -z-10"> <body class="bg-background selection:bg-yellow-400 m-0 p-0 overflow-hidden">
<div
class="bg-grid-pattern absolute inset-0 [mask-image:radial-gradient(white,transparent_85%)] bg-[center_top_-1px]" <!-- Sliding backgrounds -->
> <div class="bg"/>
</div> <div class="bg bg2"/>
</div> --> <div class="bg bg3"/>
<div class="mx-auto w-full max-w-(--breakpoint-2xl) flex-grow px-4 sm:px-6 lg:px-8">
<!-- Fixed header -->
<Header /> <Header />
<main class="min-h-screen">
<!-- Main body -->
<div id="reset-scroll" class="mask-container w-screen h-screen overflow-y-auto overflow-x-hidden">
<main>
<!-- Content -->
<div class="grow w-full max-w-(--breakpoint-2xl) px-4 sm:px-6 lg:px-8 py-20 mx-auto">
<slot /> <slot />
</div>
<!-- Footer -->
<Footer />
</main> </main>
</div> </div>
<Footer />
<style>
.scrollbar-hide::-webkit-scrollbar {
display: none;
}
.scrollbar-hide {
-ms-overflow-style: none;
scrollbar-width: none;
}
</style>
</body> </body>
</html> </html>
<script>
const resetScroll = () => {
const scrollContainer = document.getElementById('reset-scroll');
if (scrollContainer) {
scrollContainer.scrollTop = 0;
}
};
resetScroll();
document.addEventListener('astro:after-swap', resetScroll);
</script>
<style> <style>
.bg-grid-pattern { /* Fade away content below header when scrolling */
background-size: 24px 24px; .mask-container {
background-image: radial-gradient(circle, rgba(0, 0, 0, 0.2) 1px, transparent 1px); -webkit-mask-image: linear-gradient(
transition: background-image 0.7s cubic-bezier(0.65, 0, 0.35, 1); to bottom,
transparent 0px,
transparent 90px,
black 140px,
black 100%
);
mask-image: linear-gradient(
to bottom,
transparent 0px,
transparent 90px,
black 140px,
black 100%
);
-webkit-mask-size: 100vw 100vh;
-webkit-mask-repeat: no-repeat;
} }
:global(.dark) .bg-grid-pattern { /* Background that creates the "glimmer" effect */
background-image: radial-gradient(circle, rgba(255, 255, 255, 0.25) 1px, transparent 1px); .bg {
animation: slide 25s ease-in-out infinite alternate;
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);
top: 0;
bottom: 0;
left: -70%;
right: -70%;
opacity: .5;
position: fixed;
z-index: -1;
--bg-primary: #e5e5e5;
--bg-secondary: #dce3eb;
--bg-tertiary: #f4f6f8;
}
:global(.dark) .bg {
--bg-primary: #3b3836;
--bg-secondary: #332f2e;
--bg-tertiary: #44403c;
}
.bg2 {
animation-direction: alternate-reverse;
animation-duration: 30s;
}
.bg3 {
animation-duration: 20s;
}
@keyframes slide {
0% {
transform: translateX(-30%);
}
100% {
transform: translateX(30%);
}
} }
</style> </style>
+9 -1
View File
@@ -2,7 +2,11 @@ import { createDirectus, rest } from '@directus/sdk';
import type { import type {
Global, Global,
Weather,
Post, Post,
Category,
HeaderImage,
Application,
Experience, Experience,
Education, Education,
Certificate, Certificate,
@@ -10,11 +14,15 @@ import type {
Skill, Skill,
} from '@lib/directusTypes'; } from '@lib/directusTypes';
import { getDirectusURL } from '@lib/directusFunctions'; import { getDirectusURL } from '@/support/url';
type Schema = { type Schema = {
site_global: Global; site_global: Global;
site_weather: Weather;
posts: Post[]; posts: Post[];
categories: Category[];
header_images: HeaderImage[];
site_applications: Application;
site_experience: Experience; site_experience: Experience;
site_education: Education; site_education: Education;
site_certificate: Certificate; site_certificate: Certificate;
-12
View File
@@ -1,12 +0,0 @@
const getDirectusURL = () => {
if (process.env.DIRECTUS_URL) {
return `https://${process.env.DIRECTUS_URL}`;
}
return 'https://directus.alexlebens.dev';
};
async function getDirectusImageURL(image: string) {
return `${getDirectusURL()}/assets/${image}`;
}
export { getDirectusURL, getDirectusImageURL };
+43 -7
View File
@@ -2,28 +2,36 @@ export type Global = {
name: string; name: string;
about: string; about: string;
about_description: string; about_description: string;
about_blog: string;
about_applications: string;
about_categories: string;
initials: string; initials: string;
email: string; email: string;
site_url: string; site_url: string;
rybbit_site_id: string;
logo: string; logo: string;
all_logoLight: string;
all_logoDark: string;
portrait: string; portrait: string;
portrait_alt: string; portrait_alt: string;
home_image: string;
home_image_alt: string;
categories_image: string;
categories_image_alt: string;
blog_image: string;
blog_image_alt: string;
footer_image: string; footer_image: string;
footer_image_alt: string; footer_image_alt: string;
}; };
export type Weather = {
id: string;
location: string;
latitude: string;
longitude: string;
timezone: string;
}
export type Post = { export type Post = {
slug: string; slug: string;
title: string; title: string;
description: string; description: string;
tags: string[]; tags: string[];
category: string; category: Category;
selected: boolean; selected: boolean;
published: boolean; published: boolean;
content: string; content: string;
@@ -35,6 +43,30 @@ export type Post = {
updated_date: Date; updated_date: Date;
}; };
export type Category = {
slug: string;
title: string;
description: string;
logoLight: string;
logoDark: string;
};
export type HeaderImage = {
id: string;
image: string;
image_alt: string;
};
export type Application = {
id: string;
name: string;
isActive: boolean;
description: string;
highlights: string[];
url: string;
logoUrl: string;
};
export type Experience = { export type Experience = {
id: string; id: string;
name: string; name: string;
@@ -58,6 +90,8 @@ export type Education = {
area: string; area: string;
studyType: string; studyType: string;
graduationDate: string; graduationDate: string;
logo: string;
logoDark: string;
}; };
export type Certificate = { export type Certificate = {
@@ -66,6 +100,8 @@ export type Certificate = {
issuer: string; issuer: string;
issuerDate: string; issuerDate: string;
url: string; url: string;
logo: string;
logoDark: string;
}; };
export type Project = { export type Project = {

Some files were not shown because too many files have changed in this diff Show More