Compare commits

..

302 Commits
0.1.0 ... 2.0.1

Author SHA1 Message Date
ea1c3d9f1a 2.0.1 release
All checks were successful
test-build / build (push) Successful in 1m18s
release-image / release (push) Successful in 2m26s
renovate / renovate (push) Successful in 45s
2025-08-11 18:14:00 -05:00
28f73be784 update content 2025-08-11 18:13:22 -05:00
284f30c392 downgrade actions
All checks were successful
renovate / renovate (push) Successful in 33s
test-build / build (push) Successful in 57s
release-image / release (push) Successful in 2m53s
2025-08-11 17:25:44 -05:00
9e4a2d681b update checkout
Some checks failed
test-build / build (push) Failing after 3s
renovate / renovate (push) Failing after 3s
2025-08-11 17:18:51 -05:00
c8e250c5b2 Merge pull request 'Update dependency astro-compressor to v1' (#68) from renovate/astro-compressor-1.x into main
All checks were successful
renovate / renovate (push) Successful in 42s
test-build / build (push) Successful in 1m1s
Reviewed-on: #68
2025-08-11 22:15:42 +00:00
58f05178a4 Update dependency astro-compressor to v1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m3s
2025-08-11 22:10:18 +00:00
b8966e2b88 Merge pull request 'Update dependency typescript to v5.9.2' (#66) from renovate/typescript-5.x into main
All checks were successful
renovate / renovate (push) Successful in 50s
test-build / build (push) Successful in 55s
Reviewed-on: #66
2025-08-11 22:09:32 +00:00
3f6563a0d3 Update dependency typescript to v5.9.2
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 47s
2025-08-11 22:07:18 +00:00
4840d15101 Merge pull request 'Update dependency @playform/compress to ^0.2.0' (#65) from renovate/playform-compress-0.x into main
All checks were successful
test-build / build (push) Successful in 57s
renovate / renovate (push) Successful in 1m17s
Reviewed-on: #65
2025-08-11 22:06:26 +00:00
f2cb98888a Update dependency @playform/compress to ^0.2.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 51s
2025-08-11 22:03:38 +00:00
9d1402ee82 Merge pull request 'Update dependency eslint to v9.33.0' (#62) from renovate/eslint-monorepo into main
All checks were successful
test-build / build (push) Successful in 1m13s
renovate / renovate (push) Successful in 1m59s
Reviewed-on: #62
2025-08-11 22:02:33 +00:00
741338ae9f Update dependency eslint to v9.33.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m39s
2025-08-11 21:34:24 +00:00
89d8b025d3 Merge pull request 'Update dependency typescript-eslint to v8.39.1' (#69) from renovate/typescript-eslint-monorepo into main
All checks were successful
test-build / build (push) Successful in 1m29s
renovate / renovate (push) Successful in 1m51s
2025-08-11 21:33:26 +00:00
5e74f8b01e Update dependency typescript-eslint to v8.39.1
Some checks are pending
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (pull_request) Successful in 1m30s
2025-08-11 21:33:20 +00:00
bf4835e797 Merge pull request 'Update astro monorepo' (#61) from renovate/astro-monorepo into main
Some checks failed
test-build / build (push) Successful in 1m1s
renovate / renovate (push) Has been cancelled
Reviewed-on: #61
2025-08-11 21:32:27 +00:00
fc766599e1 Merge pull request 'Update dependency typescript-eslint to v8.39.0' (#60) from renovate/typescript-eslint-monorepo into main
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #60
2025-08-11 21:32:16 +00:00
465bda1859 Update dependency typescript-eslint to v8.39.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m6s
2025-08-11 21:29:30 +00:00
19d2558436 Update astro monorepo
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 2m8s
2025-08-11 21:28:10 +00:00
2f797ca614 Update dependency shiki to v3.9.2
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m14s
test-build / build (push) Successful in 1m59s
renovate / renovate (push) Successful in 2m44s
2025-08-11 21:27:13 +00:00
99e451a934 Update dependency preline to v3.2.3
Some checks failed
renovate/stability-days Updates have met minimum release age requirement
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
test-build / build (pull_request) Successful in 1m50s
2025-08-11 21:26:19 +00:00
1dc4ccfbc6 merge in new changes
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
2025-08-11 16:25:03 -05:00
a484feb7cd init 2025-08-11 16:16:05 -05:00
93d11dca17 update dependencies
All checks were successful
test-build / build (push) Successful in 26s
renovate / renovate (push) Successful in 52s
2025-08-01 20:22:38 -05:00
3eacf17f61 switch to static build 2025-08-01 20:19:53 -05:00
12ffcc4d72 Merge pull request 'Update astro monorepo' (#58) from renovate/astro-monorepo into main
All checks were successful
renovate / renovate (push) Successful in 35s
test-build / build (push) Successful in 1m21s
2025-08-02 00:01:59 +00:00
060400183f Update astro monorepo
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m10s
2025-08-02 00:01:44 +00:00
31ec9908e6 Update dependency framer-motion to v12.23.12
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (push) Successful in 24s
renovate / renovate (push) Successful in 31s
test-build / build (pull_request) Successful in 36s
2025-07-31 21:42:36 +00:00
4180a2eceb Merge pull request 'Update dependency astro to v5.12.6' (#56) from renovate/astro-monorepo into main
All checks were successful
test-build / build (push) Successful in 37s
renovate / renovate (push) Successful in 51s
2025-07-31 21:42:01 +00:00
fdef90e636 Update dependency astro to v5.12.6
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 26s
2025-07-31 21:41:47 +00:00
c369651a70 Update dependency @directus/sdk to v20.0.1
Some checks failed
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 26s
test-build / build (push) Successful in 39s
renovate / renovate (push) Has been cancelled
2025-07-31 21:40:49 +00:00
75fd981f10 remove workflow
Some checks failed
test-build / build (push) Successful in 43s
renovate / renovate (push) Has been cancelled
2025-07-31 16:40:06 -05:00
80a4aee41c release 1.1.1
All checks were successful
test-build / build (push) Successful in 43s
renovate / renovate (push) Successful in 45s
release-image / release (push) Successful in 1m40s
2025-07-30 21:23:55 -05:00
9e84de0a5a update rendering 2025-07-30 21:23:28 -05:00
64140cce6b release 1.1.0
Some checks failed
test-build / build (push) Successful in 41s
release-image / release (push) Successful in 1m50s
process-repository / process-repository (push) Failing after 10s
renovate / renovate (push) Successful in 59s
2025-07-30 18:06:07 -05:00
0733fe6a06 switch to ssr
All checks were successful
renovate / renovate (push) Successful in 18s
test-build / build (push) Successful in 33s
2025-07-30 18:05:29 -05:00
0f5c015932 Update astro monorepo
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
renovate / renovate (push) Successful in 32s
test-build / build (pull_request) Successful in 51s
test-build / build (push) Successful in 1m3s
2025-07-30 21:00:16 +00:00
dc17aeb3d5 Merge pull request 'Update react monorepo to v19.1.1' (#53) from renovate/react-monorepo into main
Some checks failed
test-build / build (push) Successful in 37s
renovate / renovate (push) Has been cancelled
Reviewed-on: #53
2025-07-30 20:59:11 +00:00
a852f22409 Update react monorepo to v19.1.1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 31s
2025-07-30 00:03:21 +00:00
130a3866bc Merge pull request 'Update dependency framer-motion to v12.23.11' (#52) from renovate/framer-motion-12.x-lockfile into main
All checks were successful
test-build / build (push) Successful in 33s
renovate / renovate (push) Successful in 50s
2025-07-30 00:02:18 +00:00
2fb0542d36 Update dependency framer-motion to v12.23.11
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m40s
2025-07-30 00:02:09 +00:00
8a2be36f17 Merge pull request 'Update dependency astro to v5.12.4' (#51) from renovate/astro-monorepo into main
Some checks failed
test-build / build (push) Has been cancelled
renovate / renovate (push) Has been cancelled
2025-07-30 00:01:56 +00:00
266d25e0f2 Update dependency astro to v5.12.4
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m45s
2025-07-30 00:01:35 +00:00
34dbe6d809 Merge pull request 'Update dependency eslint to v9.32.0' (#50) from renovate/eslint-monorepo into main
Some checks failed
test-build / build (push) Successful in 1m28s
process-repository / process-repository (push) Failing after 35s
renovate / renovate (push) Successful in 2m46s
Reviewed-on: #50
2025-07-27 14:33:52 +00:00
3c654e19e1 Update dependency eslint to v9.32.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 34s
2025-07-27 00:01:17 +00:00
2a0142ee83 Update dependency framer-motion to v12.23.9
Some checks failed
process-repository / process-repository (push) Failing after 16s
renovate / renovate (push) Successful in 1m31s
renovate/stability-days Updates have met minimum release age requirement
test-build / build (push) Successful in 34s
test-build / build (pull_request) Successful in 41s
2025-07-26 00:01:11 +00:00
7836f49828 1.0.1 release
Some checks failed
test-build / build (push) Successful in 32s
release-image / release (push) Successful in 1m27s
process-repository / process-repository (push) Failing after 18s
renovate / renovate (push) Successful in 1m23s
2025-07-25 00:05:00 -05:00
25280a239c fix math
Some checks failed
test-build / build (push) Has been cancelled
renovate / renovate (push) Has been cancelled
2025-07-25 00:04:28 -05:00
c56dc99e72 disable descriptions using comments 2025-07-25 00:04:20 -05:00
48b7a13729 update colo
All checks were successful
renovate / renovate (push) Successful in 19s
test-build / build (push) Successful in 34s
2025-07-24 23:48:32 -05:00
ac026b0264 update workflow
All checks were successful
test-build / build (push) Successful in 31s
renovate / renovate (push) Successful in 34s
2025-07-24 21:24:50 -05:00
5332854856 1.0.0 release
All checks were successful
renovate / renovate (push) Successful in 24s
test-build / build (push) Successful in 38s
2025-07-24 20:51:35 -05:00
2e0c2f3de5 rewrite a few sections 2025-07-24 20:51:35 -05:00
88d510b06f update favicon 2025-07-24 20:51:35 -05:00
7843378503 Merge pull request 'Update typescript-eslint monorepo to v8.38.0' (#45) from renovate/typescript-eslint-monorepo into main
All checks were successful
renovate / renovate (push) Successful in 28s
test-build / build (push) Successful in 35s
Reviewed-on: #45
2025-07-25 00:47:24 +00:00
75016fdb4f Update typescript-eslint monorepo to v8.38.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 45s
2025-07-25 00:02:19 +00:00
4d74f74ab2 Merge pull request 'Update dependency framer-motion to v12.23.7' (#48) from renovate/framer-motion-12.x-lockfile into main
All checks were successful
renovate / renovate (push) Successful in 40s
test-build / build (push) Successful in 42s
2025-07-25 00:01:38 +00:00
2c1b7f577d Update dependency framer-motion to v12.23.7
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 45s
2025-07-25 00:01:27 +00:00
0e79b32012 Merge pull request 'Update dependency astro to v5.12.3' (#47) from renovate/astro-monorepo into main
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
2025-07-25 00:01:22 +00:00
c1ef2d2ba2 Update dependency astro to v5.12.3
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 43s
2025-07-25 00:01:07 +00:00
020c709b43 0.12.0 release
Some checks failed
test-build / build (push) Successful in 48s
release-image / release (push) Successful in 1m37s
process-repository / process-repository (push) Failing after 26s
renovate / renovate (push) Successful in 1m43s
2025-07-23 20:32:04 -05:00
9f346ee156 add colors and logo 2025-07-23 20:30:55 -05:00
e820e4f163 add theme 2025-07-23 20:30:55 -05:00
796926316e Merge pull request 'Update dependency astro to v5.12.2' (#46) from renovate/astro-monorepo into main
All checks were successful
test-build / build (push) Successful in 34s
renovate / renovate (push) Successful in 41s
2025-07-24 00:01:08 +00:00
bf8578045e Update dependency astro to v5.12.2
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 30s
2025-07-24 00:00:57 +00:00
f16af9a98d Update dependency astro to v5.12.1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 29s
test-build / build (push) Successful in 31s
renovate / renovate (push) Successful in 1m0s
2025-07-23 00:01:09 +00:00
ec45ad29ed 0.11.3 release
Some checks failed
test-build / build (push) Successful in 29s
release-image / release (push) Successful in 3m27s
process-repository / process-repository (push) Failing after 13s
renovate / renovate (push) Successful in 1m9s
2025-07-21 21:00:55 -05:00
17afce6710 minor tweaks and polish
All checks were successful
renovate / renovate (push) Successful in 1m11s
test-build / build (push) Successful in 1m36s
2025-07-21 20:58:34 -05:00
f83fe98b38 0.11.2 release
Some checks failed
test-build / build (push) Successful in 44s
release-image / release (push) Successful in 3m31s
process-repository / process-repository (push) Failing after 16s
renovate / renovate (push) Successful in 38s
2025-07-20 22:22:20 -05:00
2f244761ed add transition 2025-07-20 22:21:54 -05:00
649bf4482b 0.11.1 release
All checks were successful
renovate / renovate (push) Successful in 30s
test-build / build (push) Successful in 41s
release-image / release (push) Successful in 3m38s
2025-07-20 22:20:42 -05:00
2028e2247e fix page transition
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
2025-07-20 22:20:16 -05:00
fcae7676c6 0.11.0 release
Some checks failed
test-build / build (push) Successful in 25s
release-image / release (push) Successful in 1m20s
process-repository / process-repository (push) Failing after 16s
renovate / renovate (push) Successful in 36s
2025-07-19 22:39:43 -05:00
cc16b5435a update node
All checks were successful
renovate / renovate (push) Successful in 1m16s
test-build / build (push) Successful in 1m26s
2025-07-19 22:37:05 -05:00
27b5e6a36b change layout and animations to be more common with each other 2025-07-19 22:37:05 -05:00
bcb91972a1 Merge pull request 'Update astro monorepo' (#42) from renovate/astro-monorepo into main
All checks were successful
renovate / renovate (push) Successful in 23s
test-build / build (push) Successful in 28s
Reviewed-on: #42
2025-07-20 03:34:37 +00:00
b11666decb Update astro monorepo
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 29s
2025-07-20 00:02:37 +00:00
a947a05041 Merge pull request 'Update dependency eslint-config-prettier to v10.1.8' (#43) from renovate/eslint-config-prettier-10.x into main
All checks were successful
test-build / build (push) Successful in 34s
renovate / renovate (push) Successful in 47s
2025-07-20 00:01:54 +00:00
297c573281 Update dependency eslint-config-prettier to v10.1.8
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 25s
2025-07-20 00:01:31 +00:00
9093594973 Update dependency astro to v5.11.2
All checks were successful
test-build / build (push) Successful in 37s
renovate/stability-days Updates have met minimum release age requirement
renovate / renovate (push) Successful in 21s
test-build / build (pull_request) Successful in 36s
2025-07-18 00:01:29 +00:00
77ce0a1182 Update dependency framer-motion to v12.23.6
All checks were successful
renovate / renovate (push) Successful in 28s
test-build / build (pull_request) Successful in 29s
test-build / build (push) Successful in 40s
renovate/stability-days Updates have met minimum release age requirement
2025-07-16 16:14:52 +00:00
799e6b6090 Merge pull request 'Update typescript-eslint monorepo to v8.37.0' (#38) from renovate/typescript-eslint-monorepo into main
Some checks failed
test-build / build (push) Successful in 26s
process-repository / process-repository (push) Failing after 11s
renovate / renovate (push) Successful in 1m21s
Reviewed-on: #38
2025-07-16 16:14:17 +00:00
735e4b4877 Update typescript-eslint monorepo to v8.37.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 27s
2025-07-16 02:59:27 +00:00
3e12a8647d update tag
All checks were successful
test-build / build (push) Successful in 51s
renovate / renovate (push) Successful in 1m9s
release-image / release (push) Successful in 3m32s
2025-07-15 21:58:20 -05:00
e07210638e add astro native SPA transition 2025-07-15 21:58:20 -05:00
22d5b50f73 formatting and layout 2025-07-15 21:58:20 -05:00
40acf8f34a strip theme transition on load to use early script 2025-07-15 21:58:20 -05:00
543516baba remove SPA scripts 2025-07-15 21:58:20 -05:00
e985f905f2 formatting changes 2025-07-15 21:58:20 -05:00
e1f09ca4ec fix slider 2025-07-15 21:58:20 -05:00
0c09eb38e9 Merge pull request 'Update dependency framer-motion to v12.23.5' (#37) from renovate/framer-motion-12.x-lockfile into main
All checks were successful
test-build / build (push) Successful in 24s
renovate / renovate (push) Successful in 41s
2025-07-16 00:03:06 +00:00
95eeb44e4f Update dependency framer-motion to v12.23.5
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 34s
2025-07-16 00:02:26 +00:00
d47d67572e Merge pull request 'Update dependency astro to v5.11.1' (#36) from renovate/astro-monorepo into main
Some checks failed
renovate / renovate (push) Successful in 59s
test-build / build (push) Has been cancelled
2025-07-16 00:01:41 +00:00
fa4841948a Update dependency astro to v5.11.1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 30s
2025-07-16 00:00:52 +00:00
71e2b0185b update tag
Some checks failed
test-build / build (push) Successful in 28s
release-image / release (push) Successful in 3m31s
process-repository / process-repository (push) Failing after 1m7s
renovate / renovate (push) Successful in 1m38s
2025-07-15 01:43:31 -05:00
7f9fb4d2b9 fix scrollbar affecting layout
Some checks failed
renovate / renovate (push) Successful in 29s
test-build / build (push) Has been cancelled
2025-07-15 01:39:40 -05:00
8420c8dd58 fix tech stack slider 2025-07-15 01:37:23 -05:00
fa6ed18edb fix dark mode 2025-07-14 23:18:38 -05:00
30860fce1e change paths
All checks were successful
renovate / renovate (push) Successful in 21s
test-build / build (push) Successful in 30s
2025-07-14 22:31:12 -05:00
b479e0e22c use single workflow script
Some checks failed
process-repository / process-repository (push) Failing after 17s
renovate / renovate (push) Successful in 39s
test-build / build (push) Successful in 41s
2025-07-13 23:43:58 -05:00
cf01ebcd3c Merge pull request 'Update dependency eslint to v9.31.0' (#35) from renovate/eslint-monorepo into main
All checks were successful
test-build / build (push) Successful in 35s
renovate / renovate (push) Successful in 34s
process-pull-requests / process-pull-requests (push) Successful in 10s
process-issues / process-issues (push) Successful in 13s
Reviewed-on: #35
2025-07-13 03:33:05 +00:00
df8ccf81c2 Update dependency eslint to v9.31.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 38s
2025-07-13 00:01:03 +00:00
073911c1b9 use tag ids
Some checks failed
process-pull-requests / process-pull-requests (push) Failing after 11s
process-issues / process-issues (push) Failing after 10s
renovate / renovate (push) Successful in 58s
test-build / build (push) Successful in 31s
2025-07-11 21:47:20 -05:00
3eeea3dd8f use tag ids
All checks were successful
renovate / renovate (push) Successful in 20s
test-build / build (push) Successful in 23s
2025-07-11 21:36:40 -05:00
43fea76778 Update dependency framer-motion to v12.23.3
All checks were successful
test-build / build (pull_request) Successful in 36s
renovate / renovate (push) Successful in 32s
test-build / build (push) Successful in 41s
renovate/stability-days Updates have met minimum release age requirement
2025-07-12 00:01:43 +00:00
d64df6473a Update dependency prettier-plugin-tailwindcss to v0.6.14
Some checks failed
test-build / build (push) Successful in 32s
process-pull-requests / process-pull-requests (push) Successful in 15s
process-issues / process-issues (push) Failing after 9s
renovate / renovate (push) Successful in 1m7s
2025-07-10 20:59:07 +00:00
63a6a00817 Update dependency framer-motion to v12.23.1
Some checks failed
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 48s
test-build / build (push) Has been cancelled
renovate / renovate (push) Has been cancelled
2025-07-10 20:57:58 +00:00
54759056b3 update version
All checks were successful
renovate / renovate (push) Successful in 1m21s
test-build / build (push) Successful in 33s
release-image / release (push) Successful in 4m15s
2025-07-10 15:57:02 -05:00
3cc9762e0d Merge pull request 'Update typescript-eslint monorepo to v8.36.0' (#31) from renovate/typescript-eslint-monorepo into main
All checks were successful
renovate / renovate (push) Successful in 26s
test-build / build (push) Successful in 45s
Reviewed-on: #31
2025-07-10 03:28:27 +00:00
ef757c4a14 Update typescript-eslint monorepo to v8.36.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 29s
2025-07-09 00:02:57 +00:00
176f92bf67 Merge pull request 'Update dependency framer-motion to v12.23.0' (#27) from renovate/framer-motion-12.x-lockfile into main
Some checks failed
test-build / build (push) Successful in 34s
process-pull-requests / process-pull-requests (push) Successful in 8s
process-issues / process-issues (push) Failing after 13s
renovate / renovate (push) Successful in 42s
Reviewed-on: #27
2025-07-05 04:54:21 +00:00
09d411dd68 Merge pull request 'Update astro monorepo' (#30) from renovate/astro-monorepo into main
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #30
2025-07-05 04:54:14 +00:00
54acfcb24d Update astro monorepo
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 43s
2025-07-05 00:01:04 +00:00
6f3b631862 Update dependency framer-motion to v12.23.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m20s
2025-07-04 00:02:50 +00:00
18cd240a9b Update dependency astro to v5.10.2
Some checks failed
test-build / build (push) Successful in 29s
process-issues / process-issues (push) Failing after 14s
process-pull-requests / process-pull-requests (push) Successful in 15s
renovate / renovate (push) Successful in 1m31s
2025-07-03 00:02:59 +00:00
bb4fe8ef37 Update dependency eslint to v9.30.1
Some checks failed
renovate/stability-days Updates have met minimum release age requirement
renovate / renovate (push) Has been cancelled
test-build / build (pull_request) Successful in 30s
test-build / build (push) Has been cancelled
2025-07-03 00:02:05 +00:00
e0e3c1f61a Update typescript-eslint monorepo to v8.35.1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (push) Successful in 48s
test-build / build (pull_request) Successful in 56s
renovate / renovate (push) Successful in 1m7s
2025-07-02 00:01:05 +00:00
0b5c6ae999 update astro
Some checks failed
test-build / build (push) Successful in 38s
process-pull-requests / process-pull-requests (push) Failing after 10s
process-issues / process-issues (push) Failing after 10s
renovate / renovate (push) Successful in 2m14s
2025-06-28 16:41:12 -05:00
a20ba4ab43 Merge pull request 'Update dependency eslint to v9.30.0' (#25) from renovate/eslint-monorepo into main
All checks were successful
renovate / renovate (push) Successful in 22s
test-build / build (push) Successful in 25s
Reviewed-on: #25
2025-06-28 21:37:27 +00:00
550e7dfe52 Merge pull request 'Update dependency @directus/sdk to v20' (#21) from renovate/directus-sdk-20.x into main
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #21
2025-06-28 21:37:21 +00:00
03174cfb9d Update dependency @directus/sdk to v20
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 31s
2025-06-28 21:36:32 +00:00
da50c1928c Update dependency eslint to v9.30.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 31s
2025-06-28 21:36:25 +00:00
f1d1fe979e change confi
All checks were successful
test-build / build (push) Successful in 26s
renovate / renovate (push) Successful in 1m0s
2025-06-28 16:35:35 -05:00
4d6019d0b0 update node
All checks were successful
test-build / build (push) Successful in 42s
renovate / renovate (push) Successful in 1m7s
2025-06-28 16:33:16 -05:00
7dd302b3d4 Update dependency prettier to v3.6.2
All checks were successful
test-build / build (pull_request) Successful in 34s
renovate/stability-days Updates have met minimum release age requirement
test-build / build (push) Successful in 32s
renovate / renovate (push) Successful in 45s
2025-06-28 05:21:40 +00:00
8a8f2a6216 Update dependency framer-motion to v12.19.2
Some checks are pending
test-build / build (pull_request) Successful in 34s
renovate/stability-days Updates have not met minimum release age requirement
test-build / build (push) Successful in 26s
renovate / renovate (push) Successful in 1m0s
2025-06-28 05:20:17 +00:00
97775f1ceb Merge pull request 'Update typescript-eslint monorepo to v8.35.0' (#19) from renovate/typescript-eslint-monorepo into main
All checks were successful
test-build / build (push) Successful in 25s
renovate / renovate (push) Successful in 1m22s
Reviewed-on: #19
2025-06-28 05:19:32 +00:00
0a437a26f1 Merge pull request 'Update dependency prettier to v3.6.1' (#18) from renovate/prettier-3.x-lockfile into main
Some checks failed
test-build / build (push) Has been cancelled
renovate / renovate (push) Has been cancelled
Reviewed-on: #18
2025-06-28 05:19:19 +00:00
ba67b4d0e4 Merge pull request 'Update dependency framer-motion to v12.19.1' (#17) from renovate/framer-motion-12.x-lockfile into main
Some checks failed
test-build / build (push) Has been cancelled
renovate / renovate (push) Has been cancelled
Reviewed-on: #17
2025-06-28 05:19:03 +00:00
0bcfa9bed4 Update typescript-eslint monorepo to v8.35.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 30s
2025-06-28 00:03:22 +00:00
ada95481f7 Update dependency prettier to v3.6.1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 30s
2025-06-28 00:03:06 +00:00
7c9f4acc00 Update dependency framer-motion to v12.19.1
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 30s
2025-06-28 00:02:54 +00:00
0b7b87580a Update tailwindcss monorepo to v4.1.11
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 42s
test-build / build (push) Successful in 33s
renovate / renovate (push) Successful in 1m43s
2025-06-28 00:01:05 +00:00
08f076e566 Update dependency prettier-plugin-tailwindcss to v0.6.13
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / build (pull_request) Successful in 1m22s
test-build / build (push) Successful in 42s
renovate / renovate (push) Successful in 21s
2025-06-20 18:18:43 +00:00
26c27b9353 update astro
Some checks failed
test-build / build (push) Successful in 1m20s
process-pull-requests / process-pull-requests (push) Failing after 10s
process-issues / process-issues (push) Failing after 12s
renovate / renovate (push) Successful in 1m37s
2025-06-20 13:17:48 -05:00
ce8b3a2e19 update config
All checks were successful
renovate / renovate (push) Successful in 39s
test-build / build (push) Successful in 1m22s
2025-06-20 00:02:41 -05:00
6d34c0d407 update config
Some checks failed
test-build / build (push) Failing after 2s
renovate / renovate (push) Successful in 12s
2025-06-19 23:59:05 -05:00
63607bbca3 Merge pull request 'Update ghcr.io/renovatebot/renovate Docker tag to v41' (#15) from renovate/ghcr.io-renovatebot-renovate-41.x into main
All checks were successful
renovate / renovate (push) Successful in 35s
test-build / build (push) Successful in 27s
Reviewed-on: #15
2025-06-20 04:40:12 +00:00
745d2553a0 Update ghcr.io/renovatebot/renovate Docker tag to v41
All checks were successful
test-build / build (pull_request) Successful in 27s
2025-06-20 04:32:37 +00:00
8a19559cc7 Merge pull request 'Update typescript-eslint monorepo to v8.34.1' (#13) from renovate/typescript-eslint-monorepo into main
All checks were successful
test-build / build (push) Successful in 1m10s
process-issues / process-issues (push) Successful in 11s
process-pull-requests / process-pull-requests (push) Successful in 11s
renovate / renovate (push) Successful in 39s
Reviewed-on: #13
2025-06-18 05:18:07 +00:00
42854db0fb Update typescript-eslint monorepo to v8.34.1
All checks were successful
test-build / build (pull_request) Successful in 1m2s
2025-06-17 00:01:12 +00:00
7b72e3849b Merge pull request 'Update dependency framer-motion to v12.18.1' (#12) from renovate/framer-motion-12.x-lockfile into main
Some checks failed
test-build / build (push) Successful in 51s
process-pull-requests / process-pull-requests (push) Successful in 6s
process-issues / process-issues (push) Failing after 5s
renovate / renovate (push) Successful in 45s
Reviewed-on: #12
2025-06-14 19:52:45 +00:00
6a8dbb0c7c Merge pull request 'Update dependency eslint to v9.29.0' (#11) from renovate/eslint-monorepo into main
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #11
2025-06-14 19:52:35 +00:00
91fdf5a83f Update dependency framer-motion to v12.18.1
All checks were successful
test-build / build (pull_request) Successful in 1m6s
2025-06-14 00:01:33 +00:00
073f3a7916 Update dependency eslint to v9.29.0
All checks were successful
test-build / build (pull_request) Successful in 1m21s
2025-06-14 00:01:11 +00:00
38202841ca Merge pull request 'Update dependency framer-motion to v12.17.3' (#10) from renovate/framer-motion-12.x-lockfile into main
All checks were successful
test-build / build (push) Successful in 57s
process-issues / process-issues (push) Successful in 8s
process-pull-requests / process-pull-requests (push) Successful in 13s
renovate / renovate (push) Successful in 1m26s
Reviewed-on: #10
2025-06-12 20:12:45 +00:00
0492922cce Update dependency framer-motion to v12.17.3
All checks were successful
test-build / build (pull_request) Successful in 40s
2025-06-12 20:11:41 +00:00
a17500835b Merge pull request 'Update dependency framer-motion to v12.17.0' (#9) from renovate/framer-motion-12.x-lockfile into main
All checks were successful
renovate / renovate (push) Successful in 2m29s
test-build / build (push) Successful in 55s
Reviewed-on: #9
2025-06-12 18:06:43 +00:00
2f8b97208c Merge pull request 'Update tailwindcss monorepo to v4.1.10' (#8) from renovate/tailwindcss-monorepo into main
Some checks failed
renovate / renovate (push) Has been cancelled
test-build / build (push) Has been cancelled
Reviewed-on: #8
2025-06-12 18:06:36 +00:00
d6c30d5e5b Update dependency framer-motion to v12.17.0
All checks were successful
test-build / build (pull_request) Successful in 1m9s
2025-06-12 00:01:41 +00:00
a7ea9db3aa Update tailwindcss monorepo to v4.1.10
All checks were successful
test-build / build (pull_request) Successful in 1m19s
2025-06-12 00:01:31 +00:00
9134e78e8a remove dispatch
All checks were successful
test-build / build (push) Successful in 39s
process-issues / process-issues (push) Successful in 7s
process-pull-requests / process-pull-requests (push) Successful in 7s
renovate / renovate (push) Successful in 1m31s
2025-06-10 16:43:37 -05:00
2ca7d6705d fix
All checks were successful
renovate / renovate (push) Successful in 17s
test-build / build (push) Successful in 46s
2025-06-10 16:42:15 -05:00
5722e8c7a1 fix
All checks were successful
renovate / renovate (push) Successful in 19s
test-build / build (push) Successful in 38s
2025-06-10 16:39:55 -05:00
e39fd2acb8 change token
All checks were successful
renovate / renovate (push) Successful in 21s
test-build / build (push) Successful in 40s
2025-06-10 16:29:55 -05:00
0313fd54bc add ref
All checks were successful
renovate / renovate (push) Successful in 16s
test-build / build (push) Successful in 50s
2025-06-10 16:23:38 -05:00
dbb0f6d7ff update workflows
All checks were successful
renovate / renovate (push) Successful in 16s
test-build / build (push) Successful in 58s
2025-06-10 16:15:10 -05:00
20669d9766 fix
All checks were successful
renovate / renovate (push) Successful in 18s
test-build / build (push) Successful in 1m18s
2025-06-10 14:04:19 -05:00
6b2e6353d1 add dispatch
Some checks failed
renovate / renovate (push) Successful in 19s
test-build / build (push) Has been cancelled
2025-06-10 14:03:40 -05:00
6d112b52df remove step
Some checks failed
renovate / renovate (push) Successful in 17s
test-build / build (push) Has been cancelled
2025-06-10 14:02:58 -05:00
ff17af604f convert to python script
Some checks failed
renovate / renovate (push) Successful in 27s
test-build / build (push) Has been cancelled
2025-06-10 14:01:26 -05:00
32ea0989d7 change to pull requests
All checks were successful
renovate / renovate (push) Successful in 22s
test-build / build (push) Successful in 1m27s
2025-06-10 13:11:43 -05:00
e4ab7d134c fix repo name
All checks were successful
renovate / renovate (push) Successful in 20s
test-build / build (push) Successful in 1m44s
2025-06-10 13:06:04 -05:00
5fad13655c fix env
All checks were successful
renovate / renovate (push) Successful in 21s
test-build / build (push) Successful in 1m35s
2025-06-10 12:56:18 -05:00
8614d40a64 debugging
All checks were successful
renovate / renovate (push) Successful in 17s
test-build / build (push) Successful in 48s
2025-06-10 12:40:20 -05:00
8c417b93b3 fix url
All checks were successful
renovate / renovate (push) Successful in 17s
test-build / build (push) Successful in 41s
2025-06-10 12:36:49 -05:00
1d9519831b temp debugging
All checks were successful
renovate / renovate (push) Successful in 20s
test-build / build (push) Successful in 48s
2025-06-10 12:35:13 -05:00
fa57f2e93f temp debugging
All checks were successful
renovate / renovate (push) Successful in 18s
test-build / build (push) Successful in 45s
2025-06-10 12:29:25 -05:00
9e01002d4e add more logging
All checks were successful
renovate / renovate (push) Successful in 21s
test-build / build (push) Successful in 46s
2025-06-10 12:25:45 -05:00
cb52c169a3 add logging
All checks were successful
renovate / renovate (push) Successful in 19s
test-build / build (push) Successful in 1m1s
2025-06-10 12:15:47 -05:00
3017668cd2 fix lint
All checks were successful
renovate / renovate (push) Successful in 18s
test-build / build (push) Successful in 2m31s
2025-06-10 12:05:33 -05:00
1972b3bc19 update lockfile
Some checks failed
renovate / renovate (push) Successful in 19s
test-build / build (push) Failing after 48s
release-image / release (push) Successful in 1m43s
2025-06-10 11:58:31 -05:00
af77f90a49 bump versions to 0.8.11
Some checks failed
test-build / build (push) Failing after 23s
renovate / renovate (push) Successful in 43s
release-image / release (push) Failing after 48s
2025-06-09 23:55:31 -05:00
bdda29f369 fix favicon path 2025-06-09 23:44:22 -05:00
644c5fcd6a fix footer 2025-06-09 23:37:47 -05:00
bafd8158d3 remove 2025-06-09 23:11:38 -05:00
4d9c1a3e8c changes for v4 tailwind 2025-06-09 23:03:04 -05:00
4a4233ac62 change module 2025-06-09 22:33:40 -05:00
c71957348d add pubilc image 2025-06-09 22:21:38 -05:00
400bf16dd9 use global value 2025-06-09 22:21:25 -05:00
85535614a0 remove dependencies 2025-06-09 22:21:09 -05:00
38fcbb635b change description 2025-06-09 21:47:43 -05:00
b1e57c3f17 use apply 2025-06-09 21:47:29 -05:00
e22a1985be remove unused keys
All checks were successful
renovate / renovate (push) Successful in 18s
test-build / build (push) Successful in 2m14s
2025-06-09 21:32:45 -05:00
70b0b86944 change transitions
All checks were successful
renovate / renovate (push) Successful in 1m8s
test-build / build (push) Successful in 1m16s
2025-06-09 21:31:20 -05:00
ba36de8e36 remove translate on hover for links and buttons 2025-06-09 21:21:13 -05:00
d2e44fe046 add gitea link 2025-06-09 21:20:48 -05:00
36ec797d3b change favicon 2025-06-09 19:34:30 -05:00
086d98ba50 remove ignored
Some checks failed
test-build / build (push) Successful in 47s
tag-old-issues / tag-old-issues (push) Failing after 1m11s
renovate / renovate (push) Successful in 24s
2025-06-09 15:59:59 -05:00
8a05fa4d96 update prettier and add eslint
All checks were successful
renovate / renovate (push) Successful in 18s
test-build / build (push) Successful in 2m11s
2025-06-09 15:54:56 -05:00
dbbf886de9 add test build
All checks were successful
renovate / renovate (push) Successful in 17s
test-build / build (push) Successful in 2m14s
2025-06-09 13:14:56 -05:00
ae7e21eb82 change registry
All checks were successful
renovate / renovate (push) Successful in 18s
2025-06-09 13:02:52 -05:00
ce6f476e8f fix repo
All checks were successful
renovate / renovate (push) Successful in 18s
2025-06-09 12:57:28 -05:00
0ca6be1d91 limit repo
All checks were successful
renovate / renovate (push) Successful in 55s
2025-06-09 12:56:12 -05:00
cedcae02ce update astro
All checks were successful
renovate / renovate (push) Successful in 1m41s
release-image / release (push) Successful in 2m13s
2025-06-09 12:38:55 -05:00
4ef6e85ed9 change url
Some checks failed
renovate / renovate (push) Has been cancelled
2025-06-09 12:37:04 -05:00
1ad039e9ff add workflow to tag old issues
All checks were successful
renovate / renovate (push) Successful in 1m14s
2025-06-09 12:32:34 -05:00
034d6d1120 downgrade priority
All checks were successful
renovate / renovate (push) Successful in 1m40s
2025-06-08 23:28:42 -05:00
2c436100c5 fix topic
All checks were successful
renovate / renovate (push) Successful in 1m14s
2025-06-08 23:23:57 -05:00
6ea1467653 change ntfy workflwo
All checks were successful
renovate / renovate (push) Successful in 2m30s
2025-06-08 23:02:40 -05:00
1ba76ab5cf change log level
All checks were successful
renovate / renovate (push) Successful in 1m22s
2025-06-08 22:29:17 -05:00
478482ab01 remove test
Some checks failed
renovate / renovate (push) Successful in 10m9s
release-image-gitea / release (push) Failing after 21m20s
2025-06-08 22:19:00 -05:00
f1e3e4ecaa change workflow
All checks were successful
renovate / renovate (push) Successful in 1m36s
2025-06-08 22:12:43 -05:00
05eb8a092c change command
Some checks failed
renovate / renovate (push) Successful in 5m33s
release-image-gitea / release (push) Failing after 11m33s
2025-06-08 21:54:10 -05:00
633e374a17 change tags
Some checks failed
renovate / renovate (push) Has been cancelled
2025-06-08 21:47:30 -05:00
cd75440a6d remove command
Some checks failed
renovate / renovate (push) Successful in 1m7s
release-image-gitea / release (push) Failing after 14m4s
2025-06-08 20:50:39 -05:00
3354975e2e remove command
Some checks failed
renovate / renovate (push) Has been cancelled
2025-06-08 20:49:53 -05:00
1ffe933d6e change build arg
Some checks failed
renovate / renovate (push) Successful in 1m26s
release-image-gitea / release (push) Failing after 1m24s
2025-06-08 20:45:47 -05:00
90318aad14 change build arg
Some checks failed
renovate / renovate (push) Successful in 1m15s
release-image-gitea / release (push) Failing after 1m19s
2025-06-08 20:39:46 -05:00
e454a510c6 change
All checks were successful
renovate / renovate (push) Successful in 1m4s
2025-06-08 20:35:06 -05:00
a6d3ec5052 change
All checks were successful
renovate / renovate (push) Successful in 1m8s
2025-06-08 20:33:24 -05:00
1d134d43da remove oci
All checks were successful
renovate / renovate (push) Successful in 1m11s
2025-06-08 20:30:25 -05:00
54c7c9e259 remove oci
All checks were successful
renovate / renovate (push) Successful in 1m9s
2025-06-08 20:26:29 -05:00
0d8cf28be4 modify daemon
All checks were successful
renovate / renovate (push) Successful in 1m16s
2025-06-08 20:24:03 -05:00
d78a8d8c45 use different registry
All checks were successful
renovate / renovate (push) Successful in 1m7s
2025-06-08 20:19:32 -05:00
5b6abeb9f9 use registry argument
Some checks failed
renovate / renovate (push) Successful in 1m34s
release-image-gitea / release (push) Failing after 23s
2025-06-08 20:14:29 -05:00
a3b0301d23 merge workflows 2025-06-08 20:09:45 -05:00
06f7546212 bump tag
Some checks failed
release-image-gitea / release (push) Failing after 22s
release-image-harbor / release (push) Failing after 26s
renovate / renovate (push) Successful in 1m19s
2025-06-08 19:49:37 -05:00
abd1d43f79 remove command 2025-06-08 19:49:19 -05:00
07f2f5f0e1 add node render
Some checks failed
release-image-gitea / release (push) Failing after 1m5s
renovate / renovate (push) Successful in 1m14s
release-image-harbor / release (push) Failing after 1m15s
2025-06-08 19:46:11 -05:00
91b53a33c2 update tags
Some checks failed
renovate / renovate (push) Successful in 1m43s
release-image-gitea / release (push) Failing after 5m13s
release-image-harbor / release (push) Failing after 5m16s
2025-06-08 19:10:25 -05:00
b3e23f3e6c update tags
Some checks failed
renovate / renovate (push) Has been cancelled
2025-06-08 19:09:57 -05:00
ab68b6248f fix tag 2025-06-08 19:01:01 -05:00
37d1f1d1f2 change workflows
Some checks failed
release-image-gitea / release (push) Failing after 7m21s
release-image-harbor / release (push) Failing after 7m20s
renovate / renovate (push) Successful in 2m6s
2025-06-08 17:15:25 -05:00
89e1c59e37 upgrade to tailwind 4
All checks were successful
renovate / renovate (push) Successful in 2m35s
2025-06-08 17:10:20 -05:00
7153f29022 update docker base
All checks were successful
renovate / renovate (push) Successful in 4m11s
2025-06-08 16:49:57 -05:00
51041f6ae9 apply prettier formatting
Some checks failed
renovate / renovate (push) Has been cancelled
2025-06-08 16:45:36 -05:00
67f12ecf72 add missing config
All checks were successful
renovate / renovate (push) Successful in 4m14s
2025-06-08 16:28:14 -05:00
3e89e6cb1c upgrade to different layout
Some checks failed
renovate / renovate (push) Successful in 5m22s
release-image-gitea / release (push) Failing after 7m9s
release-image-harbor / release (push) Failing after 7m9s
2025-06-08 16:02:39 -05:00
e1632629a9 add renovate
All checks were successful
renovate / renovate (push) Successful in 4m10s
2025-06-04 21:04:00 -05:00
87343e78bb add ntfy action 2025-05-17 20:56:04 -05:00
f243249fb8 update tag
All checks were successful
release-image-gitea / release (push) Successful in 47s
release-image-harbor / release (push) Successful in 50s
2025-05-15 20:43:22 -05:00
6e5458de37 fix lock 2025-05-15 20:33:04 -05:00
b7ea8165d2 update dependencies
Some checks failed
release-image-gitea / release (push) Failing after 2m22s
release-image-harbor / release (push) Failing after 47s
2025-05-15 20:27:06 -05:00
ae4941073c update repo config 2025-03-20 01:15:10 -05:00
de87ffeff2 change tag 2025-03-14 22:55:16 -05:00
369e97af41 bump package image
Some checks failed
release-image-gitea / release (push) Successful in 54s
release-image-harbor / release (push) Failing after 52s
2025-03-14 22:37:55 -05:00
754ff5d9a9 add harbor 2025-03-14 22:37:03 -05:00
351cac00b3 use k8 driver 2025-03-14 22:20:45 -05:00
11c85e324e remove container 2025-03-14 21:52:01 -05:00
5a418428d3 change config 2025-03-14 21:26:34 -05:00
3d4c9c2214 remove verify 2025-03-14 21:16:17 -05:00
10262c4b7a remove certs 2025-03-14 21:15:07 -05:00
57ea8374a5 change workflow 2025-03-14 21:13:27 -05:00
e9e1cabd11 add podman 2025-03-14 21:04:35 -05:00
4c1ec680a9 use buildah 2025-03-14 20:58:33 -05:00
4f826e8964 add build step 2025-03-14 20:43:18 -05:00
3ddce86e64 change config 2025-03-14 20:32:49 -05:00
61aa06310c update 2025-03-14 20:15:23 -05:00
03195017c5 update version 2025-03-14 16:56:25 -05:00
fc3f4fdad4 fix workflow 2025-03-14 16:52:43 -05:00
fc42f31fb0 update images 2025-03-14 16:51:34 -05:00
renovate[bot]
e56b3a001e Update docker/build-push-action digest to 0adf995 (#38)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-02-19 23:17:00 -06:00
renovate[bot]
13711618b7 Update docker/build-push-action digest to 67a2d40 (#35)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-17 17:17:55 -06:00
04980a38af update tag 2025-01-09 21:31:14 -06:00
385ad20c82 update lock 2025-01-09 21:26:35 -06:00
761652f46d update dependencies 2025-01-09 21:25:00 -06:00
renovate[bot]
8f6b1af8ad Update docker/metadata-action digest to 8e1d546 (#32)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 21:17:30 -06:00
renovate[bot]
90c8d30e3f Update docker/login-action digest to 327cd5a (#31)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 21:17:19 -06:00
renovate[bot]
ad9128acea Update docker/build-push-action digest to 31ca4e5 (#29)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-01-09 21:16:45 -06:00
renovate[bot]
5f9235c9dc Migrate config .github/renovate.json (#27)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-21 19:53:06 -06:00
renovate[bot]
3b2702af36 Update renovate/renovate Docker tag to v39 (#24)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-21 19:52:28 -06:00
renovate[bot]
f999b9a92c Update docker/metadata-action digest to 906ecf0 (#21)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-21 19:50:40 -06:00
renovate[bot]
35c940bef7 Update docker/build-push-action digest to 7e09459 (#20)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-21 19:50:36 -06:00
renovate[bot]
7aa6898a93 Update docker/login-action digest to 7ca3450 (#15)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-12-21 19:50:29 -06:00
9d77c9db2a update dependencies 2024-12-21 19:48:14 -06:00
528eb8fb2e update lockfile 2024-09-30 17:15:52 -05:00
14e73d61ef update dependencies 2024-09-30 17:13:16 -05:00
renovate[bot]
d10fe280a5 Update dependency @astrojs/node to v8.3.4 (#12)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 17:08:27 -05:00
renovate[bot]
5ea5774042 Update docker/build-push-action digest to 4f58ea7 (#13)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 17:08:15 -05:00
renovate[bot]
3c82fb43d8 Update docker/login-action digest to 3b8fed7 (#10)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 17:07:40 -05:00
renovate[bot]
c7071ab583 Update docker/metadata-action digest to 70b2cdc (#11)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 17:07:27 -05:00
renovate[bot]
125d70d62e Update dependency typescript to v5.6.2 (#9)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 17:07:15 -05:00
renovate[bot]
357634d3f0 Update dependency @directus/sdk to v17.0.1 (#8)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 17:06:28 -05:00
renovate[bot]
bd4b85c874 Update dependency astro to v4.14.6 (#6)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-09-30 16:23:34 -05:00
7efa375427 remove author field 2024-08-24 02:09:34 -05:00
358d6b91c6 upgrade base image 2024-08-24 01:35:22 -05:00
92d4be91df upgrade astro 2024-08-24 01:34:16 -05:00
0f5fc27371 convert to use directus 2024-08-24 01:23:12 -05:00
fd85557d6b replace placeholder info with my own 2024-08-23 22:01:12 -05:00
f6c05b8a0c change work to projects 2024-08-23 21:01:26 -05:00
b8efef1a00 polishing pass 2024-08-23 20:46:46 -05:00
ad88da00e6 enable hyrbide rendering 2024-08-23 00:13:47 -05:00
ff50ff7e9a remove main release 2024-08-19 21:57:31 -05:00
a37da0c6ed add gitignore 2024-08-19 21:49:57 -05:00
5dfe806e6b remove default content and replace with myself 2024-08-19 21:47:37 -05:00
7ef4b48b18 add directus
Some checks failed
release-image / release-image (push) Has been cancelled
2024-08-19 21:31:24 -05:00
d321c805e1 bump version 2024-08-19 17:21:25 -05:00
ba28de9f61 change tags in release workflow 2024-08-19 17:19:56 -05:00
29bdf18fd6 change release workflow 2024-08-19 17:17:33 -05:00
3d5a1e12c0 bump docker tag 2024-08-19 17:15:19 -05:00
75284a9696 add labels 2024-08-19 17:11:51 -05:00
2701cbe8c5 fix lock file 2024-08-19 17:08:56 -05:00
e055360b68 used staged docker build 2024-08-19 17:03:41 -05:00
5a1a6ef72a add dockerignore 2024-08-19 17:00:40 -05:00
3ad8ec7a64 change default port 2024-08-19 16:59:04 -05:00
9f7ee247a0 reset readme 2024-08-19 16:49:10 -05:00
renovate[bot]
22f90ab925 Update renovate/renovate Docker tag to v38 (#4)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-19 16:46:10 -05:00
renovate[bot]
ff54bfcb95 Update docker/metadata-action digest to 60a0d34 (#3)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-19 16:45:52 -05:00
renovate[bot]
0843b2771a Update docker/login-action digest to 9780b0c (#2)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-19 16:45:41 -05:00
renovate[bot]
45a8f04ec4 Update docker/build-push-action digest to 5cd11c3 (#1)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-19 16:45:30 -05:00
3b7524c940 configure dockerfile for pnpm 2024-08-19 16:36:07 -05:00
63a7ddcb48 fix image tag 2024-08-19 16:26:53 -05:00
147 changed files with 16200 additions and 3706 deletions

5
.dockerignore Normal file
View File

@@ -0,0 +1,5 @@
.DS_Store
.astro
.vscode
node_modules
dist

View File

@@ -0,0 +1,98 @@
name: release-image
on:
push:
tags:
- 2.*
workflow_dispatch:
jobs:
release:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Login to Registry
uses: docker/login-action@v3
with:
registry: ${{ vars.REPOSITORY_HOST }}
username: ${{ gitea.actor }}
password: ${{ secrets.REPOSITORY_TOKEN }}
- 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
images: |
${{ vars.REPOSITORY_HOST }}/${{ gitea.repository }}
${{ vars.REGISTRY_HOST }}/images/site-profile
- 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 }}
file: ./Dockerfile
- name: ntfy Success
uses: niniyas/ntfy-action@master
if: success()
with:
url: '${{ secrets.NTFY_URL }}'
topic: '${{ secrets.NTFY_TOPIC }}'
title: 'Gitea Action'
priority: 3
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,successfully,completed
details: 'Site Profile build workflow has successfully completed!'
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: 'Gitea Action'
priority: 4
headers: '{"Authorization": "Bearer ${{ secrets.NTFY_CRED }}"}'
tags: action,failed
details: 'Site Profile build workflow has failed!'
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

View File

@@ -0,0 +1,32 @@
name: renovate
on:
schedule:
- cron: '@daily'
push:
branches:
- main
workflow_dispatch:
jobs:
renovate:
runs-on: ubuntu-latest
container: ghcr.io/renovatebot/renovate:41
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Renovate
run: renovate
env:
RENOVATE_PLATFORM: gitea
RENOVATE_ENDPOINT: ${{ vars.INSTANCE_URL }}
RENOVATE_REPOSITORIES: alexlebens/site-profile
RENOVATE_GIT_AUTHOR: Renovate Bot <renovate-bot@alexlebens.net>
LOG_LEVEL: info
RENOVATE_TOKEN: ${{ secrets.RENOVATE_TOKEN }}
RENOVATE_GIT_PRIVATE_KEY: ${{ secrets.RENOVATE_GIT_PRIVATE_KEY }}
RENOVATE_GITHUB_COM_TOKEN: ${{ secrets.RENOVATE_GITHUB_COM_TOKEN }}
RENOVATE_REDIS_URL: ${{ vars.RENOVATE_REDIS_URL }}

View File

@@ -0,0 +1,37 @@
name: test-build
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Set up pnpm
uses: pnpm/action-setup@v4
with:
version: 10.x
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: 22.18.0
cache: pnpm
- name: Install Dependencies
run: pnpm install
- name: Lint Code
run: pnpm lint
- name: Build Project
run: pnpm build

View File

@@ -1,2 +0,0 @@
# This file is processed by Renovate bot so that it creates a PR on new major Renovate versions
FROM renovate/renovate:37

44
.github/renovate.json vendored
View File

@@ -1,44 +0,0 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"mergeConfidence:all-badges",
":rebaseStalePrs"
],
"timezone": "US/Central",
"schedule": [
"every weekday"
],
"labels": [],
"prHourlyLimit": 0,
"prConcurrentLimit": 0,
"packageRules": [
{
"description": "Disables for non major Renovate version",
"matchPaths": [
".github/renovate-update-notification/Dockerfile"
],
"matchUpdateTypes": [
"minor",
"patch",
"pin",
"digest",
"rollback"
],
"enabled": false
},
{
"description": "Generate for major Renovate version",
"matchPaths": [
".github/renovate-update-notification/Dockerfile"
],
"matchUpdateTypes": [
"major"
],
"addLabels": [
"upgrade"
],
"automerge": false
}
]
}

View File

@@ -1,47 +0,0 @@
name: release-image
on:
push:
branches:
- main
tags:
- 6.*
env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}
jobs:
release-image:
permissions:
contents: read
packages: write
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v4
- name: Log into the container registry
uses: docker/login-action@e92390c5fb421da1463c202d546fed0ec5c39f20
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Extract metadata for Docker
id: meta
uses: docker/metadata-action@8e5442c4ef9f78752691e2d8f8d19755c6f78e81
with:
tags: |
type=ref,event=branch
type=ref,event=tag
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
- name: Build and push Docker image
uses: docker/build-push-action@2cdde995de11925a030ce8070c3d77a52ffcf1c0
with:
context: .
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
file: ./Dockerfile

5
.gitignore vendored
View File

@@ -20,5 +20,6 @@ pnpm-debug.log*
# macOS-specific files
.DS_Store
# jetbrains setting folder
.idea/
# ide
.vscode/
site-profile.code-workspace

3
.npmrc Normal file
View File

@@ -0,0 +1,3 @@
registry=https://registry.npmjs.org/
engine-strict=true
save-exact=true

1
.prettierignore Normal file
View File

@@ -0,0 +1 @@
/src/components/ui/sections/Experience.astro

View File

@@ -1,4 +0,0 @@
{
"recommendations": ["astro-build.astro-vscode"],
"unwantedRecommendations": []
}

11
.vscode/launch.json vendored
View File

@@ -1,11 +0,0 @@
{
"version": "0.2.0",
"configurations": [
{
"command": "./node_modules/.bin/astro dev",
"name": "Development server",
"request": "launch",
"type": "node-terminal"
}
]
}

View File

@@ -1,12 +1,36 @@
FROM 20.16.0-alpine3.20 AS runtime
ARG REGISTRY=docker.io
FROM ${REGISTRY}/node:22.18.0-alpine3.22 AS base
LABEL version="2.0.1"
LABEL description="Astro based personal website"
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
COPY . .
COPY package.json pnpm-lock.yaml ./
RUN pnpm install
FROM base AS prod-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --prod --frozen-lockfile
FROM prod-deps AS build-deps
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
FROM build-deps AS build
COPY . .
RUN pnpm run build
RUN pnpm prune --prod
FROM base AS runtime
COPY --from=prod-deps /app/node_modules /app/node_modules
COPY --from=build /app/dist /app/dist
ENV HOST=0.0.0.0
ENV SITE_URL=https://www.alexlebens.dev
ENV DIRECTUS_URL=https://directus.alexlebens.dev
ENV PORT=4321
EXPOSE 4321
CMD node ./dist/server/entry.mjs
EXPOSE $PORT
CMD ["node", "./dist/server/entry.mjs"]

View File

@@ -1,6 +1,6 @@
MIT License
Copyright (c) 2024 Alex Lebens
Copyright (c) 2025 Alex Lebens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal

23
LICENSE.md Normal file
View File

@@ -0,0 +1,23 @@
# MIT License
Copyright (c) 2025 Lê Vĩnh Khang
Copyright (c) 2025 Alex Lebens
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,30 +1,31 @@
# Astro Starter Kit: Portfolio
# This is an open-source and simple blog built with Astro.
```sh
npm create astro@latest -- --template portfolio
```
Personal site used for information about myself and blog.
[![Open in StackBlitz](https://developer.stackblitz.com/img/open_in_stackblitz.svg)](https://stackblitz.com/github/withastro/astro/tree/latest/examples/portfolio)
[![Open with CodeSandbox](https://assets.codesandbox.io/github/button-edit-lime.svg)](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/portfolio)
[![Open in GitHub Codespaces](https://github.com/codespaces/badge.svg)](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/portfolio/devcontainer.json)
## Features
> 🧑‍🚀 **Seasoned astronaut?** Delete this file. Have fun!
- 🐈 Simple And Beautiful
- 🖥️️ Responsive And Light/Dark mode
- 🐛 SiteMap & RSS Feed
- 🐝 Category Support
- 🐜 SEO and Responsiveness
- 🪲 Markdown And MDX
- 🏂🏾 Page Compression & Image Optimization
![portfolio](https://user-images.githubusercontent.com/357379/210779178-a98f0fb7-6b1a-4068-894c-8e1403e26654.jpg)
### Development Commands
## 🧞 Commands
With dependencies installed, you can utilize the following npm scripts to manage your project's development lifecycle:
All commands are run from the root of the project, from a terminal:
- `pnpm run dev`: Starts a local development server with hot reloading enabled.
- `pnpm run preview`: Serves your build output locally for preview before deployment.
- `pnpm run build`: Bundles your site into static files for production.
| Command | Action |
| :------------------------ | :----------------------------------------------- |
| `npm install` | Installs dependencies |
| `npm run dev` | Starts local dev server at `localhost:4321` |
| `npm run build` | Build your production site to `./dist/` |
| `npm run preview` | Preview your build locally, before deploying |
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
| `npm run astro -- --help` | Get help using the Astro CLI |
For detailed help with Astro CLI commands, visit [Astro's documentation](https://docs.astro.build/en/reference/cli-reference/).
## 👀 Want to learn more?
## Thanks
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
Thanks https://github.com/mearashadowfax/ScrewFast, https://github.com/godruoyi/gblog/tree/gblog-template
## License
This project is released under the MIT License. Please read the [LICENSE](https://gitea.alexlebens.dev/alexlebens/site-profile/src/LICENSE.md) file for more details.

View File

@@ -1,11 +1,112 @@
import { defineConfig } from 'astro/config';
import { defineConfig, passthroughImageService, sharpImageService } from 'astro/config';
import node from "@astrojs/node";
import mdx from '@astrojs/mdx';
import node from '@astrojs/node';
import partytown from '@astrojs/partytown';
import react from '@astrojs/react';
import sitemap from '@astrojs/sitemap';
import tailwindcss from '@tailwindcss/vite';
import icon from 'astro-icon';
import swup from '@swup/astro';
import rehypePrettyCode from 'rehype-pretty-code';
import { transformerCopyButton } from '@rehype-pretty/transformers';
const getSiteURL = () => {
if (process.env.SITE_URL) {
return `https://${process.env.SITE_URL}`;
}
return 'http://localhost:4321';
};
// https://astro.build/config
export default defineConfig({
output: "server",
site: getSiteURL(),
image: {
service: {
entrypoint: 'astro/assets/services/sharp',
}
},
prefetch: true,
integrations: [
mdx(),
partytown(),
react(),
sitemap(),
icon({
include: {
mdi: ['*'],
},
}),
swup({
theme: 'fade',
native: true,
cache: true,
preload: true,
accessibility: true,
smoothScrolling: true,
morph: ['#nav'],
}),
(await import('@playform/compress')).default({
CSS: true,
JavaScript: true,
HTML: {
'html-minifier-terser': {
collapseWhitespace: true,
minifyCSS: false,
minifyJS: true,
},
},
Image: false,
SVG: true,
Logger: 2,
}),
],
markdown: {
syntaxHighlight: false,
rehypePlugins: [
[
rehypePrettyCode,
{
theme: {
light: 'github-light',
dark: 'github-dark-dimmed',
},
keepBackground: false,
transformers: [
transformerCopyButton({
visibility: 'always',
feedbackDuration: 2500,
}),
],
},
],
],
},
plugins: {
'@tailwindcss/postcss': {},
},
vite: {
plugins: [tailwindcss()],
},
output: 'static',
adapter: node({
mode: "standalone"
})
mode: 'standalone',
}),
build: {
// Specifies the directory in the build output where Astro-generated assets (bundled JS and CSS for example) should live.
// see https://docs.astro.build/en/reference/configuration-reference/#buildassets
assets: 'assets',
// see https://docs.astro.build/en/reference/configuration-reference/#buildassetsprefix
assetsPrefix:
!!import.meta.env.S3_ENABLE || !!process.env.S3_ENABLE ? 'https://digitalocean.com' : '',
},
});

11
eslint.config.mjs Normal file
View File

@@ -0,0 +1,11 @@
import eslintPluginAstro from 'eslint-plugin-astro';
import eslintConfigPrettier from "eslint-config-prettier/flat";
export default [
...eslintPluginAstro.configs.recommended,
eslintConfigPrettier,
{
rules: {
}
}
];

View File

@@ -1,18 +1,84 @@
{
"name": "",
"name": "site-profile",
"type": "module",
"version": "0.0.1",
"version": "2.0.1",
"homepage": "https://www.alexlebens.dev",
"bugs": {
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile/issues",
"email": "alexander.lebens@gmail.com"
},
"repository": {
"type": "git",
"url": "https://gitea.alexlebens.dev/alexlebens/site-profile"
},
"license": "MIT",
"author": {
"name": "Alex Lebens",
"email": "alexander.lebens@gmail.com",
"url": "https://www.alexlebens.dev"
},
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro check && astro build",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
"astro": "astro",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,md,mdx,astro}\"",
"lint": "eslint \"src/**/*.{js,ts,jsx,tsx,astro}\"",
"lint:fix": "eslint --fix \"src/**/*.{js,ts,jsx,tsx,astro}\""
},
"dependencies": {
"@astrojs/check": "^0.9.3",
"@astrojs/node": "^8.3.3",
"astro": "^4.14.2",
"typescript": "^5.5.4"
"@astrojs/check": "^0.9.4",
"@astrojs/mdx": "^4.3.3",
"@astrojs/node": "^9.3.3",
"@astrojs/partytown": "^2.1.4",
"@astrojs/react": "^4.3.0",
"@astrojs/rss": "^4.0.12",
"@astrojs/sitemap": "^3.4.2",
"@giscus/react": "^3.1.0",
"@iconify-json/mdi": "^1.1.63",
"@iconify-json/pajamas": "^1.2.13",
"@iconify-json/simple-icons": "^1.2.47",
"@playform/compress": "^0.2.0",
"@rehype-pretty/transformers": "^0.13.2",
"@swup/astro": "1.7.0",
"@tailwindcss/postcss": "^4.1.8",
"@tailwindcss/vite": "^4.1.8",
"@directus/sdk": "^20.0.0",
"@types/react": "^19.0.0",
"@types/unist": "^3.0.2",
"astro": "^5.12.8",
"astro-compressor": "^1.0.0",
"astro-icon": "^1.1.5",
"framer-motion": "^12.16.0",
"mdast-util-to-string": "^4.0.0",
"preline": "^3.1.0",
"react": "^19.1.0",
"react-dom": "^19.1.0",
"reading-time": "^1.5.0",
"rehype-pretty-code": "^0.14.1",
"sharp": "^0.34.3",
"sharp-ico": "^0.1.5",
"shiki": "^3.2.2",
"tailwindcss": "^4.1.11",
"ultrahtml": "^1.5.3"
},
"devDependencies": {
"@eslint-react/eslint-plugin": "^1.52.3",
"@tailwindcss/forms": "^0.5.10",
"@tailwindcss/typography": "^0.5.16",
"astro-icon": "^1.1.5",
"eslint": "^9.32.0",
"eslint-config-prettier": "^10.1.8",
"eslint-plugin-astro": "^1.3.1",
"eslint-plugin-format": "^1.0.1",
"eslint-plugin-react": "^7.37.5",
"eslint-plugin-react-hooks": "^5.2.0",
"eslint-plugin-react-refresh": "^0.4.20",
"prettier": "^3.5.3",
"prettier-plugin-astro": "^0.14.1",
"prettier-plugin-tailwindcss": "^0.6.14",
"timeago.js": "^4.0.2",
"typescript": "5.9.2",
"typescript-eslint": "8.39.1"
}
}

11934
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

2
pnpm-workspace.yaml Normal file
View File

@@ -0,0 +1,2 @@
onlyBuiltDependencies:
- swup

8
postcss.config.mjs Normal file
View File

@@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
'@tailwindcss/postcss': {},
},
};
export default config;

23
prettier.config.mjs Normal file
View File

@@ -0,0 +1,23 @@
/** @type {import("prettier").Config} */
const config = {
printWidth: 100,
semi: true,
singleQuote: true,
tabWidth: 2,
trailingComma: 'es5',
useTabs: false,
plugins: [
'prettier-plugin-astro',
'prettier-plugin-tailwindcss',
],
overrides: [
{
files: '*.astro',
options: {
parser: 'astro',
},
},
],
};
export default config;

Binary file not shown.

Before

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -1 +0,0 @@
<svg height="640" width="1440" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><linearGradient id="a"><stop offset=".58" stop-opacity="0"/><stop offset="1"/></linearGradient><linearGradient id="b" gradientUnits="userSpaceOnUse" x1="793.5" x2="759.5" xlink:href="#a" y1="261.5" y2="149.5"/><linearGradient id="c" gradientUnits="userSpaceOnUse" x1="644.19" x2="645.54" xlink:href="#a" y1="398.02" y2="267.7"/><linearGradient id="d" gradientUnits="userSpaceOnUse" x1="547" x2="522.36" xlink:href="#a" y1="457.27" y2="342.85"/><g clip-rule="evenodd" fill-rule="evenodd" opacity=".15"><path d="m439.57 249.55a2149.47 2149.47 0 0 1 1193.87-182.45l-12.48 93.17a2055.46 2055.46 0 0 0 -1141.66 174.47l-454.24 211.86-39.73-85.2z" fill="url(#b)"/><path d="m272.3 266.93a2393.36 2393.36 0 0 1 1328.96 205.6l-44.42 94.78a2288.7 2288.7 0 0 0 -1270.84-196.61l-553.29 73.05-13.7-103.77z" fill="url(#c)" opacity=".56"/><path d="m195.26 416.13a2149.46 2149.46 0 0 1 1204.86-83.21l-20.13 91.82a2055.46 2055.46 0 0 0 -1152.17 79.56l-470.18 173.62-32.56-88.18 470.18-173.62z" fill="url(#d)"/></g><path d="m-258.15 719.56 1743.12-517.56 182.93 616.12-1743.1 517.56z" fill="#090b11"/></svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="1440" height="640"><g opacity=".15"><path fill="url(#a)" d="M439.57 249.55A2149.47 2149.47 0 0 1 1633.44 67.1l-12.48 93.17A2055.46 2055.46 0 0 0 479.3 334.74L25.06 546.6l-39.73-85.2z"/><path fill="url(#b)" d="M272.3 265.93a2393.36 2393.36 0 0 1 1328.96 205.6l-44.42 94.78A2288.7 2288.7 0 0 0 286 369.7l-553.29 73.05-13.7-103.77z" opacity=".56"/><path fill="url(#c)" d="M195.26 416.13a2149.47 2149.47 0 0 1 1204.86-83.21l-20.13 91.82A2055.46 2055.46 0 0 0 227.82 504.3l-470.18 173.62-32.56-88.18 470.18-173.62z"/></g><path fill="#fff" d="M-258 718.56 1485.12 201l182.93 616.12-1743.11 517.56z"/><defs><linearGradient id="d"><stop offset=".58" stop-opacity="0"/><stop offset="1"/></linearGradient><linearGradient xlink:href="#d" id="a" x1="793.5" x2="759.5" y1="261.5" y2="149.5" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#d" id="b" x1="644.19" x2="645.54" y1="397.02" y2="266.7" gradientUnits="userSpaceOnUse"/><linearGradient xlink:href="#d" id="c" x1="547" x2="522.36" y1="457.27" y2="342.85" gradientUnits="userSpaceOnUse"/></defs></svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.2 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 128 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 38 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 749 B

After

Width:  |  Height:  |  Size: 9.9 KiB

BIN
public/i.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 381 KiB

4
public/robots.txt Normal file
View File

@@ -0,0 +1,4 @@
User-agent: *
Allow: /
Sitemap: https://www.alexlebens.dev/sitemap-index.xml

View File

@@ -0,0 +1,352 @@
/**
* Skipped minification because the original files appears to be already minified.
* Original file: /npm/@preline/collapse@2.1.0/index.js
*
* Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
*/
!(function (t, e) {
if ('object' == typeof exports && 'object' == typeof module) module.exports = e();
else if ('function' == typeof define && define.amd) define([], e);
else {
var n = e();
for (var o in n) ('object' == typeof exports ? exports : t)[o] = n[o];
}
})(self, () =>
(() => {
'use strict';
var t = {
737: (t, e) => {
/*
* HSBasePlugin
* @version: 2.1.0
* @author: HTMLStream
* @license: Licensed under MIT (https://preline.co/docs/license.html)
* Copyright 2023 HTMLStream
*/
Object.defineProperty(e, '__esModule', { value: !0 });
var n = (function () {
function t(t, e, n) {
((this.el = t),
(this.options = e),
(this.events = n),
(this.el = t),
(this.options = e),
(this.events = {}));
}
return (
(t.prototype.createCollection = function (t, e) {
var n;
t.push({
id:
(null === (n = null == e ? void 0 : e.el) || void 0 === n ? void 0 : n.id) ||
t.length + 1,
element: e,
});
}),
(t.prototype.fireEvent = function (t, e) {
if ((void 0 === e && (e = null), this.events.hasOwnProperty(t)))
return this.events[t](e);
}),
(t.prototype.on = function (t, e) {
this.events[t] = e;
}),
t
);
})();
e.default = n;
},
652: function (t, e, n) {
/*
* HSCollapse
* @version: 2.1.0
* @author: HTMLStream
* @license: Licensed under MIT (https://preline.co/docs/license.html)
* Copyright 2023 HTMLStream
*/
var o,
i =
(this && this.__extends) ||
((o = function (t, e) {
return (
(o =
Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array &&
function (t, e) {
t.__proto__ = e;
}) ||
function (t, e) {
for (var n in e) Object.prototype.hasOwnProperty.call(e, n) && (t[n] = e[n]);
}),
o(t, e)
);
}),
function (t, e) {
if ('function' != typeof e && null !== e)
throw new TypeError(
'Class extends value ' + String(e) + ' is not a constructor or null'
);
function n() {
this.constructor = t;
}
(o(t, e),
(t.prototype =
null === e ? Object.create(e) : ((n.prototype = e.prototype), new n())));
});
Object.defineProperty(e, '__esModule', { value: !0 });
var s = n(969),
r = (function (t) {
function e(e, n, o) {
var i = t.call(this, e, n, o) || this;
return (
(i.contentId = i.el.dataset.hsCollapse),
(i.content = document.querySelector(i.contentId)),
(i.animationInProcess = !1),
i.content && i.init(),
i
);
}
return (
i(e, t),
(e.prototype.init = function () {
var t = this;
(this.createCollection(window.$hsCollapseCollection, this),
this.el.addEventListener('click', function () {
t.content.classList.contains('open') ? t.hide() : t.show();
}));
}),
(e.prototype.hideAllMegaMenuItems = function () {
this.content
.querySelectorAll('.hs-mega-menu-content.block')
.forEach(function (t) {
(t.classList.remove('block'), t.classList.add('hidden'));
});
}),
(e.prototype.show = function () {
var t = this;
if (this.animationInProcess || this.el.classList.contains('open')) return !1;
((this.animationInProcess = !0),
this.el.classList.add('open'),
this.content.classList.add('open'),
this.content.classList.remove('hidden'),
(this.content.style.height = '0'),
setTimeout(function () {
((t.content.style.height = ''.concat(t.content.scrollHeight, 'px')),
t.fireEvent('beforeOpen', t.el),
(0, s.dispatch)('beforeOpen.hs.collapse', t.el, t.el));
}),
(0, s.afterTransition)(this.content, function () {
((t.content.style.height = ''),
t.fireEvent('open', t.el),
(0, s.dispatch)('open.hs.collapse', t.el, t.el),
(t.animationInProcess = !1));
}));
}),
(e.prototype.hide = function () {
var t = this;
if (this.animationInProcess || !this.el.classList.contains('open')) return !1;
((this.animationInProcess = !0),
this.el.classList.remove('open'),
(this.content.style.height = ''.concat(this.content.scrollHeight, 'px')),
setTimeout(function () {
t.content.style.height = '0';
}),
this.content.classList.remove('open'),
(0, s.afterTransition)(this.content, function () {
(t.content.classList.add('hidden'),
(t.content.style.height = ''),
t.fireEvent('hide', t.el),
(0, s.dispatch)('hide.hs.collapse', t.el, t.el),
(t.animationInProcess = !1));
}),
this.content.querySelectorAll('.hs-mega-menu-content.block').length &&
this.hideAllMegaMenuItems());
}),
(e.getInstance = function (t, e) {
void 0 === e && (e = !1);
var n = window.$hsCollapseCollection.find(function (e) {
return e.element.el === ('string' == typeof t ? document.querySelector(t) : t);
});
return n ? (e ? n : n.element.el) : null;
}),
(e.autoInit = function () {
(window.$hsCollapseCollection || (window.$hsCollapseCollection = []),
document
.querySelectorAll('.hs-collapse-toggle:not(.--prevent-on-load-init)')
.forEach(function (t) {
window.$hsCollapseCollection.find(function (e) {
var n;
return (
(null === (n = null == e ? void 0 : e.element) || void 0 === n
? void 0
: n.el) === t
);
}) || new e(t);
}));
}),
(e.show = function (t) {
var e = window.$hsCollapseCollection.find(function (e) {
return e.element.el === ('string' == typeof t ? document.querySelector(t) : t);
});
e && e.element.content.classList.contains('hidden') && e.element.show();
}),
(e.hide = function (t) {
var e = window.$hsCollapseCollection.find(function (e) {
return e.element.el === ('string' == typeof t ? document.querySelector(t) : t);
});
e && !e.element.content.classList.contains('hidden') && e.element.hide();
}),
(e.on = function (t, e, n) {
var o = window.$hsCollapseCollection.find(function (t) {
return t.element.el === ('string' == typeof e ? document.querySelector(e) : e);
});
o && (o.element.events[t] = n);
}),
e
);
})(n(737).default);
(window.addEventListener('load', function () {
r.autoInit();
}),
'undefined' != typeof window && (window.HSCollapse = r),
(e.default = r));
},
969: function (t, e) {
var n = this;
(Object.defineProperty(e, '__esModule', { value: !0 }),
(e.menuSearchHistory =
e.classToClassList =
e.htmlToElement =
e.afterTransition =
e.dispatch =
e.debounce =
e.isFormElement =
e.isParentOrElementHidden =
e.isEnoughSpace =
e.isIpadOS =
e.isIOS =
e.getClassPropertyAlt =
e.getClassProperty =
e.stringToBoolean =
void 0));
e.stringToBoolean = function (t) {
return 'true' === t;
};
e.getClassProperty = function (t, e, n) {
return (
void 0 === n && (n = ''),
(window.getComputedStyle(t).getPropertyValue(e) || n).replace(' ', '')
);
};
e.getClassPropertyAlt = function (t, e, n) {
void 0 === n && (n = '');
var o = '';
return (
t.classList.forEach(function (t) {
t.includes(e) && (o = t);
}),
o.match(/:(.*)]/) ? o.match(/:(.*)]/)[1] : n
);
};
e.isIOS = function () {
return (
!!/iPad|iPhone|iPod/.test(navigator.platform) ||
(navigator.maxTouchPoints &&
navigator.maxTouchPoints > 2 &&
/MacIntel/.test(navigator.platform))
);
};
e.isIpadOS = function () {
return (
navigator.maxTouchPoints &&
navigator.maxTouchPoints > 2 &&
/MacIntel/.test(navigator.platform)
);
};
e.isEnoughSpace = function (t, e, n, o, i) {
(void 0 === n && (n = 'auto'), void 0 === o && (o = 10), void 0 === i && (i = null));
var s = e.getBoundingClientRect(),
r = i ? i.getBoundingClientRect() : null,
l = window.innerHeight,
c = r ? s.top - r.top : s.top,
a = (i ? r.bottom : l) - s.bottom,
u = t.clientHeight + o;
return 'bottom' === n ? a >= u : 'top' === n ? c >= u : c >= u || a >= u;
};
e.isFormElement = function (t) {
return (
t instanceof HTMLInputElement ||
t instanceof HTMLTextAreaElement ||
t instanceof HTMLSelectElement
);
};
var o = function (t) {
return !!t && ('none' === window.getComputedStyle(t).display || o(t.parentElement));
};
e.isParentOrElementHidden = o;
e.debounce = function (t, e) {
var o;
return (
void 0 === e && (e = 200),
function () {
for (var i = [], s = 0; s < arguments.length; s++) i[s] = arguments[s];
(clearTimeout(o),
(o = setTimeout(function () {
t.apply(n, i);
}, e)));
}
);
};
e.dispatch = function (t, e, n) {
void 0 === n && (n = null);
var o = new CustomEvent(t, {
detail: { payload: n },
bubbles: !0,
cancelable: !0,
composed: !1,
});
e.dispatchEvent(o);
};
e.afterTransition = function (t, e) {
var n = function () {
(e(), t.removeEventListener('transitionend', n, !0));
};
window.getComputedStyle(t, null).getPropertyValue('transition') !==
(navigator.userAgent.includes('Firefox') ? 'all' : 'all 0s ease 0s')
? t.addEventListener('transitionend', n, !0)
: e();
};
e.htmlToElement = function (t) {
var e = document.createElement('template');
return ((t = t.trim()), (e.innerHTML = t), e.content.firstChild);
};
e.classToClassList = function (t, e, n, o) {
(void 0 === n && (n = ' '),
void 0 === o && (o = 'add'),
t.split(n).forEach(function (t) {
return 'add' === o ? e.classList.add(t) : e.classList.remove(t);
}));
};
e.menuSearchHistory = {
historyIndex: -1,
addHistory: function (t) {
this.historyIndex = t;
},
existsInHistory: function (t) {
return t > this.historyIndex;
},
clearHistory: function () {
this.historyIndex = -1;
},
};
},
},
e = {};
var n = (function n(o) {
var i = e[o];
if (void 0 !== i) return i.exports;
var s = (e[o] = { exports: {} });
return (t[o].call(s.exports, s, s.exports, n), s.exports);
})(652);
return n;
})()
);

40
renovate.json Normal file
View File

@@ -0,0 +1,40 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": [
"config:recommended",
"mergeConfidence:all-badges",
":rebaseStalePrs"
],
"timezone": "US/Central",
"labels": [],
"prHourlyLimit": 0,
"prConcurrentLimit": 0,
"packageRules": [
{
"description": "Label dependency",
"matchDatasources": [
"npm"
],
"addLabels": [
"dependency"
],
"automerge": false,
"minimumReleaseAge": "1 days"
},
{
"description": "Automerge dependency patch",
"matchDatasources": [
"npm"
],
"matchUpdateTypes": [
"patch"
],
"addLabels": [
"dependency",
"automerge"
],
"automerge": true,
"minimumReleaseAge": "1 days"
}
]
}

View File

@@ -0,0 +1,10 @@
{
"folders": [
{
"path": "."
}
],
"settings": {
"typescript.tsdk": "node_modules/typescript/lib"
}
}

View File

@@ -0,0 +1,95 @@
---
import { getImage } from 'astro:assets';
import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus';
import brandSrc from '@images/brand_logo.png';
import faviconSvgSrc from '@images/favicon_icon.svg';
import faviconSrc from '@images/favicon_icon.png';
import { SEO } from '@/config';
interface Props {
title: string;
description: string;
ogImage?: any;
ogTitle?: string;
ogDescription?: string;
structuredData?: object;
}
const canonicalURL = Astro.url.href;
let {
title,
description,
ogImage,
ogTitle = title,
ogDescription = description,
structuredData = SEO.structuredData,
} = Astro.props;
let card = 'summary_large_image';
if (!ogImage) {
ogImage = brandSrc;
card = 'summary';
}
const global = await directus.request(readSingleton('site_global'));
const faviconSvg = await getImage({ src: faviconSvgSrc, format: 'svg' });
const appleTouchIcon = await getImage({ src: faviconSrc, width: 180, height: 180, format: 'png' });
const socialImageRes = await getImage({ src: ogImage, width: 1200, height: 600 });
let socialImage = socialImageRes.src;
if (!socialImage.startsWith('http')) {
socialImage = Astro.url.origin + socialImageRes.src;
}
---
<!-- Inject structured data https://developers.google.com/search/docs/advanced/structured-data/intro-structured-data -->{
structuredData && <script type="application/ld+json" set:html={JSON.stringify(structuredData)} />
}
<!-- Global Metadata -->
<meta name="title" content={title} />
<meta name="description" content={description} />
<meta charset="utf-8" />
<meta name="web_author" content={global.name} />
<meta
name="viewport"
content="width=device-width, initial-scale=1.0, maximum-scale=5.0, minimum-scale=1.0"
/>
<meta name="generator" content={Astro.generator} />
<meta http-equiv="X-UA-Compatible" content="ie=edge" />
<meta name="mobile-web-app-capable" content="yes" />
<meta name="theme-color" content="#facc15" />
<!-- Open Graph -->
<meta property="og:type" content="website" />
<meta property="og:locale" content="en_US" />
<meta property="og:url" content={Astro.url} />
<meta property="og:type" content="website" />
<meta property="og:title" content={ogTitle} />
<meta property="og:site_name" content={global.name} />
<meta property="og:description" content={ogDescription} />
<meta property="og:image" content={socialImage} />
<meta content="1200" property="og:image:width" />
<meta content="600" property="og:image:height" />
<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 -->
<link href={canonicalURL} rel="canonical" />
<link rel="sitemap" href="/sitemap-index.xml" />
<!--<link href="/manifest.json" rel="manifest" />-->
<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={appleTouchIcon.src} rel="apple-touch-icon" />
<link href={appleTouchIcon.src} rel="shortcut icon" />
<link rel="preconnect" href="https://461ZQ3AX3S-dsn.algolia.net" crossorigin />

View File

@@ -1,56 +0,0 @@
---
interface Props {
href: string;
}
const { href } = Astro.props;
---
<a href={href}><slot /></a>
<style>
a {
position: relative;
display: flex;
place-content: center;
text-align: center;
padding: 0.56em 2em;
gap: 0.8em;
color: var(--accent-text-over);
text-decoration: none;
line-height: 1.1;
border-radius: 999rem;
overflow: hidden;
background: var(--gradient-accent-orange);
box-shadow: var(--shadow-md);
white-space: nowrap;
}
@media (min-width: 20em) {
a {
font-size: var(--text-lg);
}
}
/* Overlay for hover effects. */
a::after {
content: '';
position: absolute;
inset: 0;
pointer-events: none;
transition: background-color var(--theme-transition);
mix-blend-mode: overlay;
}
a:focus::after,
a:hover::after {
background-color: hsla(var(--gray-999-basis), 0.3);
}
@media (min-width: 50em) {
a {
padding: 1.125rem 2.5rem;
font-size: var(--text-xl);
}
}
</style>

View File

@@ -1,46 +0,0 @@
---
import CallToAction from './CallToAction.astro';
import Icon from './Icon.astro';
---
<aside>
<h2>Interested in working together?</h2>
<CallToAction href="mailto:me@example.com">
Send Me a Message
<Icon icon="paper-plane-tilt" size="1.2em" />
</CallToAction>
</aside>
<style>
aside {
display: flex;
flex-direction: column;
align-items: center;
gap: 3rem;
border-top: 1px solid var(--gray-800);
border-bottom: 1px solid var(--gray-800);
padding: 5rem 1.5rem;
background-color: var(--gray-999_40);
box-shadow: var(--shadow-sm);
}
h2 {
font-size: var(--text-xl);
text-align: center;
max-width: 15ch;
}
@media (min-width: 50em) {
aside {
padding: 7.5rem;
flex-direction: row;
flex-wrap: wrap;
justify-content: space-between;
}
h2 {
font-size: var(--text-3xl);
text-align: left;
}
}
</style>

View File

@@ -1,74 +1,144 @@
---
import Icon from './Icon.astro';
import { readSingleton } from '@directus/sdk';
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 footerImg from '@images/flowers.png';
const global = await directus.request(readSingleton('site_global'));
const currentYear = new Date().getFullYear();
---
<footer>
<div class="group">
<p>
Designed & Developed in Portland with <a href="https://astro.build/">Astro</a>
<Icon icon="rocket-launch" size="1.2em" />
</p>
<p>&copy; {currentYear} Jeanine White</p>
<footer
class="w-full overflow-hidden bg-stone-300/40 dark:bg-stone-800/20"
transition:animate="none"
>
<div class="relative px-4 pt-16 pb-12 sm:px-6">
<div class="mx-auto max-w-[85rem]">
<div class="grid grid-cols-1 gap-10 md:grid-cols-12">
<!-- Brand section -->
<div class="col-span-1 md:col-span-3">
<a href="/" class="group inline-block">
<div class="flex items-center">
<div class="mx-auto aspect-square overflow-hidden rounded-lg">
<BrandLogo class="max-h-[40px] max-w-[40px] rounded-full" />
</div>
<p class="socials">
<a href="https://twitter.com/me"> Twitter</a>
<a href="https://github.com/me"> GitHub</a>
<a href="https://codepen.io/me"> CodePen</a>
<span class="ml-3 text-xl font-bold text-neutral-800 dark:text-neutral-200">
{global.name}
</span>
</div>
</a>
<p class="mt-4 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400">
{global.about}
</p>
</div>
<!-- Left links -->
<div class="col-span-1 md:col-span-2">
<h3
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"
>
Blog
</h3>
<ul class="mt-4 space-y-3">
{
NavigationLinks.map((link) => (
<li>
<a
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"
>
<span class="relative inline-block overflow-hidden">
<span class="relative z-10">{link.name}</span>
</span>
</a>
</li>
))
}
</ul>
</div>
<!-- Right links -->
<div class="col-span-1 md:col-span-3">
<h3
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
</h3>
<ul class="mt-4 space-y-3">
{
FooterLinks.map((link) => (
<li>
<a
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"
>
<span class="relative inline-block overflow-hidden">
<span class="relative z-10">{link.name}</span>
</span>
</a>
</li>
))
}
</ul>
</div>
<!-- Right image -->
<div class="col-span-3 mt-10 flex justify-center md:mt-0">
<div class="-mt-10 hidden max-h-[460px] max-w-[220px] scale-80 md:block">
<Image
src={footerImg}
alt={global.footer_image_alt}
class="h-full w-full object-cover object-center"
draggable="false"
loading="eager"
format="webp"
quality="low"
widths={[440]}
disableBlur={true}
/>
</div>
</div>
</div>
<!-- Bottom section -->
<div class="mt-12 border-t border-neutral-400/30 pt-8 dark:border-neutral-600/50">
<div class="flex flex-col items-center justify-between gap-4 md:flex-row">
<p class="text-sm text-neutral-600 dark:text-neutral-400">
&copy; {currentYear} All rights reserved.
</p>
<div class="flex items-center space-x-2">
<span class="text-xs text-neutral-500 dark:text-neutral-400">Built with </span>
<a
href="https://astro.build"
target="_blank"
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"
>
<svg class="mr-1 h-4 w-4 text-[#FF5D01]" viewBox="0 0 36 36" fill="none">
<path
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>
</a>
</div>
</div>
</div>
</div>
</div>
</footer>
<style>
footer {
display: flex;
flex-direction: column;
gap: 3rem;
margin-top: auto;
padding: 3rem 2rem 3rem;
text-align: center;
color: var(--gray-400);
font-size: var(--text-sm);
}
footer a {
color: var(--gray-400);
text-decoration: 1px solid underline transparent;
text-underline-offset: 0.25em;
transition: text-decoration-color var(--theme-transition);
}
footer a:hover,
footer a:focus {
text-decoration-color: currentColor;
}
.group {
display: flex;
flex-direction: column;
gap: 0.5rem;
}
.socials {
display: flex;
justify-content: center;
gap: 1rem;
flex-wrap: wrap;
}
@media (min-width: 50em) {
footer {
flex-direction: row;
justify-content: space-between;
padding: 2.5rem 5rem;
}
.group {
flex-direction: row;
gap: 1rem;
flex-wrap: wrap;
}
.socials {
justify-content: flex-end;
}
}
</style>

View File

@@ -1,65 +0,0 @@
---
interface Props {
variant?: 'offset' | 'small';
}
const { variant } = Astro.props;
---
<ul class:list={['grid', { offset: variant === 'offset', small: variant === 'small' }]}>
<slot />
</ul>
<style>
.grid {
display: grid;
grid-auto-rows: 1fr;
gap: 1rem;
list-style: none;
padding: 0;
}
.grid.small {
grid-template-columns: 1fr 1fr;
gap: 1.5rem;
}
/* If last row contains only one item, make it span both columns. */
.grid.small > :global(:last-child:nth-child(odd)) {
grid-column: 1 / 3;
}
@media (min-width: 50em) {
.grid {
grid-template-columns: 1fr 1fr;
gap: 4rem;
}
.grid.offset {
--row-offset: 7.5rem;
padding-bottom: var(--row-offset);
}
/* Shift first item in each row vertically to create staggered effect. */
.grid.offset > :global(:nth-child(odd)) {
transform: translateY(var(--row-offset));
}
/* If last row contains only one item, display it in the second column. */
.grid.offset > :global(:last-child:nth-child(odd)) {
grid-column: 2 / 3;
transform: none;
}
.grid.small {
display: flex;
flex-wrap: wrap;
justify-content: center;
gap: 2rem;
}
.grid.small > :global(*) {
flex-basis: 20rem;
}
}
</style>

100
src/components/Header.astro Normal file
View File

@@ -0,0 +1,100 @@
---
import BrandLogo from '@components/ui/logos/BrandLogo.astro';
import ThemeToggle from '@components/ui/buttons/ThemeToggle.astro';
import { NavigationLinks } from '@/config';
const pathname = new URL(Astro.request.url).pathname;
const currentPath = pathname.slice(1);
---
<header
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"
>
<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"
aria-label="Global"
>
<div class="flex items-center justify-between">
<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"
href="/"
aria-label="Brand"
>
<BrandLogo class="h-full w-auto rounded-full object-cover" />
</a>
<div class="ml-auto md:hidden">
<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"
data-hs-collapse="#navbar-collapse-with-animation"
aria-controls="navbar-collapse-with-animation"
aria-label="Toggle navigation"
>
<svg
class="hs-collapse-open:hidden h-[1.25rem] w-[1.25rem] flex-shrink-0"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<line x1="3" x2="21" y1="6" y2="6"></line>
<line x1="3" x2="21" y1="12" y2="12"></line>
<line x1="3" x2="21" y1="18" y2="18"></line>
</svg>
<svg
class="hs-collapse-open:block hidden h-[1.25rem] w-[1.25rem] flex-shrink-0"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M18 6 6 18"></path>
<path d="m6 6 12 12"></path>
</svg>
</button>
</div>
</div>
<div
id="navbar-collapse-with-animation"
class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block"
>
<div
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) => {
const isActive = currentPath === (item.url === '/' ? '' : item.url.slice(1));
return (
<a
href={item.url}
class={`text-sm font-medium ${
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}
</a>
);
})
}
<span class="md:inline-block">
<ThemeToggle />
</span>
</div>
</div>
</nav>
</header>
<script is:inline src="/vendor/preline/collapse2.1.0.min.js"></script>

View File

@@ -1,54 +0,0 @@
---
interface Props {
title: string;
tagline?: string;
align?: 'start' | 'center';
}
const { align = 'center', tagline, title } = Astro.props;
---
<div class:list={['hero stack gap-4', align]}>
<div class="stack gap-2">
<h1 class="title">{title}</h1>
{tagline && <p class="tagline">{tagline}</p>}
</div>
<slot />
</div>
<style>
.hero {
font-size: var(--text-lg);
text-align: center;
}
.title,
.tagline {
max-width: 37ch;
margin-inline: auto;
}
.title {
font-size: var(--text-3xl);
color: var(--gray-0);
}
@media (min-width: 50em) {
.hero {
font-size: var(--text-xl);
}
.start {
text-align: start;
}
.start .title,
.start .tagline {
margin-inline: unset;
}
.title {
font-size: var(--text-5xl);
}
}
</style>

View File

@@ -1,56 +0,0 @@
---
import type { HTMLAttributes } from 'astro/types';
import { iconPaths } from './IconPaths';
interface Props {
icon: keyof typeof iconPaths;
color?: string;
gradient?: boolean;
size?: string;
}
const { color = 'currentcolor', gradient, icon, size } = Astro.props;
const iconPath = iconPaths[icon];
const attrs: HTMLAttributes<'svg'> = {};
if (size) attrs.style = { '--size': size };
const gradientId = 'icon-gradient-' + Math.round(Math.random() * 10e12).toString(36);
---
<svg
xmlns="http://www.w3.org/2000/svg"
width="40"
height="40"
viewBox="0 0 256 256"
aria-hidden="true"
stroke={gradient ? `url(#${gradientId})` : color}
fill={gradient ? `url(#${gradientId})` : color}
{...attrs}
>
<g set:html={iconPath} />
{
gradient && (
<linearGradient
id={gradientId}
x1="23"
x2="235"
y1="43"
y2="202"
gradientUnits="userSpaceOnUse"
>
<stop stop-color="var(--gradient-stop-1)" />
<stop offset=".5" stop-color="var(--gradient-stop-2)" />
<stop offset="1" stop-color="var(--gradient-stop-3)" />
</linearGradient>
)
}
</svg>
<style>
svg {
vertical-align: middle;
width: var(--size, 1em);
height: var(--size, 1em);
}
</style>

View File

@@ -1,38 +0,0 @@
/**
* Icons adapted from https://phosphoricons.com/
*
* Want to add more?
* 1. Find the icon you want on Phosphor Icons.
* 2. Click “Copy SVG”.
* 3. Paste the SVG code in your editor.
* 4. Remove the `<svg>` wrapper so you only have elements like `<path>`, `<circle>`, `<rect>` etc.
* 5. Remove any `stroke="#000000"` attributes
* 6. Replace any `fill="#000000"` attributes with `stroke="none"`
* (or add `stroke="none"` on shapes with no `fill` or `stroke` specified).
*/
export const iconPaths = {
'terminal-window': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m80 96 40 32-40 32m56 0h40"/><rect width="192" height="160" x="32" y="48" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16.97" rx="8.5"/>`,
trophy: `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M56 56v55.1c0 39.7 31.8 72.6 71.5 72.9a72 72 0 0 0 72.5-72V56a8 8 0 0 0-8-8H64a8 8 0 0 0-8 8Zm40 168h64m-32-40v40"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M198.2 128h9.8a32 32 0 0 0 32-32V80a8 8 0 0 0-8-8h-32M58 128H47.9a32 32 0 0 1-32-32V80a8 8 0 0 1 8-8h32"/>`,
strategy: `<circle cx="68" cy="188" r="28" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m40 72 40 40m0-40-40 40m136 56 40 40m0-40-40 40M136 80V40h40"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m136 40 16 16c40 40 8 88-24 96"/>`,
'paper-plane-tilt': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M210.3 35.9 23.9 88.4a8 8 0 0 0-1.2 15l85.6 40.5a7.8 7.8 0 0 1 3.8 3.8l40.5 85.6a8 8 0 0 0 15-1.2l52.5-186.4a7.9 7.9 0 0 0-9.8-9.8Zm-99.4 109.2 45.2-45.2"/>`,
'arrow-right': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M40 128h176m-72-72 72 72-72 72"/>`,
'arrow-left': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M216 128H40m72-72-72 72 72 72"/>`,
code: `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m64 88-48 40 48 40m128-80 48 40-48 40M160 40 96 216"/>`,
'microphone-stage': `<circle cx="168" cy="88" r="64" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m213.3 133.3-90.6-90.6M100 156l-12 12m16.8-70.1L28.1 202.5a7.9 7.9 0 0 0 .8 10.4l14.2 14.2a7.9 7.9 0 0 0 10.4.8l104.6-76.7"/>`,
'pencil-line': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M96 216H48a8 8 0 0 1-8-8v-44.7a7.9 7.9 0 0 1 2.3-5.6l120-120a8 8 0 0 1 11.4 0l44.6 44.6a8 8 0 0 1 0 11.4Zm40-152 56 56"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M216 216H96l-55.5-55.5M164 92l-96 96"/>`,
'rocket-launch': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M94.1 184.6c-11.4 33.9-56.6 33.9-56.6 33.9s0-45.2 33.9-56.6m124.5-56.5L128 173.3 82.7 128l67.9-67.9C176.3 34.4 202 34.7 213 36.3a7.8 7.8 0 0 1 6.7 6.7c1.6 11 1.9 36.7-23.8 62.4Z"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M184.6 116.7v64.6a8 8 0 0 1-2.4 5.6l-32.3 32.4a8 8 0 0 1-13.5-4.1l-8.4-41.9m11.3-101.9H74.7a8 8 0 0 0-5.6 2.4l-32.4 32.3a8 8 0 0 0 4.1 13.5l41.9 8.4"/>`,
list: `<path stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M40 128h176M40 64h176M40 192h176"/>`,
heart: `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M128 216S28 160 28 92a52 52 0 0 1 100-20h0a52 52 0 0 1 100 20c0 68-100 124-100 124Z"/>`,
'moon-stars': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M216 112V64m24 24h-48m-24-64v32m16-16h-32m65 113A92 92 0 0 1 103 39h0a92 92 0 1 0 114 114Z"/>`,
sun: `<circle cx="128" cy="128" r="60" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M128 36V16M63 63 49 49m-13 79H16m47 65-14 14m79 13v20m65-47 14 14m13-79h20m-47-65 14-14"/>`,
'twitter-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M128 88c0-22 18.5-40.3 40.5-40a40 40 0 0 1 36.2 24H240l-32.3 32.3A127.9 127.9 0 0 1 80 224c-32 0-40-12-40-12s32-12 48-36c0 0-64-32-48-120 0 0 40 40 88 48Z"/>`,
'codepen-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m232 101-104 59-104-59 100.1-56.8a8.3 8.3 0 0 1 7.8 0Z"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m232 165-100.1 56.8a8.3 8.3 0 0 1-7.8 0L24 165l104-59Zm0-64v64M24 101v64m104-5v62.8m0-179.6V106"/>`,
'github-logo': `<g stroke-linecap="round" stroke-linejoin="round"><path fill="none" stroke-width="14.7" d="M55.7 167.2c13.9 1 21.3 13.1 22.2 14.6 4.2 7.2 10.4 9.6 18.3 7.1l1.1-3.4a60.3 60.3 0 0 1-25.8-11.9c-12-10.1-18-25.6-18-46.3"/><path fill="none" stroke-width="16" d="M61.4 205.1a24.5 24.5 0 0 1-3-6.1c-3.2-7.9-7.1-10.6-7.8-11.1l-1-.6c-2.4-1.6-9.5-6.5-7.2-13.9 1.4-4.5 6-7.2 12.3-7.2h.8c4 .3 7.6 1.5 10.7 3.2-9.1-10.1-13.6-24.3-13.6-42.3 0-11.3 3.5-21.7 10.1-30.4A46.7 46.7 0 0 1 65 67.3a8.3 8.3 0 0 1 5-4.7c2.8-.9 13.3-2.7 33.2 9.9a105 105 0 0 1 50.5 0c19.9-12.6 30.4-10.8 33.2-9.9 2.3.7 4.1 2.4 5 4.7 5 12.7 4 23.2 2.6 29.4 6.7 8.7 10 18.9 10 30.4 0 42.6-25.8 54.7-43.6 58.7 1.4 4.1 2.2 8.8 2.2 13.7l-.1 23.4v2.3"/><path fill="none" stroke-width="16" d="M160.9 185.7c1.4 4.1 2.2 8.8 2.2 13.7l-.1 23.4v2.3A98.6 98.6 0 1 0 61.4 205c-1.4-2.1-11.3-17.5-11.8-17.8-2.4-1.6-9.5-6.5-7.2-13.9 1.4-4.5 6-7.2 12.3-7.2h.8c4 .3 7.6 1.5 10.7 3.2-9.1-10.1-13.6-24.3-13.6-42.3 0-11.3 3.5-21.7 10.1-30.4A46.4 46.4 0 0 1 65 67.3a8.3 8.3 0 0 1 5-4.7c2.8-.9 13.3-2.7 33.2 9.9a105 105 0 0 1 50.5 0c19.9-12.6 30.4-10.8 33.2-9.9 2.3.7 4.1 2.4 5 4.7 5 12.7 4 23.2 2.6 29.4 6.7 8.7 10 18.9 10 30.4.1 42.6-25.8 54.7-43.6 58.6z"/><path fill="none" stroke-width="18.7" d="m170.1 203.3 17.3-12 17.2-18.7 9.5-26.6v-27.9l-9.5-27.5" /><path fill="none" stroke-width="22.7" d="m92.1 57.3 23.3-4.6 18.7-1.4 29.3 5.4m-110 32.6-8 16-4 21.4.6 20.3 3.4 13" /><path fill="none" stroke-width="13.3" d="M28.8 133a100 100 0 0 0 66.9 94.4v-8.7c-22.4 1.8-33-11.5-35.6-19.8-3.4-8.6-7.8-11.4-8.5-11.8"/></g>`,
'twitch-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M165 200h-42a8 8 0 0 0-5 2l-46 38v-40H48a8 8 0 0 1-8-8V48a8 8 0 0 1 8-8h160a8 8 0 0 1 8 8v108a8 8 0 0 1-3 6l-43 36a8 8 0 0 1-5 2Zm3-112v48m-48-48v48"/>`,
'youtube-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m160 128-48-32v64l48-32z"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M24 128c0 30 3 47 5 56a16 16 0 0 0 10 11c34 13 89 13 89 13s56 0 89-13a16 16 0 0 0 10-11c2-9 5-26 5-56s-3-47-5-56a16 16 0 0 0-10-11c-33-13-89-13-89-13s-55 0-89 13a16 16 0 0 0-10 11c-2 9-5 26-5 56Z"/>`,
'dribbble-logo': `<circle cx="128" cy="128" r="96" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M71 205a160 160 0 0 1 137-77l16 1m-36-76a160 160 0 0 1-124 59 165 165 0 0 1-30-3"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M86 42a161 161 0 0 1 74 177"/>`,
'discord-logo': `<circle stroke="none" cx="96" cy="144" r="12"/><circle stroke="none" cx="160" cy="144" r="12"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M74 80a175 175 0 0 1 54-8 175 175 0 0 1 54 8m0 96a175 175 0 0 1-54 8 175 175 0 0 1-54-8"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="m155 182 12 24a8 8 0 0 0 9 4c25-6 46-16 61-30a8 8 0 0 0 3-8L206 59a8 8 0 0 0-5-5 176 176 0 0 0-30-9 8 8 0 0 0-9 5l-8 24m-53 108-12 24a8 8 0 0 1-9 4c-25-6-46-16-61-30a8 8 0 0 1-3-8L50 59a8 8 0 0 1 5-5 176 176 0 0 1 30-9 8 8 0 0 1 9 5l8 24"/>`,
'linkedin-logo': `<rect width="184" height="184" x="36" y="36" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" rx="8"/><path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M120 112v64m-32-64v64m32-36a28 28 0 0 1 56 0v36"/><circle stroke="none" cx="88" cy="80" r="12"/>`,
'instagram-logo': `<circle cx="128" cy="128" r="40" fill="none" stroke-miterlimit="10" stroke-width="16"/><rect width="184" height="184" x="36" y="36" fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" rx="48"/><circle cx="180" cy="76" r="12" stroke="none" />`,
'tiktok-logo': `<path fill="none" stroke-linecap="round" stroke-linejoin="round" stroke-width="16" d="M168 106a96 96 0 0 0 56 18V84a56 56 0 0 1-56-56h-40v128a28 28 0 1 1-40-25V89a68 68 0 1 0 80 67Z"/>`,
};

View File

@@ -1,47 +0,0 @@
---
import '../styles/global.css';
interface Props {
title?: string | undefined;
description?: string | undefined;
}
const {
title = 'Jeanine White: Personal Site',
description = 'The personal site of Jeanine White',
} = Astro.props;
---
<meta charset="UTF-8" />
<meta name="description" property="og:description" content={description} />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<title>{title}</title>
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Public+Sans:ital,wght@0,400;0,700;1,400&family=Rubik:wght@500;600&display=swap"
rel="stylesheet"
/>
<script is:inline>
// This code is inlined in the head to make dark mode instant & blocking.
const getThemePreference = () => {
if (typeof localStorage !== 'undefined' && localStorage.getItem('theme')) {
return localStorage.getItem('theme');
}
return window.matchMedia('(prefers-color-scheme: dark)').matches ? 'dark' : 'light';
};
const isDark = getThemePreference() === 'dark';
document.documentElement.classList[isDark ? 'add' : 'remove']('theme-dark');
if (typeof localStorage !== 'undefined') {
// Watch the document element and persist user preference when it changes.
const observer = new MutationObserver(() => {
const isDark = document.documentElement.classList.contains('theme-dark');
localStorage.setItem('theme', isDark ? 'dark' : 'light');
});
observer.observe(document.documentElement, { attributes: true, attributeFilter: ['class'] });
}
</script>

View File

@@ -1,367 +0,0 @@
---
import Icon from './Icon.astro';
import ThemeToggle from './ThemeToggle.astro';
import type { iconPaths } from './IconPaths';
/** Main menu items */
const textLinks: { label: string; href: string }[] = [
{ label: 'Home', href: '/' },
{ label: 'Work', href: '/work/' },
{ label: 'About', href: '/about/' },
];
/** Icon links to social media — edit these with links to your profiles! */
const iconLinks: { label: string; href: string; icon: keyof typeof iconPaths }[] = [
{ label: 'Twitter', href: 'https://twitter.com/me', icon: 'twitter-logo' },
{ label: 'Twitch', href: 'https://twitch.tv/me', icon: 'twitch-logo' },
{ label: 'GitHub', href: 'https://github.com/me', icon: 'github-logo' },
{ label: 'CodePen', href: 'https://codepen.io/me', icon: 'codepen-logo' },
{ label: 'dribbble', href: 'https://dribbble.com/me', icon: 'dribbble-logo' },
{ label: 'YouTube', href: 'https://www.youtube.com/@me/', icon: 'youtube-logo' },
];
---
<nav>
<div class="menu-header">
<a href="/" class="site-title">
<Icon icon="terminal-window" color="var(--accent-regular)" size="1.6em" gradient />
Jeanine White
</a>
<menu-button>
<template>
<button class="menu-button" aria-expanded="false">
<span class="sr-only">Menu</span>
<Icon icon="list" />
</button>
</template>
</menu-button>
</div>
<noscript>
<ul class="nav-items">
{
textLinks.map(({ label, href }) => (
<li>
<a
aria-current={Astro.url.pathname === href}
class:list={[
'link',
{
active:
Astro.url.pathname === href ||
(href !== '/' && Astro.url.pathname.startsWith(href)),
},
]}
href={href}
>
{label}
</a>
</li>
))
}
</ul>
</noscript>
<noscript>
<div class="menu-footer">
<div class="socials">
{
iconLinks.map(({ href, icon, label }) => (
<a href={href} class="social">
<span class="sr-only">{label}</span>
<Icon icon={icon} />
</a>
))
}
</div>
</div>
</noscript>
<div id="menu-content" hidden>
<ul class="nav-items">
{
textLinks.map(({ label, href }) => (
<li>
<a
aria-current={Astro.url.pathname === href}
class:list={[
'link',
{
active:
Astro.url.pathname === href ||
(href !== '/' && Astro.url.pathname.startsWith(href)),
},
]}
href={href}
>
{label}
</a>
</li>
))
}
</ul>
<div class="menu-footer">
<div class="socials">
{
iconLinks.map(({ href, icon, label }) => (
<a href={href} class="social">
<span class="sr-only">{label}</span>
<Icon icon={icon} />
</a>
))
}
</div>
<div class="theme-toggle">
<ThemeToggle />
</div>
</div>
</div>
</nav>
<script>
class MenuButton extends HTMLElement {
constructor() {
super();
// Inject menu toggle button when JS runs.
this.appendChild(this.querySelector('template')!.content.cloneNode(true));
const btn = this.querySelector('button')!;
// Hide menu (shown by default to support no-JS browsers).
const menu = document.getElementById('menu-content')!;
menu.hidden = true;
// Add "menu-content" class in JS to avoid covering content in non-JS browsers.
menu.classList.add('menu-content');
/** Set whether the menu is currently expanded or collapsed. */
const setExpanded = (expand: boolean) => {
btn.setAttribute('aria-expanded', expand ? 'true' : 'false');
menu.hidden = !expand;
};
// Toggle menu visibility when the menu button is clicked.
btn.addEventListener('click', () => setExpanded(menu.hidden));
// Hide menu button for large screens.
const handleViewports = (e: MediaQueryList | MediaQueryListEvent) => {
setExpanded(e.matches);
btn.hidden = e.matches;
};
const mediaQueries = window.matchMedia('(min-width: 50em)');
handleViewports(mediaQueries);
mediaQueries.addEventListener('change', handleViewports);
}
}
customElements.define('menu-button', MenuButton);
</script>
<style>
nav {
z-index: 9999;
position: relative;
font-family: var(--font-brand);
font-weight: 500;
margin-bottom: 3.5rem;
}
.menu-header {
display: flex;
justify-content: space-between;
gap: 0.5rem;
padding: 1.5rem;
}
.site-title {
display: flex;
gap: 0.5rem;
align-items: center;
line-height: 1.1;
color: var(--gray-0);
text-decoration: none;
}
.menu-button {
position: relative;
display: flex;
border: 0;
border-radius: 999rem;
padding: 0.5rem;
font-size: 1.5rem;
color: var(--gray-300);
background: radial-gradient(var(--gray-900), var(--gray-800) 150%);
box-shadow: var(--shadow-md);
}
.menu-button[aria-expanded='true'] {
color: var(--gray-0);
background: linear-gradient(180deg, var(--gray-600), transparent),
radial-gradient(var(--gray-900), var(--gray-800) 150%);
}
.menu-button[hidden] {
display: none;
}
.menu-button::before {
position: absolute;
inset: -1px;
content: '';
background: var(--gradient-stroke);
border-radius: 999rem;
z-index: -1;
}
.menu-content {
position: absolute;
left: 0;
right: 0;
}
.nav-items {
margin: 0;
display: flex;
flex-direction: column;
gap: 1rem;
font-size: var(--text-md);
line-height: 1.2;
list-style: none;
padding: 2rem;
background-color: var(--gray-999);
border-bottom: 1px solid var(--gray-800);
}
.link {
display: inline-block;
color: var(--gray-300);
text-decoration: none;
}
.link.active {
color: var(--gray-0);
}
.menu-footer {
--icon-size: var(--text-xl);
--icon-padding: 0.5rem;
display: flex;
justify-content: space-between;
gap: 0.75rem;
padding: 1.5rem 2rem 1.5rem 1.5rem;
background-color: var(--gray-999);
border-radius: 0 0 0.75rem 0.75rem;
box-shadow: var(--shadow-lg);
}
.socials {
display: flex;
flex-wrap: wrap;
gap: 0.625rem;
font-size: var(--icon-size);
}
.social {
display: flex;
padding: var(--icon-padding);
text-decoration: none;
color: var(--accent-dark);
transition: color var(--theme-transition);
}
.social:hover,
.social:focus {
color: var(--accent-text-over);
}
.theme-toggle {
display: flex;
align-items: center;
height: calc(var(--icon-size) + 2 * var(--icon-padding));
}
@media (min-width: 50em) {
nav {
display: grid;
grid-template-columns: 1fr auto 1fr;
align-items: center;
padding: 2.5rem 5rem;
gap: 1rem;
}
.menu-header {
padding: 0;
}
.site-title {
font-size: var(--text-lg);
}
.menu-content {
display: contents;
}
.nav-items {
position: relative;
flex-direction: row;
font-size: var(--text-sm);
border-radius: 999rem;
border: 0;
padding: 0.5rem 0.5625rem;
background: radial-gradient(var(--gray-900), var(--gray-800) 150%);
box-shadow: var(--shadow-md);
}
.nav-items::before {
position: absolute;
inset: -1px;
content: '';
background: var(--gradient-stroke);
border-radius: 999rem;
z-index: -1;
}
.link {
padding: 0.5rem 1rem;
border-radius: 999rem;
transition:
color var(--theme-transition),
background-color var(--theme-transition);
}
.link:hover,
.link:focus {
color: var(--gray-100);
background-color: var(--accent-subtle-overlay);
}
.link.active {
color: var(--accent-text-over);
background-color: var(--accent-regular);
}
.menu-footer {
--icon-padding: 0.375rem;
justify-self: flex-end;
align-items: center;
padding: 0;
background-color: transparent;
box-shadow: none;
}
.socials {
display: none;
}
}
@media (min-width: 60em) {
.socials {
display: flex;
justify-content: flex-end;
gap: 0;
}
}
@media (forced-colors: active) {
.link.active {
color: SelectedItem;
}
}
</style>

View File

@@ -1,16 +0,0 @@
<div class="pill"><slot /></div>
<style>
.pill {
display: flex;
padding: 0.5rem 1rem;
gap: 0.5rem;
color: var(--accent-text-over);
border: 1px solid var(--accent-regular);
background-color: var(--accent-regular);
border-radius: 999rem;
font-size: var(--text-md);
line-height: 1.35;
white-space: nowrap;
}
</style>

View File

@@ -1,64 +0,0 @@
---
import type { CollectionEntry } from 'astro:content';
interface Props {
project: CollectionEntry<'work'>;
}
const { data, slug } = Astro.props.project;
---
<a class="card" href={`/work/${slug}`}>
<span class="title">{data.title}</span>
<img src={data.img} alt={data.img_alt || ''} loading="lazy" decoding="async" />
</a>
<style>
.card {
display: grid;
grid-template: auto 1fr / auto 1fr;
height: 11rem;
background: var(--gradient-subtle);
border: 1px solid var(--gray-800);
border-radius: 0.75rem;
overflow: hidden;
box-shadow: var(--shadow-sm);
text-decoration: none;
font-family: var(--font-brand);
font-size: var(--text-lg);
font-weight: 500;
transition: box-shadow var(--theme-transition);
}
.card:hover {
box-shadow: var(--shadow-md);
}
.title {
grid-area: 1 / 1 / 2 / 2;
z-index: 1;
margin: 0.5rem;
padding: 0.5rem 1rem;
background: var(--gray-999);
color: var(--gray-200);
border-radius: 0.375rem;
}
img {
grid-area: 1 / 1 / 3 / 3;
width: 100%;
height: 100%;
object-fit: cover;
}
@media (min-width: 50em) {
.card {
height: 22rem;
border-radius: 1.5rem;
}
.title {
border-radius: 0.9375rem;
}
}
</style>

View File

@@ -1,62 +0,0 @@
---
import Icon from './Icon.astro';
---
<section class="box skills">
<div class="stack gap-2 lg:gap-4">
<Icon icon="terminal-window" color="var(--accent-regular)" size="2.5rem" gradient />
<h2>Full Stack</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod.</p>
</div>
<div class="stack gap-2 lg:gap-4">
<Icon icon="trophy" color="var(--accent-regular)" size="2.5rem" gradient />
<h2>Industry Leader</h2>
<p>Neque viverra justo nec ultrices dui. Est ultricies integer quis auctor elit.</p>
</div>
<div class="stack gap-2 lg:gap-4">
<Icon icon="strategy" color="var(--accent-regular)" size="2.5rem" gradient />
<h2>Strategy-Minded</h2>
<p>Urna porttitor rhoncus dolor purus non enim praesent ornare.</p>
</div>
</section>
<style>
.box {
border: 1px solid var(--gray-800);
border-radius: 0.75rem;
padding: 1.5rem;
background-color: var(--gray-999_40);
box-shadow: var(--shadow-sm);
}
.skills {
display: flex;
flex-direction: column;
gap: 3rem;
}
.skills h2 {
font-size: var(--text-lg);
}
.skills p {
color: var(--gray-400);
}
@media (min-width: 50em) {
.box {
border-radius: 1.5rem;
padding: 2.5rem;
}
.skills {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 5rem;
}
.skills h2 {
font-size: var(--text-2xl);
}
}
</style>

View File

@@ -1,95 +0,0 @@
---
import Icon from './Icon.astro';
---
<theme-toggle>
<button>
<span class="sr-only">Dark theme</span>
<span class="icon light"><Icon icon="sun" /></span>
<span class="icon dark"><Icon icon="moon-stars" /></span>
</button>
</theme-toggle>
<style>
button {
display: flex;
border: 0;
border-radius: 999rem;
padding: 0;
background-color: var(--gray-999);
box-shadow: inset 0 0 0 1px var(--accent-overlay);
cursor: pointer;
}
.icon {
z-index: 1;
position: relative;
display: flex;
padding: 0.5rem;
width: 2rem;
height: 2rem;
font-size: 1rem;
color: var(--accent-overlay);
}
.icon.light::before {
content: '';
z-index: -1;
position: absolute;
inset: 0;
background-color: var(--accent-regular);
border-radius: 999rem;
}
:global(.theme-dark) .icon.light::before {
transform: translateX(100%);
}
:global(.theme-dark) .icon.dark,
:global(html:not(.theme-dark)) .icon.light,
button[aria-pressed='false'] .icon.light {
color: var(--accent-text-over);
}
@media (prefers-reduced-motion: no-preference) {
.icon,
.icon.light::before {
transition:
transform var(--theme-transition),
color var(--theme-transition);
}
}
@media (forced-colors: active) {
.icon.light::before {
background-color: SelectedItem;
}
}
</style>
<script>
class ThemeToggle extends HTMLElement {
constructor() {
super();
const button = this.querySelector('button')!;
/** Set the theme to dark/light mode. */
const setTheme = (dark: boolean) => {
document.documentElement.classList[dark ? 'add' : 'remove']('theme-dark');
button.setAttribute('aria-pressed', String(dark));
};
// Toggle the theme when a user clicks the button.
button.addEventListener('click', () => setTheme(!this.isDark()));
// Initialize button state to reflect current theme.
setTheme(this.isDark());
}
isDark() {
return document.documentElement.classList.contains('theme-dark');
}
}
customElements.define('theme-toggle', ThemeToggle);
</script>

View File

@@ -0,0 +1,61 @@
---
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>

View File

@@ -0,0 +1,82 @@
---
interface Props {
slug: string;
title: string;
description: string;
count: number;
publishDate: string;
layoutPattern?: {
smCol: number;
mdCol: number;
row: number;
index: number;
};
}
const { slug, layoutPattern, title, description, count, publishDate } = Astro.props;
const isSingleItem =
layoutPattern &&
layoutPattern.row === 1 &&
(layoutPattern.smCol === 1 || layoutPattern.mdCol === 1);
const formatedDescription = isSingleItem ? `No description available` : description;
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 justify-end p-3 md:p-4 lg:p-5">
<h2
class="group-hover:text-steel dark:group-hover:text-bermuda transition-text mb-4 text-4xl font-extrabold tracking-tight text-balance text-neutral-800 duration-300 dark:text-neutral-200"
>
{title}
</h2>
<p
class=`mb-4 ${isSingleItem ? 'hidden lg:block' : ''} max-w-prose text-pretty font-light text-neutral-600 dark:text-neutral-400 sm:text-lg`
>
{formatedDescription}
</p>
<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>

View File

@@ -0,0 +1,29 @@
---
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>

View File

@@ -0,0 +1,44 @@
---
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>

View File

@@ -0,0 +1,45 @@
---
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>

View File

@@ -0,0 +1,87 @@
---
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>

View File

@@ -0,0 +1,85 @@
---
import Icon from '@components/ui/icons/icon.astro';
---
<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"
data-bookmark-button="bookmark-button"
>
<Icon name="bookmark" />
</button>
<script>
class Bookmark {
private static readonly BOOKMARKS_KEY = 'bookmarks';
private bookmarkButton: Element | null;
constructor(private dataAttrValue: string) {
this.bookmarkButton = document.querySelector(`[data-bookmark-button="${dataAttrValue}"]`);
}
private getStoredBookmarks(): string[] {
const item = localStorage.getItem(Bookmark.BOOKMARKS_KEY);
return item ? JSON.parse(item) : [];
}
init(): void {
if (this.bookmarkButton && this.isStored()) {
this.markAsStored();
}
this.bookmarkButton?.addEventListener('click', () => this.toggleBookmark());
}
isStored(): boolean {
return this.getStoredBookmarks().includes(window.location.pathname);
}
markAsStored(): void {
if (this.bookmarkButton) {
this.bookmarkButton.classList.add('bookmarked');
const svgElement = this.bookmarkButton.querySelector('svg');
if (svgElement) {
svgElement.setAttribute('class', 'h-6 w-6 fill-red-500 dark:fill-red-500');
}
const pathElement = svgElement?.querySelector('path');
if (pathElement) {
pathElement.setAttribute('class', 'fill-current text-red-500 dark:text-red-500');
}
}
}
unmarkAsStored(): void {
if (this.bookmarkButton) {
this.bookmarkButton.classList.remove('bookmarked');
const svgElement = this.bookmarkButton.querySelector('svg');
if (svgElement) {
svgElement.setAttribute('class', 'h-6 w-6 fill-none');
}
const pathElement = svgElement?.querySelector('path');
if (pathElement) {
pathElement.setAttribute(
'class',
'fill-current text-neutral-500 group-hover:text-red-400 dark:text-neutral-500 group-hover:dark:text-red-400'
);
}
}
}
toggleBookmark(): void {
const storedBookmarks = this.getStoredBookmarks();
const index = storedBookmarks.indexOf(window.location.pathname);
if (index !== -1) {
storedBookmarks.splice(index, 1);
this.unmarkAsStored();
} else {
storedBookmarks.push(window.location.pathname);
this.markAsStored();
}
localStorage.setItem(Bookmark.BOOKMARKS_KEY, JSON.stringify(storedBookmarks));
}
}
new Bookmark('bookmark-button').init();
</script>

View File

@@ -0,0 +1,32 @@
---
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>

View File

@@ -0,0 +1,35 @@
---
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>

View File

@@ -0,0 +1,45 @@
---
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>

View File

@@ -0,0 +1,26 @@
---
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>

View File

@@ -0,0 +1,150 @@
---
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>

View File

@@ -0,0 +1,279 @@
---
---
<button
id="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"
aria-label="Toggle dark mode"
>
<div class="relative z-10 flex h-5 w-5 items-center justify-center">
<!-- Sun icon -->
<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"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="12" cy="12" r="5"></circle>
<path
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"
></path>
</svg>
<!-- Moon icon -->
<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"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path>
</svg>
</div>
</button>
<script is:inline>
// Use a function to persist theme when using SPA transitions
// https://docs.astro.build/en/guides/view-transitions/#script-re-execution
function applyTheme() {
localStorage.theme === 'dark'
? document.documentElement.classList.add('dark')
: document.documentElement.classList.remove('dark');
}
document.addEventListener('astro:after-swap', applyTheme);
applyTheme();
</script>
<script>
// Use a function to handle theme toggle to ensure it can be called from anywhere
function setupThemeToggle() {
const themeToggles = document.querySelectorAll('[data-theme-toggle]');
// Create theme switch overlay element if it doesn't exist
if (!document.querySelector('.theme-switch-overlay')) {
const overlay = document.createElement('div');
overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50';
overlay.style.opacity = '0';
overlay.style.transition = 'opacity 0.3s ease-out';
document.body.appendChild(overlay);
}
// Toggle theme when any theme toggle button is clicked
themeToggles.forEach((toggle) => {
// Add event listeners for both click and touch events
['click', 'touchend'].forEach((eventType) => {
toggle.addEventListener(
eventType,
(e) => {
e.preventDefault();
e.stopPropagation();
// Get click/touch position for radial animation
let x, y;
if (e.type === 'touchend' && e.changedTouches && e.changedTouches[0]) {
const rect = toggle.getBoundingClientRect();
x = e.changedTouches[0].clientX - rect.left;
y = e.changedTouches[0].clientY - rect.top;
} else {
const rect = toggle.getBoundingClientRect();
x = e.clientX - rect.left;
y = e.clientY - rect.top;
}
// Set the position variables for the radial gradient
document.documentElement.style.setProperty('--x', `${x}px`);
document.documentElement.style.setProperty('--y', `${y}px`);
// Get the overlay element
const overlay = document.querySelector('.theme-switch-overlay');
// Determine the new theme
const isDark = document.documentElement.classList.contains('dark');
const newTheme = isDark ? 'light' : 'dark';
// Show overlay during transition
if (overlay) {
overlay.style.backgroundColor =
newTheme === 'dark' ? 'rgba(24, 24, 27, 0.3)' : 'rgba(255, 255, 255, 0.3)';
overlay.style.opacity = '1';
}
// Add transition class
document.documentElement.classList.add('theme-switching');
// Force a reflow to ensure all elements update
document.body.offsetHeight;
// Toggle dark mode with a slight delay to allow overlay to appear
setTimeout(() => {
if (isDark) {
document.documentElement.classList.remove('dark');
} else {
document.documentElement.classList.add('dark');
}
// Store the preference
localStorage.setItem('theme', newTheme);
// Dispatch a custom event for other components to react to
document.dispatchEvent(
new CustomEvent('themeChanged', {
detail: { isDark: newTheme === 'dark' },
})
);
// Force another reflow to ensure all elements update
document.body.offsetHeight;
// Hide overlay after theme has changed
setTimeout(() => {
if (overlay) {
overlay.style.opacity = '0';
}
// Remove transition class after animation completes
document.documentElement.classList.remove('theme-switching');
}, 300);
}, 50);
},
{ 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 }
);
});
}
// Run setup on load
document.addEventListener('astro:page-load', setupThemeToggle);
// Also run on page visibility change to ensure theme is consistent
document.addEventListener('visibilitychange', () => {
if (document.visibilityState === 'visible') {
const currentTheme = localStorage.getItem('theme');
if (currentTheme === 'dark') {
document.documentElement.classList.add('dark');
} else if (currentTheme === 'light') {
document.documentElement.classList.remove('dark');
}
}
});
// Listen for system preference changes
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', ({ matches }) => {
if (!localStorage.getItem('theme')) {
if (matches) {
document.documentElement.classList.add('dark');
} else {
document.documentElement.classList.remove('dark');
}
}
});
</script>
<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 */
#theme-toggle {
transform: translateY(0);
box-shadow: 0 0 0 rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent; /* Remove default mobile tap highlight */
min-height: 32px; /* Ensure minimum touch target size */
min-width: 32px; /* Ensure minimum touch target size */
}
/* Only apply hover effects on non-touch devices */
@media (hover: hover) {
#theme-toggle:hover {
transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
}
#theme-toggle:hover .icon-light:not(.dark .icon-light) {
filter: drop-shadow-sm(0 0 2px rgba(251, 191, 36, 0.6));
transform: scale(1.1) rotate(15deg);
}
#theme-toggle:hover .icon-dark:not(:not(.dark) .icon-dark) {
filter: drop-shadow-sm(0 0 2px rgba(129, 140, 248, 0.6));
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 */
@media (prefers-reduced-motion: reduce) {
.icon-light,
.icon-dark {
transition: all 0.2s ease-out !important;
}
#theme-toggle,
#theme-toggle:hover {
transform: none;
transition: none;
}
}
/* Adjust size for very small screens */
@media (max-width: 320px) {
#theme-toggle {
padding: 0.25rem !important;
}
}
</style>

View File

@@ -0,0 +1,45 @@
---
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 ${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>

View File

@@ -0,0 +1,39 @@
---
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'
)
}

View File

@@ -0,0 +1,573 @@
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',
},
};

View File

@@ -0,0 +1,17 @@
---
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} />

View File

@@ -0,0 +1,11 @@
---
import { readSingleton } from '@directus/sdk';
import Image from '@components/ui/images/Image.astro';
import logo from '@images/brand_logo.png';
import directus from '@lib/directus';
const global = await directus.request(readSingleton('site_global'));
---
<Image src={logo} alt={global.name} {...Astro.props} draggable="false" loading="eager" />

View File

@@ -0,0 +1,129 @@
---
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>

View File

@@ -0,0 +1,152 @@
---
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>

View File

@@ -0,0 +1,37 @@
---
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>

View File

@@ -0,0 +1,35 @@
---
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>

View File

@@ -0,0 +1,63 @@
---
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>

View File

@@ -0,0 +1,164 @@
---
import GiteaBtn from '@components/ui/buttons/GiteaBtn.astro';
const { title, subTitle, url } = Astro.props;
const btnTitle = 'Continue to Gitea';
interface Props {
title: string;
subTitle?: string;
url?: string;
}
---
<section class="lg:px- relative mx-auto mb-20 max-w-[85rem] px-4 pt-30 pb-30 sm:px-6">
<div
class="smooth-reveal absolute top-[55%] left-0 scale-90 md:top-[20%] xl:top-[25%] xl:left-[10%]"
>
<svg
class="animate-hover animate-hover-1"
width="64"
height="64"
fill="none"
stroke-width="1.5"
color="#ea580c"
viewBox="0 0 24 24"
>
<path
fill="#ea580c"
stroke="#ea580c"
stroke-linecap="round"
stroke-linejoin="round"
d="M12 23a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM3 8a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM3 18a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"
></path>
<path
stroke="#ea580c"
stroke-linecap="round"
stroke-linejoin="round"
d="M21 7.353v9.294a.6.6 0 0 1-.309.525l-8.4 4.666a.6.6 0 0 1-.582 0l-8.4-4.666A.6.6 0 0 1 3 16.647V7.353a.6.6 0 0 1 .309-.524l8.4-4.667a.6.6 0 0 1 .582 0l8.4 4.667a.6.6 0 0 1 .309.524Z"
></path>
<path
stroke="#ea580c"
stroke-linecap="round"
stroke-linejoin="round"
d="m3.528 7.294 8.18 4.544a.6.6 0 0 0 .583 0l8.209-4.56M12 21v-9"></path>
</svg>
</div>
<div class="smooth-reveal absolute top-0 left-[85%] scale-75">
<svg
class="animate-hover animate-hover-2"
width="64"
height="64"
fill="none"
stroke-width="1.5"
color="#fbbf24"
viewBox="0 0 24 24"
>
<path
stroke="#fbbf24"
stroke-linecap="round"
stroke-linejoin="round"
d="M12 22c5.523 0 10-4.477 10-10S17.523 2 12 2 2 6.477 2 12s4.477 10 10 10Z"></path>
<path
fill="#fbbf24"
stroke="#fbbf24"
stroke-linecap="round"
stroke-linejoin="round"
d="M5 6a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"></path>
<path stroke="#fbbf24" stroke-linecap="round" stroke-linejoin="round" d="M5 10.5V9M5 15v-1.5"
></path>
<path
fill="#fbbf24"
stroke="#fbbf24"
stroke-linecap="round"
stroke-linejoin="round"
d="M5 20a1 1 0 1 0 0-2 1 1 0 0 0 0 2ZM19 20a1 1 0 1 0 0-2 1 1 0 0 0 0 2Z"></path>
<path
stroke="#fbbf24"
stroke-linecap="round"
stroke-linejoin="round"
d="M10.5 19H9M15 19h-1.5"></path>
</svg>
</div>
<div
class="smooth-reveal absolute bottom-[5%] left-[60%] scale-[.6] xl:bottom-[15%] xl:left-[35%]"
>
<svg
class="animate-hover animate-hover-3"
width="64"
height="64"
fill="none"
stroke-width="1.5"
color="#a3a3a3"
viewBox="0 0 24 24"
>
<path
stroke="#a3a3a3"
stroke-linecap="round"
stroke-linejoin="round"
d="M5.164 17c.29-1.049.67-2.052 1.132-3M11.5 7.794A16.838 16.838 0 0 1 14 6.296M4.5 22a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5Z"
></path>
<path
stroke="#a3a3a3"
stroke-linecap="round"
stroke-linejoin="round"
d="M9.5 12a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5ZM19.5 7a2.5 2.5 0 1 1 0-5 2.5 2.5 0 0 1 0 5Z"
></path>
</svg>
</div>
<!-- Hero Section Heading -->
<div class="smooth-reveal-2 mx-auto mt-5 max-w-xl text-center">
<h2
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}
</h2>
</div>
<!-- Hero Section Sub-heading -->
<div class="smooth-reveal-2 mx-auto mt-5 max-w-3xl text-center">
{
subTitle && (
<p class="text-lg text-pretty text-neutral-600 dark:text-neutral-400">{subTitle}</p>
)
}
</div>
<!-- Github Button -->
{
url && (
<div class="smooth-reveal-2 mt-8 flex justify-center gap-3">
<GiteaBtn url={url} title={btnTitle} />
</div>
)
}
</section>
<style>
@keyframes animate-hover {
from {
transform: translateY(15px);
}
to {
transform: translateY(-15px);
}
}
.animate-hover {
animation: animate-hover ease-in-out;
animation-iteration-count: infinite;
animation-direction: alternate;
}
.animate-hover-1 {
animation-duration: 5s;
}
.animate-hover-2 {
animation-duration: 5.5s;
}
.animate-hover-3 {
animation-duration: 6s;
}
</style>

View File

@@ -0,0 +1,34 @@
---
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', {
fields: ['*'],
sort: ['-published_date'],
})
);
const recentPosts = posts
.sort((a: Post, b: Post) => b.published_date.getTime() - 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>

View File

@@ -0,0 +1,75 @@
---
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>

View File

@@ -0,0 +1,250 @@
---
import { Icon } from 'astro-icon/components';
import { readItems } from '@directus/sdk';
import type { Skill } from '@lib/directusTypes';
import directus from '@lib/directus';
const skills = await directus.request(
readItems('site_skills', {
fields: ['*'],
sort: ['-date_created'],
})
);
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]}>
<h3
class="relative flex w-full items-center gap-3 pb-4 text-5xl text-neutral-800 dark:text-neutral-200"
>
Skills
</h3>
<div class="">
<div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8">
<!-- Main slider container -->
<div class="slider-track animate-slide flex">
{
[...skills, ...skills, ...skills].map((skill: Skill) => {
return (
<div
class={`skill-card transform rounded-xl transition-all duration-300 ${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${shadowClasses}`}
>
<div class="p-4 sm:p-6">
<div class="mb-4 flex items-center justify-between sm:mb-6">
<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">
<Icon name={skill.icon} class="h-8 w-8 sm:h-12 sm:w-12" />
</div>
<h3 class="text-base font-semibold text-neutral-900 sm:text-xl dark:text-neutral-100">
{skill.title}
</h3>
</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">
{skill.level}%
</span>
</div>
<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
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"
style={`width: ${skill.level}%`}
/>
</div>
<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>Advanced</span>
</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>
</section>
<script>
document.addEventListener('astro:page-load', () => {
// Create seamless infinite scrolling effect
function setupInfiniteScroll() {
const cards = document.querySelectorAll('.skill-card');
if (!cards.length) return;
}
setupInfiniteScroll();
// Add hover effects to cards - only on non-touch devices
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
const cards = document.querySelectorAll('.skill-card');
if (!isTouchDevice) {
cards.forEach((card) => {
card.addEventListener('mousemove', (e) => {
const rect = card.getBoundingClientRect();
const x = e.clientX - rect.left;
const y = e.clientY - rect.top;
const centerX = rect.width / 2;
const centerY = rect.height / 2;
const angleX = (y - centerY) / 15;
const angleY = (centerX - x) / 15;
card.style.transform = `perspective(1000px) rotateX(${angleX}deg) rotateY(${angleY}deg) scale(1.08) translateZ(20px)`;
// Dynamic shadow based on tilt
const shadowX = (x - centerX) / 25;
const shadowY = (y - centerY) / 25;
card.style.boxShadow = `
${shadowX}px ${shadowY}px 20px rgba(0, 0, 0, 0.1),
0 10px 20px rgba(0, 0, 0, 0.05)
`;
});
card.addEventListener('mouseleave', () => {
card.style.transform = '';
card.style.boxShadow = '';
});
});
} else {
// Simpler effects for touch devices
cards.forEach((card) => {
card.addEventListener('touchstart', () => {
card.classList.add('is-touched');
});
card.addEventListener('touchend', () => {
setTimeout(() => {
card.classList.remove('is-touched');
}, 300);
});
});
}
});
</script>
<style>
/* Tech Stack Slider */
.slider-track {
width: fit-content;
animation: scroll 40s linear infinite;
}
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-220px * 6 - 16px * 6)); /* Card width + margin for mobile */
}
}
@media (min-width: 640px) {
.slider-track {
animation: scroll 80s linear infinite;
}
@keyframes scroll {
0% {
transform: translateX(0);
}
100% {
transform: translateX(calc(-280px * 6 - 32px * 6)); /* Card width + margin for desktop */
}
}
}
.tech-stack-slider:hover .slider-track {
animation-play-state: paused;
}
.skill-card {
transition: all 0.5s cubic-bezier(0.175, 0.885, 0.32, 1.275);
position: relative;
overflow: hidden;
}
.skill-card:hover {
z-index: 10;
}
/* Reduce animation complexity on mobile */
@media (max-width: 640px) {
.skill-card {
transition:
transform 0.3s ease,
box-shadow 0.3s ease;
}
.skill-card:hover {
transform: translateY(-5px) !important;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1) !important;
}
}
.skill-card:before {
content: '';
position: absolute;
top: -10%;
left: -10%;
width: 120%;
height: 120%;
background: radial-gradient(
circle at center,
rgba(255, 255, 255, 0.1) 0%,
rgba(255, 255, 255, 0) 70%
);
opacity: 0;
transition: opacity 0.5s ease;
pointer-events: none;
}
.skill-card:hover:before {
opacity: 1;
}
.progress-bar-animate {
position: relative;
overflow: hidden;
}
.progress-bar-animate:after {
content: '';
position: absolute;
top: 0;
left: -100%;
width: 100%;
height: 100%;
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);
animation: progress-shine 2s infinite;
}
@keyframes progress-shine {
0% {
left: -100%;
}
100% {
left: 100%;
}
}
</style>

66
src/config.ts Normal file
View File

@@ -0,0 +1,66 @@
import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus';
export interface NavigationLink {
name: string;
url: string;
}
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[] = [
{ name: 'Home', url: '/' },
{ name: 'Blog', url: '/blog/' },
{ name: 'Categories', url: '/categories/' },
{ name: 'About Me', url: '/about/' },
];
export const FooterLinks: NavigationLink[] = [
{ name: 'RSS', url: '/rss.xml' },
{ name: 'Gitea', url: '/https://gitea.alexlebens.dev' },
];
export const SEO = {
title: global.name,
description: global.about,
structuredData: {
'@context': 'https://schema.org',
'@type': 'WebPage',
inLanguage: 'en-US',
'@id': global.site_url,
url: global.site_url,
name: global.name,
description: global.about,
isPartOf: {
'@type': 'WebSite',
url: global.site_url,
name: global.name,
description: global.about,
},
},
};

View File

@@ -0,0 +1,4 @@
---
title: 'Books 📖'
description: 'Books I have read or listened to'
---

View File

@@ -0,0 +1,4 @@
---
title: 'Cloud ☁️'
description: "Its just someone else's server"
---

View File

@@ -0,0 +1,4 @@
---
title: 'Homelab 🏠'
description: 'What happens when rack servers find a home'
---

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