Compare commits

..

10 Commits

Author SHA1 Message Date
ebf70bd747 chore(deps): update dependency @swup/astro to v1.8.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 38s
test-build / build (pull_request) Successful in 1m37s
2026-02-16 05:57:02 +00:00
9c5e9b6a5b Merge pull request 'chore(deps): update dependency @eslint-react/eslint-plugin to v2.13.0' (#338) from renovate/eslint-react-eslint-plugin-2.x-lockfile into main
All checks were successful
test-build / guarddog (push) Successful in 51s
test-build / build (push) Successful in 1m42s
renovate / renovate (push) Successful in 2m17s
Reviewed-on: #338
2026-02-16 05:55:20 +00:00
568f9e5164 chore(deps): update dependency @eslint-react/eslint-plugin to v2.13.0
All checks were successful
renovate/stability-days Updates have met minimum release age requirement
test-build / guarddog (pull_request) Successful in 43s
test-build / build (pull_request) Successful in 1m50s
2026-02-16 05:40:13 +00:00
a74cc775d0 feat: final refactor of sections
All checks were successful
test-build / guarddog (push) Successful in 35s
test-build / build (push) Successful in 1m1s
renovate / renovate (push) Successful in 2m15s
2026-02-15 23:38:55 -06:00
5271be52a2 feat: rename button components to include button in name for consistency 2026-02-15 22:05:36 -06:00
8a649b7647 feat: imporvement pass over sections 2026-02-15 15:42:27 -06:00
c4be4653be fix: run theme on page swap 2026-02-15 00:05:16 -06:00
47a637353c feat: move improved components out of ui folder 2026-02-14 23:10:43 -06:00
a09a4ee240 feat: imporve theme toggle button 2026-02-14 23:08:12 -06:00
342ae8900a feat: refactor buttons, except for theme 2026-02-14 22:09:49 -06:00
45 changed files with 785 additions and 918 deletions

182
pnpm-lock.yaml generated
View File

@@ -46,7 +46,7 @@ importers:
version: 0.2.1(@types/node@25.2.3)(jiti@2.6.1)(rollup@2.79.2)(typescript@5.9.3)(yaml@2.8.2) version: 0.2.1(@types/node@25.2.3)(jiti@2.6.1)(rollup@2.79.2)(typescript@5.9.3)(yaml@2.8.2)
'@swup/astro': '@swup/astro':
specifier: ^1.7.0 specifier: ^1.7.0
version: 1.7.0(@types/babel__core@7.20.5) version: 1.8.0(@types/babel__core@7.20.5)
'@tailwindcss/postcss': '@tailwindcss/postcss':
specifier: ^4.1.18 specifier: ^4.1.18
version: 4.1.18 version: 4.1.18
@@ -110,7 +110,7 @@ importers:
devDependencies: devDependencies:
'@eslint-react/eslint-plugin': '@eslint-react/eslint-plugin':
specifier: ^2.12.4 specifier: ^2.12.4
version: 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) version: 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@tailwindcss/forms': '@tailwindcss/forms':
specifier: ^0.5.11 specifier: ^0.5.11
version: 0.5.11(tailwindcss@4.1.18) version: 0.5.11(tailwindcss@4.1.18)
@@ -1168,40 +1168,40 @@ packages:
resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==}
engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0}
'@eslint-react/ast@2.12.4': '@eslint-react/ast@2.13.0':
resolution: {integrity: sha512-UNr5OsaNXfDBcIWZtahlNBjqEQeysAU4NzPqsxnogghUd7pX0aQxaQHXIOdSKTd5037PbyHIILbiimDmI/BYAQ==} resolution: {integrity: sha512-43+5gmqV3MpatTzKnu/V2i/jXjmepvwhrb9MaGQvnXHQgq9J7/C7VVCCcwp6Rvp2QHAFquAAdvQDSL8IueTpeA==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@eslint-react/core@2.12.4': '@eslint-react/core@2.13.0':
resolution: {integrity: sha512-YAZrvc62wcMFT06twU4orNeGI8HCv9/B3X19hzU9/vhN5hCdr4HM3tl9E7qd79anjFBO30zflhpghB1zeAfjyQ==} resolution: {integrity: sha512-m62XDzkf1hpzW4sBc7uh7CT+8rBG2xz/itSADuEntlsg4YA7Jhb8hjU6VHf3wRFDwyfx5VnbV209sbJ7Azey0Q==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@eslint-react/eff@2.12.4': '@eslint-react/eff@2.13.0':
resolution: {integrity: sha512-lpC7s9bvHvVMcYZ01Py/nUTWjhq2/VRI+ftfFfIeqPTYXuXt95V3MIS+jtTK74f3BVXqxpAFbSx81hpd7ISMeg==} resolution: {integrity: sha512-rEH2R8FQnUAblUW+v3ZHDU1wEhatbL1+U2B1WVuBXwSKqzF7BGaLqCPIU7o9vofumz5MerVfaCtJgI8jYe2Btg==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
'@eslint-react/eslint-plugin@2.12.4': '@eslint-react/eslint-plugin@2.13.0':
resolution: {integrity: sha512-ML7EyET19pPgGOQmm5uEN9yoVkHqTtPyLMPWL2wlap9LVuFxoMK2owESkrq7mhWrXPf/NLuO4Z7lTcu5Sqv1EQ==} resolution: {integrity: sha512-iaMXpqnJCTW7317hg8L4wx7u5aIiPzZ+d1p59X8wXFgMHzFX4hNu4IfV8oygyjmWKdLsjKE9sEpv/UYWczlb+A==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@eslint-react/shared@2.12.4': '@eslint-react/shared@2.13.0':
resolution: {integrity: sha512-a6qV2cQzxH3EFPlc2MU1/13D6J+LwlzUVTQ7vGAEKBHNEIxIwXBSdah8MGU8TQp/3+12oVMIm6FK71yZOjwqkA==} resolution: {integrity: sha512-IOloCqrZ7gGBT4lFf9+0/wn7TfzU7JBRjYwTSyb9SDngsbeRrtW95ZpgUpS8/jen1wUEm6F08duAooTZ2FtsWA==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
'@eslint-react/var@2.12.4': '@eslint-react/var@2.13.0':
resolution: {integrity: sha512-NITTpD0fNPVmFpiTwP4PuyLblhuKvMWDU36bIoQL+Xh7xxcCc+lbiUCCOQiOzFgz+l36JhnOAjoRJ4rnP3yDUA==} resolution: {integrity: sha512-dM+QaeiHR16qPQoJYg205MkdHYSWVa2B7ore5OFpOPlSwqDV3tLW7I+475WjbK7potq5QNPTxRa7VLp9FGeQqA==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -1747,8 +1747,8 @@ packages:
peerDependencies: peerDependencies:
swup: ^4.0.0 swup: ^4.0.0
'@swup/astro@1.7.0': '@swup/astro@1.8.0':
resolution: {integrity: sha512-OxZskzeYkEZ4+McbwJKyD018sHNlrKQLdtHzlmNjivrzQY/dB7ncnWqDs3d4IkA7Y+ydNjpwBqpR/asjDhTiag==} resolution: {integrity: sha512-FI8/jNXgyUoWpnOgdB3MOUobRtRzedKBE8+qbBH5c8CE4q9Ni/GHPGrXK/0xG+g1ZiIQuTF3OXRvTLNVouiBig==}
'@swup/body-class-plugin@3.3.0': '@swup/body-class-plugin@3.3.0':
resolution: {integrity: sha512-4h/6mAgDd0+ml8Gc2kX8tikIR0HZZLF+WTnfm0JTVrGDw74bgEBenbsaWFfIS+gn4RWKZdWBUOfaZ+Im5J7Gvw==} resolution: {integrity: sha512-4h/6mAgDd0+ml8Gc2kX8tikIR0HZZLF+WTnfm0JTVrGDw74bgEBenbsaWFfIS+gn4RWKZdWBUOfaZ+Im5J7Gvw==}
@@ -2395,6 +2395,9 @@ packages:
caniuse-lite@1.0.30001769: caniuse-lite@1.0.30001769:
resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==} resolution: {integrity: sha512-BCfFL1sHijQlBGWBMuJyhZUhzo7wer5sVj9hqekB/7xn0Ypy+pER/edCYQm4exbXj4WiySGp40P8UuTh6w1srg==}
caniuse-lite@1.0.30001770:
resolution: {integrity: sha512-x/2CLQ1jHENRbHg5PSId2sXq1CIO1CISvwWAj027ltMVG2UNgW+w9oH2+HzgEIRFembL8bUlXtfbBHR1fCg2xw==}
ccount@2.0.1: ccount@2.0.1:
resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==}
@@ -2906,15 +2909,15 @@ packages:
peerDependencies: peerDependencies:
eslint: ^8.40.0 || ^9.0.0 eslint: ^8.40.0 || ^9.0.0
eslint-plugin-react-dom@2.12.4: eslint-plugin-react-dom@2.13.0:
resolution: {integrity: sha512-KxjyVB+zb8DR+knd8pTCLRFZh3Tdvkv96LYzruTbM4rKAOpHwsmJ+5/tXo5P8kgl7bMBYsHR0akbhUm+tb/MBw==} resolution: {integrity: sha512-+2IZzQ1WEFYOWatW+xvNUqmZn55YBCufzKA7hX3XQ/8eu85Mp4vnlOyNvdVHEOGhUnGuC6+9+zLK+IlEHKdKLQ==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
eslint-plugin-react-hooks-extra@2.12.4: eslint-plugin-react-hooks-extra@2.13.0:
resolution: {integrity: sha512-7KiMJYYYG4K6KHd+6VtlmA/1BdUSzqcMIR3ebL47+6znYgwNatTouKOSX6V+HeQPW9IIsFfmmTvwlzWh+hsQJA==} resolution: {integrity: sha512-qIbha1nzuyhXM9SbEfrcGVqmyvQu7GAOB2sy9Y4Qo5S8nCqw4fSBxq+8lSce5Tk5Y7XzIkgHOhNyXEvUHRWFMQ==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -2926,8 +2929,8 @@ packages:
peerDependencies: peerDependencies:
eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0 eslint: ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0-0 || ^9.0.0
eslint-plugin-react-naming-convention@2.12.4: eslint-plugin-react-naming-convention@2.13.0:
resolution: {integrity: sha512-VCPmN15AFFZ2fuy8OA40zumN5p12BVlEJvA/YLb6b0uiLHdCQsV6kuZwCNRRUWE6DJmJBe9k5fZW/Ww71ceQyg==} resolution: {integrity: sha512-uSd25JzSg2R4p81s3Wqck0AdwRlO9Yc+cZqTEXv7vW8exGGAM3mWnF6hgrgdqVJqBEGJIbS/Vx1r5BdKcY/MHA==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -2938,22 +2941,22 @@ packages:
peerDependencies: peerDependencies:
eslint: '>=9' eslint: '>=9'
eslint-plugin-react-rsc@2.12.4: eslint-plugin-react-rsc@2.13.0:
resolution: {integrity: sha512-Bp2Yy3Hk3OUcUtGPRt5z/UtODFUP2ExM1j47J4KJJghrTay/UH01vFC+T0tXQ4GXGxpXt/aJJX42DJpbz5sY6Q==} resolution: {integrity: sha512-RaftgITDLQm1zIgYyvR51sBdy4FlVaXFts5VISBaKbSUB0oqXyzOPxMHasfr9BCSjPLKus9zYe+G/Hr6rjFLXQ==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
eslint-plugin-react-web-api@2.12.4: eslint-plugin-react-web-api@2.13.0:
resolution: {integrity: sha512-upmeq9d5D4wDgm+exkBAOAtWfdi5vRZGxYQhtmhXabmV2nEq/UFKT8vMkcnWNBJCpTM+bcb0YtJK6WVlM2WcdA==} resolution: {integrity: sha512-nmJbzIAte7PeAkp22CwcKEASkKi49MshSdiDGO1XuN3f4N4/8sBfDcWbQuLPde6JiuzDT/0+l7Gi8wwTHtR1kg==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.0.0' typescript: '>=4.8.4 <6.0.0'
eslint-plugin-react-x@2.12.4: eslint-plugin-react-x@2.13.0:
resolution: {integrity: sha512-DzVSPAOCHMawhkn30GUYCL2y89NgZk2umFTJ9RFpfoN1AejdT1T7wXysJiqzfezSpbxW0uzKSoWbjnbsTE0YPQ==} resolution: {integrity: sha512-cMNX0+ws/fWTgVxn52qAQbaFF2rqvaDAtjrPUzY6XOzPjY0rJQdR2tSlWJttz43r2yBfqu+LGvHlGpWL2wfpTQ==}
engines: {node: '>=20.19.0'} engines: {node: '>=20.19.0'}
peerDependencies: peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
@@ -5140,6 +5143,7 @@ packages:
tar@7.5.7: tar@7.5.7:
resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==} resolution: {integrity: sha512-fov56fJiRuThVFXD6o6/Q354S7pnWMJIVlDBYijsTNx6jKSE4pvrDTs6lUnmGvNyfJwFQQwWy3owKz1ucIhveQ==}
engines: {node: '>=18'} engines: {node: '>=18'}
deprecated: Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
terser@5.44.1: terser@5.44.1:
resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==} resolution: {integrity: sha512-t/R3R/n0MSwnnazuPpPNVO60LX0SKL45pyl9YlvxIdkH0Of7D5qM2EVe+yASRIlY5pZ73nclYJfNANGWPwFDZw==}
@@ -6794,9 +6798,9 @@ snapshots:
'@eslint-community/regexpp@4.12.2': {} '@eslint-community/regexpp@4.12.2': {}
'@eslint-react/ast@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': '@eslint-react/ast@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
'@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.55.0(typescript@5.9.3)
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
@@ -6806,12 +6810,12 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@eslint-react/core@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': '@eslint-react/core@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/var': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
@@ -6821,31 +6825,31 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@eslint-react/eff@2.12.4': {} '@eslint-react/eff@2.13.0': {}
'@eslint-react/eslint-plugin@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': '@eslint-react/eslint-plugin@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint: 10.0.0(jiti@2.6.1) eslint: 10.0.0(jiti@2.6.1)
eslint-plugin-react-dom: 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-dom: 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint-plugin-react-hooks-extra: 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-hooks-extra: 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint-plugin-react-naming-convention: 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-naming-convention: 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint-plugin-react-rsc: 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-rsc: 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint-plugin-react-web-api: 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-web-api: 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint-plugin-react-x: 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react-x: 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
ts-api-utils: 2.4.0(typescript@5.9.3) ts-api-utils: 2.4.0(typescript@5.9.3)
typescript: 5.9.3 typescript: 5.9.3
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@eslint-react/shared@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': '@eslint-react/shared@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint: 10.0.0(jiti@2.6.1) eslint: 10.0.0(jiti@2.6.1)
ts-pattern: 5.9.0 ts-pattern: 5.9.0
@@ -6854,11 +6858,11 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
'@eslint-react/var@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)': '@eslint-react/var@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)':
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
@@ -7377,7 +7381,7 @@ snapshots:
focus-options-polyfill: 1.6.0 focus-options-polyfill: 1.6.0
swup: 4.8.2 swup: 4.8.2
'@swup/astro@1.7.0(@types/babel__core@7.20.5)': '@swup/astro@1.8.0(@types/babel__core@7.20.5)':
dependencies: dependencies:
'@swup/a11y-plugin': 5.0.0(swup@4.8.2) '@swup/a11y-plugin': 5.0.0(swup@4.8.2)
'@swup/body-class-plugin': 3.3.0(swup@4.8.2) '@swup/body-class-plugin': 3.3.0(swup@4.8.2)
@@ -8217,7 +8221,7 @@ snapshots:
autoprefixer@10.4.24(postcss@8.5.6): autoprefixer@10.4.24(postcss@8.5.6):
dependencies: dependencies:
browserslist: 4.28.1 browserslist: 4.28.1
caniuse-lite: 1.0.30001769 caniuse-lite: 1.0.30001770
fraction.js: 5.3.4 fraction.js: 5.3.4
picocolors: 1.1.1 picocolors: 1.1.1
postcss: 8.5.6 postcss: 8.5.6
@@ -8351,12 +8355,14 @@ snapshots:
caniuse-api@3.0.0: caniuse-api@3.0.0:
dependencies: dependencies:
browserslist: 4.28.1 browserslist: 4.28.1
caniuse-lite: 1.0.30001769 caniuse-lite: 1.0.30001770
lodash.memoize: 4.1.2 lodash.memoize: 4.1.2
lodash.uniq: 4.5.0 lodash.uniq: 4.5.0
caniuse-lite@1.0.30001769: {} caniuse-lite@1.0.30001769: {}
caniuse-lite@1.0.30001770: {}
ccount@2.0.1: {} ccount@2.0.1: {}
chalk@1.1.3: chalk@1.1.3:
@@ -9009,13 +9015,13 @@ snapshots:
prettier: 3.8.1 prettier: 3.8.1
synckit: 0.11.12 synckit: 0.11.12
eslint-plugin-react-dom@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): eslint-plugin-react-dom@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/core': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/core': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/var': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
@@ -9026,13 +9032,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-react-hooks-extra@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): eslint-plugin-react-hooks-extra@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/core': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/core': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/var': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
@@ -9054,13 +9060,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-react-naming-convention@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): eslint-plugin-react-naming-convention@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/core': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/core': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/var': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
@@ -9077,11 +9083,11 @@ snapshots:
dependencies: dependencies:
eslint: 10.0.0(jiti@2.6.1) eslint: 10.0.0(jiti@2.6.1)
eslint-plugin-react-rsc@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): eslint-plugin-react-rsc@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/var': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
eslint: 10.0.0(jiti@2.6.1) eslint: 10.0.0(jiti@2.6.1)
@@ -9090,13 +9096,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-react-web-api@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): eslint-plugin-react-web-api@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/core': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/core': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/var': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0
'@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
@@ -9107,13 +9113,13 @@ snapshots:
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
eslint-plugin-react-x@2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3): eslint-plugin-react-x@2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3):
dependencies: dependencies:
'@eslint-react/ast': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/ast': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/core': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/core': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/eff': 2.12.4 '@eslint-react/eff': 2.13.0
'@eslint-react/shared': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/shared': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@eslint-react/var': 2.12.4(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@eslint-react/var': 2.13.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.55.0 '@typescript-eslint/scope-manager': 8.55.0
'@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/type-utils': 8.55.0(eslint@10.0.0(jiti@2.6.1))(typescript@5.9.3)
'@typescript-eslint/types': 8.55.0 '@typescript-eslint/types': 8.55.0

View File

@@ -1,6 +1,6 @@
--- ---
import BrandLogo from '@components/ui/logos/BrandLogo.astro'; import BrandLogo from '@components/ui/logos/BrandLogo.astro';
import ThemeToggle from '@components/ui/buttons/ThemeToggle.astro'; import ThemeToggleButton from '@components/buttons/ThemeToggleButton.astro';
import { NavigationLinks } from '@/config'; import { NavigationLinks } from '@/config';
const pathname = new URL(Astro.request.url).pathname; const pathname = new URL(Astro.request.url).pathname;
@@ -67,7 +67,7 @@ const currentPath = pathname.slice(1);
<div <div
id="navbar-collapse-with-animation" id="navbar-collapse-with-animation"
class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block" class="hs-collapse hidden grow basis-full overflow-hidden transition-all duration-300 md:block md:overflow-visible"
> >
<div <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" 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"
@@ -90,7 +90,7 @@ const currentPath = pathname.slice(1);
}) })
} }
<span class="md:inline-block"> <span class="md:inline-block">
<ThemeToggle /> <ThemeToggleButton />
</span> </span>
</div> </div>
</div> </div>

View File

@@ -1,5 +1,5 @@
--- ---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import Image from '@components/ui/images/Image.astro'; import Image from '@components/ui/images/Image.astro';
interface Props { interface Props {
@@ -39,6 +39,6 @@ const { title, subTitle, btnExists, btnTitle, btnURL, img, imgAlt } = Astro.prop
> >
{subTitle} {subTitle}
</p> </p>
{btnExists ? <PrimaryCTA title={btnTitle} url={btnURL} /> : null} {btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
</div> </div>
</section> </section>

View File

@@ -1,5 +1,5 @@
--- ---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import Image from '@components/ui/images/Image.astro'; import Image from '@components/ui/images/Image.astro';
interface Props { interface Props {
@@ -43,7 +43,7 @@ const {
> >
{subTitle} {subTitle}
</p> </p>
{btnExists ? <PrimaryCTA title={btnTitle} url={btnURL} /> : null} {btnExists ? <GoLinkPrimaryButton title={btnTitle} url={btnURL} /> : null}
</div> </div>
{ {

View File

@@ -4,7 +4,7 @@ import Icon from '@components/ui/icons/icon.astro';
<button <button
type="button" type="button"
class="focus-visible:ring-secondary group inline-flex items-center rounded-lg p-2.5 text-neutral-600 ring-neutral-500 transition duration-300 outline-none hover:bg-neutral-100 focus:outline-none focus-visible:ring-1 focus-visible:outline-none dark:text-neutral-400 dark:ring-neutral-200 dark:hover:bg-neutral-700" class="button-base button-bg-blue group inline-flex items-center rounded-lg p-2.5"
data-bookmark-button="bookmark-button" data-bookmark-button="bookmark-button"
> >
<Icon name="bookmark" /> <Icon name="bookmark" />

View File

@@ -0,0 +1,31 @@
---
import { Icon } from 'astro-icon/components';
interface Props {
title?: string;
url?: string;
}
const { title, url } = Astro.props;
---
<a
class="button-base button-bg-gitea group inline-flex rounded-full gap-x-2"
href={url}
target="_blank"
rel="noopener noreferrer"
>
<div class="button-text-title flex relative items-center text-center">
<Icon
name="pajamas:gitea"
class="h-4 w-4 md:h-6 md:w-6"
/>
<span class="ml-2">
Continue to Gitea
</span>
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
</div>
</a>

View File

@@ -0,0 +1,28 @@
---
import Icon from '@components/ui/icons/icon.astro';
interface Props {
noArrow?: boolean;
}
const { noArrow } = Astro.props;
---
<button
class="button-base button-bg-blue group inline-flex rounded-lg gap-x-2"
id="back-button"
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
{noArrow ? null : <Icon name="arrowLeft" />}
<span class="ml-2">
Go Back
</span>
</div>
</button>
<script>
document.getElementById('back-button')?.addEventListener('click', () => {
window.history.back();
});
</script>

View File

@@ -0,0 +1,25 @@
---
import { Icon } from 'astro-icon/components';
interface Props {
url?: string;
}
const { url } = Astro.props;
---
<a
class="button-base button-bg-teal group inline-flex rounded-lg gap-x-2"
href={url}
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
<Icon
name="mdi:home-variant-outline"
class="card-hover-icon-scale h-3 w-3 md:h-5 md:w-5"
/>
<span class="ml-2">
Return Home
</span>
</div>
</a>

View File

@@ -0,0 +1,29 @@
---
import { Icon } from 'astro-icon/components';
interface Props {
title?: string;
url?: string;
noArrow?: boolean;
}
const { title, url, noArrow } = Astro.props;
---
<a
class="button-base button-bg-teal group inline-flex rounded-lg gap-x-2"
href={url}
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
<span class="mr-2">
{title}
</span>
{noArrow ? null : (
<Icon
name="mdi:keyboard-arrow-right"
class="button-hover-arrow"
/>
)}
</div>
</a>

View File

@@ -0,0 +1,20 @@
---
interface Props {
title?: string;
url?: string;
}
const { title, url } = Astro.props;
---
<a
class="button-base button-bg-neutral group inline-flex rounded-lg gap-x-2"
href={url}
data-astro-prefetch
>
<div class="button-text-title flex relative items-center text-center">
<span>
{title}
</span>
</div>
</a>

View File

@@ -0,0 +1,51 @@
---
import Icon from '@components/ui/icons/icon.astro';
type SocialPlatform = {
name: string;
url: string;
svg: string;
};
interface Props {
pageTitle: string;
}
const { pageTitle } = Astro.props;
const socialPlatforms: SocialPlatform[] = [
{
name: 'Facebook',
url: `https://www.facebook.com/sharer/sharer.php?u=${Astro.url}`,
svg: 'facebook',
},
{
name: 'X',
url: `https://x.com/intent/tweet?url=${Astro.url}&text=${pageTitle}`,
svg: 'x',
},
{
name: 'LinkedIn',
url: `https://www.linkedin.com/sharing/share-offsite/?url=${Astro.url}`,
svg: 'linkedIn',
},
];
---
<div class="inline-flex items-center gap-x-2">
{
socialPlatforms.map((platform) => (
<a
class="button-base-hidden group inline-flex rounded-lg gap-x-2"
href={platform.url}
target="_blank"
rel="noopener noreferrer"
title={`Share on ${platform.name}`}
>
<div class="button-text-title-hidden flex relative items-center text-center">
<Icon name={platform.svg} class="h-5 w-5" />
</div>
</a>
))
}
</div>

View File

@@ -5,14 +5,14 @@
<button <button
id="theme-toggle" id="theme-toggle"
data-theme-toggle data-theme-toggle
class="group dark:hover:bg-steel/30 relative touch-manipulation overflow-hidden rounded-full p-1.5 transition-all duration-300 hover:bg-yellow-300/20 focus:outline-hidden sm:p-2" class="group dark:hover:bg-steel/30 hover:bg-yellow-300/20 transition-all duration-300 relative rounded-full p-1.5 sm:p-2 touch-manipulation"
aria-label="Toggle dark mode" aria-label="Toggle dark mode"
> >
<div class="relative z-10 flex h-5 w-5 items-center justify-center"> <div class="relative flex h-5 w-5 items-center justify-center">
<!-- Sun icon --> <!-- Sun icon -->
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon-light absolute h-5 w-5 scale-100 rotate-0 text-neutral-600 transition-all duration-500 dark:scale-0 dark:-rotate-90 dark:text-neutral-400" class="icon-light absolute h-5 w-5 text-neutral-600 dark:text-neutral-400 scale-100 dark:scale-0 rotate-0 dark:-rotate-90 transition-all duration-500"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -29,7 +29,7 @@
<!-- Moon icon --> <!-- Moon icon -->
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="icon-dark absolute h-5 w-5 scale-0 rotate-90 text-neutral-600 transition-all duration-500 dark:scale-100 dark:rotate-0 dark:text-neutral-400" class="icon-dark absolute h-5 w-5 text-neutral-600 dark:text-neutral-400 scale-0 dark:scale-100 rotate-90 dark:rotate-0 transition-all duration-500"
viewBox="0 0 24 24" viewBox="0 0 24 24"
fill="none" fill="none"
stroke="currentColor" stroke="currentColor"
@@ -43,25 +43,23 @@
</button> </button>
<script is:inline> <script is:inline>
// Use a function to persist theme when using SPA transitions const applyTheme = () => {
// https://docs.astro.build/en/guides/view-transitions/#script-re-execution const isDark =
function applyTheme() { localStorage.theme === 'dark' ||
localStorage.theme === 'dark' (!('theme' in localStorage) && window.matchMedia('(prefers-color-scheme: dark)').matches);
? document.documentElement.classList.add('dark') document.documentElement.classList.toggle('dark', isDark);
: document.documentElement.classList.remove('dark'); };
}
document.addEventListener('astro:after-swap', applyTheme);
applyTheme(); applyTheme();
document.addEventListener('astro:after-swap', applyTheme);
</script> </script>
<script> <script>
// Use a function to handle theme toggle to ensure it can be called from anywhere
function setupThemeToggle() { function setupThemeToggle() {
const themeToggles = document.querySelectorAll('[data-theme-toggle]'); const themeToggles = document.querySelectorAll('[data-theme-toggle]');
// Create theme switch overlay element if it doesn't exist // Create theme switch overlay element
if (!document.querySelector('.theme-switch-overlay')) { if (!document.querySelector('.theme-switch-overlay')) {
const overlay = document.createElement('div'); const overlay = document.createElement('div');
overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50'; overlay.className = 'theme-switch-overlay fixed inset-0 pointer-events-none z-50';
@@ -70,9 +68,7 @@
document.body.appendChild(overlay); document.body.appendChild(overlay);
} }
// Toggle theme when any theme toggle button is clicked
themeToggles.forEach((toggle) => { themeToggles.forEach((toggle) => {
// Add event listeners for both click and touch events
['click', 'touchend'].forEach((eventType) => { ['click', 'touchend'].forEach((eventType) => {
toggle.addEventListener( toggle.addEventListener(
eventType, eventType,
@@ -92,14 +88,10 @@
y = e.clientY - rect.top; y = e.clientY - rect.top;
} }
// Set the position variables for the radial gradient
document.documentElement.style.setProperty('--x', `${x}px`); document.documentElement.style.setProperty('--x', `${x}px`);
document.documentElement.style.setProperty('--y', `${y}px`); document.documentElement.style.setProperty('--y', `${y}px`);
// Get the overlay element
const overlay = document.querySelector('.theme-switch-overlay'); const overlay = document.querySelector('.theme-switch-overlay');
// Determine the new theme
const isDark = document.documentElement.classList.contains('dark'); const isDark = document.documentElement.classList.contains('dark');
const newTheme = isDark ? 'light' : 'dark'; const newTheme = isDark ? 'light' : 'dark';
@@ -110,7 +102,6 @@
overlay.style.opacity = '1'; overlay.style.opacity = '1';
} }
// Add transition class
document.documentElement.classList.add('theme-switching'); document.documentElement.classList.add('theme-switching');
// Force a reflow to ensure all elements update // Force a reflow to ensure all elements update
@@ -124,10 +115,7 @@
document.documentElement.classList.add('dark'); document.documentElement.classList.add('dark');
} }
// Store the preference
localStorage.setItem('theme', newTheme); localStorage.setItem('theme', newTheme);
// Dispatch a custom event for other components to react to
document.dispatchEvent( document.dispatchEvent(
new CustomEvent('themeChanged', { new CustomEvent('themeChanged', {
detail: { isDark: newTheme === 'dark' }, detail: { isDark: newTheme === 'dark' },
@@ -137,13 +125,10 @@
// Force another reflow to ensure all elements update // Force another reflow to ensure all elements update
document.body.offsetHeight; document.body.offsetHeight;
// Hide overlay after theme has changed
setTimeout(() => { setTimeout(() => {
if (overlay) { if (overlay) {
overlay.style.opacity = '0'; overlay.style.opacity = '0';
} }
// Remove transition class after animation completes
document.documentElement.classList.remove('theme-switching'); document.documentElement.classList.remove('theme-switching');
}, 300); }, 300);
}, 50); }, 50);
@@ -151,25 +136,6 @@
{ passive: false } { passive: false }
); );
}); });
// Add touch feedback
toggle.addEventListener(
'touchstart',
() => {
toggle.classList.add('active-touch');
},
{ passive: true }
);
toggle.addEventListener(
'touchend',
() => {
setTimeout(() => {
toggle.classList.remove('active-touch');
}, 150);
},
{ passive: true }
);
}); });
} }
@@ -201,61 +167,32 @@
</script> </script>
<style> <style>
/* Smooth transition for the entire page when theme changes */
:global(body) {
transition:
background-color 0.5s ease,
color 0.5s ease;
}
/* Theme transition overlay */
:global(.theme-switch-overlay) {
position: fixed;
inset: 0;
z-index: 9999;
pointer-events: none;
transition: opacity 0.3s ease-out;
}
/* Ensure theme transitions apply to all elements */
:global(.theme-switching *) {
transition-duration: 0.5s !important;
transition-property: background-color, border-color, color, fill, stroke !important;
}
/* Subtle hover animation */ /* Subtle hover animation */
#theme-toggle { #theme-toggle {
transform: translateY(0); transform: translateY(0);
box-shadow: 0 0 0 rgba(0, 0, 0, 0); box-shadow: 0 0 0 rgba(0, 0, 0, 0);
-webkit-tap-highlight-color: transparent; /* Remove default mobile tap highlight */ -webkit-tap-highlight-color: transparent;
min-height: 32px; /* Ensure minimum touch target size */ min-height: 32px;
min-width: 32px; /* Ensure minimum touch target size */ min-width: 32px;
} }
/* Only apply hover effects on non-touch devices */
@media (hover: hover) { @media (hover: hover) {
#theme-toggle:hover { #theme-toggle:hover {
transform: translateY(-2px); transform: translateY(-2px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
} }
#theme-toggle:hover .icon-light:not(.dark .icon-light) { :global(:root:not(.dark)) #theme-toggle:hover .icon-light {
filter: drop-shadow-sm(0 0 2px rgba(251, 191, 36, 0.6)); filter: drop-shadow(0 0 2px rgba(251, 191, 36, 0.6));
transform: scale(1.1) rotate(15deg); transform: scale(1.1) rotate(15deg);
} }
#theme-toggle:hover .icon-dark:not(:not(.dark) .icon-dark) { :global(:root.dark) #theme-toggle:hover .icon-dark {
filter: drop-shadow-sm(0 0 2px rgba(129, 140, 248, 0.6)); filter: drop-shadow(0 0 2px rgba(129, 140, 248, 0.6));
transform: scale(1.1) rotate(-15deg); transform: scale(1.1) rotate(-15deg);
} }
} }
/* Touch feedback */
#theme-toggle.active-touch {
transform: scale(0.95);
transition: transform 0.15s ease-in-out;
}
/* Optimize animations for mobile */ /* Optimize animations for mobile */
@media (prefers-reduced-motion: reduce) { @media (prefers-reduced-motion: reduce) {
.icon-light, .icon-light,

View File

@@ -55,7 +55,7 @@ const visitClass = visitSource ? 'card-hover-text-gitea' : 'card-hover-text-titl
)} )}
<div class="ml-6 flex"> <div class="ml-6 flex">
<div class="relative inline-block"> <div class="relative inline-block">
<div class={`card-text-title ${visitClass} flex relative mx-auto min-h-11 items-center font-semibold text-md sm:mx-0 sm:mt-4`}> <div class={`card-text-title ${visitClass} flex relative items-center mx-auto min-h-11 font-semibold text-md sm:mx-0 sm:mt-4`}>
{visitSource && <Icon name="pajamas:gitea" />} {visitSource && <Icon name="pajamas:gitea" />}
<span class="relative inline-block overflow-hidden ml-2"> <span class="relative inline-block overflow-hidden ml-2">
{visitText} {visitText}

View File

@@ -4,7 +4,7 @@ import { readItems } from '@directus/sdk';
import type { Application } from '@lib/directusTypes'; import type { Application } from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
import HighlightsCard from '@components/ui/cards/HighlightsCard.astro'; import HighlightsCard from '@components/cards/HighlightsCard.astro';
const applications = ((await directus.request( const applications = ((await directus.request(
readItems('site_applications' as any, { readItems('site_applications' as any, {

View File

@@ -5,7 +5,7 @@ import type { Education, Certificate} from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
import { getDirectusImageURL } from '@lib/directusFunctions'; import { getDirectusImageURL } from '@lib/directusFunctions';
import EducationCard from '@components/ui/cards/EducationCard.astro'; import EducationCard from '@components/cards/EducationCard.astro';
const educations = ((await directus.request( const educations = ((await directus.request(
readItems('site_education' as any, { readItems('site_education' as any, {
@@ -27,7 +27,7 @@ const certificates = ((await directus.request(
Education Education
</h3> </h3>
<div class="mx-8"> <div class="mx-8">
<h4 class="smooth-reveal card-text-header-minor pt-5 "> <h4 class="smooth-reveal card-text-header-minor pt-5">
College College
</h4> </h4>
<div class="grid md:grid-cols-2 sm:grid-cols-1 gap-4 py-3"> <div class="grid md:grid-cols-2 sm:grid-cols-1 gap-4 py-3">

View File

@@ -0,0 +1,159 @@
---
import { Icon } from 'astro-icon/components';
import { readItems } from '@directus/sdk';
import type { Experience } from '@lib/directusTypes';
import directus from '@lib/directus';
const experiences = ((await directus.request(
readItems('site_experience'as any, {
fields: ['*'],
sort: ['-endDate'],
})
)) as unknown) as Experience[];
---
<section class:list={['flex flex-col gap-8', Astro.props.className]}>
<h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-10">
Experience
</h3>
<ul class="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="smooth-reveal group relative grid sm:grid-cols-18 sm:gap-8 md:gap-6 pb-16">
<header class="relative sm:col-span-3 text-header font-semibold text-lg mt-1">
<time datetime={experience.startDate} data-title={experience.startDate}>
{startYear}
</time>
{' '}-{' '}
<time datetime={experience.endDate} data-title={experience.endDate}>
{endYear}
</time>
</header>
<div class="relative flex flex-col sm:col-span-12 pb-6">
<div class="absolute bg-stone-400 -translate-x-[1.71rem] rounded-full h-2 w-2 mt-4"/>
<h3>
<div
class="inline-flex items-center text-2xl leading-tight font-semibold"
aria-label="{position} - {company}"
>
<span class="text-header">
{experience.position} <span>@</span>
{experience.url ? (
<a
class="hover:text-main"
href={experience.url}
title={`Ver ${experience.name}`}
target="_blank"
>
{experience.name}
</a>
) : (
<span>{experience.name}</span>
)}
</span>
</div>
</h3>
{(experience.location || experience.location_type) && (
<div class="text-secondary text-sm">
{experience.location} {experience.location && experience.location_type && '-'} {experience.location_type}
</div>
)}
<div class="text-md mt-4 flex flex-col gap-4" x-data="{ expanded: false }">
{experience.summary && (
<div class="flex flex-col gap-1">
<h4 class="text-header font-semibold">
Summary:
</h4>
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
<li class="marker:text-main">
{experience.summary}
</li>
</ul>
</div>
)}
{(experience.responsibilities || experience.achievements) && (
<div class="relative flex flex-col gap-4 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="text-header font-semibold">
Responsibilities:
</h4>
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
{experience.responsibilities.map(responsibility => (
<li class="marker:text-main">
{responsibility}
</li>
))}
</ul>
</div>
)}
{experience.achievements && (
<div class="flex flex-col gap-1">
<h4 class="text-header font-semibold">
Achievements:
</h4>
<ul class="flex flex-col text-primary list-disc gap-2 [&>li]:ml-4">
{experience.achievements.map(achievement => (
<li class="marker:text-main">
{achievement}
</li>
))}
</ul>
</div>
)}
</div>
<button @click="expanded = ! expanded" class="group/more flex items-center justify-center text-primary hover:text-primary-hover text-xs underline transition-all gap-1.5 w-fit cursor-pointer">
<span x-text="expanded ? 'Show less' : 'Show more'">
Show more
</span>
<svg
class="group-hover/more:translate-y-0.5 ease-out duration-300 h-4 w-4"
:class="{ 'rotate-180': expanded }"
viewBox="0 0 24 24"
xmlns="http://www.w3.org/2000/svg"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="6 9 12 15 18 9" />
</svg>
</button>
<ul
class="flex print:hidden flex-wrap gap-2"
aria-label="Technologies used"
>
{experience.skills && experience.skills.map(skill => {
const iconName = skill.toLowerCase();
const skillName = skill.split(':')[1].replace(/^language-/, '').replace(/-/g, ' ').replace(/\b\w/g, (l) => l.toUpperCase());
return (
<li class="flex items-center bg-steel/20 dark:bg-bermuda/20 text-neutral-800 dark:text-neutral-200 text-xs rounded-md border border-solid border-steel/20 dark:border-bermuda/20 gap-1 px-2 py-0.5">
<Icon name={`${iconName}`} class="h-4 w-4" /> <span>{skillName}</span>
</li>
)
})}
</ul>
)}
</div>
</div>
</div>
</li>
);
})}
</ul>
</section>
<!-- Alpine Plugins -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/collapse@3.x.x/dist/cdn.min.js"></script>
<!-- Alpine Core -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>

View File

@@ -2,15 +2,13 @@
import { readSingleton } from '@directus/sdk'; import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus'; import directus from '@lib/directus';
import FeaturesCard from '@components/ui/cards/FeaturesCard.astro'; import FeaturesCard from '@components/cards/FeaturesCard.astro';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
--- ---
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"> <section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full">
<div <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">
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="max-w-5xl sm:px-6 lg:px-8"> <div class="max-w-5xl sm:px-6 lg:px-8">
<div class="flex flex-wrap gap-6 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 justify-center"> <div class="flex flex-wrap gap-6 sm:grid-cols-2 sm:gap-6 lg:grid-cols-3 justify-center">
<FeaturesCard <FeaturesCard

View File

@@ -1,22 +1,20 @@
--- ---
import GiteaBtn from '@components/ui/buttons/GiteaBtn.astro'; import GiteaButton from '@components/buttons/GiteaButton.astro';
const { title, subTitle, url } = Astro.props;
const btnTitle = 'Continue to Gitea';
interface Props { interface Props {
title: string; title: string;
subTitle?: string; subTitle?: string;
url?: string; url?: string;
} }
const { title, subTitle, url } = Astro.props;
--- ---
<section class="lg:px- relative mx-auto mb-20 max-w-340 px-4 pt-30 pb-30 sm:px-6"> <section class="lg:px- relative mx-auto mb-20 max-w-340 px-4 pt-30 pb-30 sm:px-6">
<div <!-- Animated shapes -->
class="smooth-reveal absolute top-[55%] left-0 scale-90 md:top-[20%] xl:top-[25%] xl:left-[10%]" <div class="smooth-reveal absolute top-[55%] left-0 scale-90 md:top-[20%] xl:top-[25%] xl:left-[10%]">
>
<svg <svg
class="animate-hover animate-hover-1" class="gitea-animate-hover gitea-animate-hover-1"
width="64" width="64"
height="64" height="64"
fill="none" fill="none"
@@ -46,7 +44,7 @@ interface Props {
</div> </div>
<div class="smooth-reveal absolute top-0 left-[85%] scale-75"> <div class="smooth-reveal absolute top-0 left-[85%] scale-75">
<svg <svg
class="animate-hover animate-hover-2" class="gitea-animate-hover gitea-animate-hover-2"
width="64" width="64"
height="64" height="64"
fill="none" fill="none"
@@ -80,11 +78,9 @@ interface Props {
d="M10.5 19H9M15 19h-1.5"></path> d="M10.5 19H9M15 19h-1.5"></path>
</svg> </svg>
</div> </div>
<div <div class="smooth-reveal absolute bottom-[5%] left-[60%] scale-[.6] xl:bottom-[15%] xl:left-[35%]">
class="smooth-reveal absolute bottom-[5%] left-[60%] scale-[.6] xl:bottom-[15%] xl:left-[35%]"
>
<svg <svg
class="animate-hover animate-hover-3" class="gitea-animate-hover gitea-animate-hover-3"
width="64" width="64"
height="64" height="64"
fill="none" fill="none"
@@ -106,59 +102,54 @@ interface Props {
></path> ></path>
</svg> </svg>
</div> </div>
<!-- Hero Section Heading --> <!-- Heading -->
<div class="smooth-reveal-2 mx-auto mt-5 max-w-xl text-center"> <div class="smooth-reveal-2 mx-auto mt-5 max-w-xl text-center">
<h2 <h1 class="card-text-header block">
class="block text-4xl leading-tight font-bold tracking-tight text-balance text-neutral-800 md:text-5xl lg:text-5xl dark:text-neutral-200"
>
{title} {title}
</h2> </h1>
</div> </div>
<!-- Hero Section Sub-heading --> <!-- Sub-heading -->
<div class="smooth-reveal-2 mx-auto mt-5 max-w-3xl text-center"> <div class="smooth-reveal-2 mx-auto mt-5 max-w-3xl text-center">
{ {subTitle && (
subTitle && ( <p class="card-text-header-description">
<p class="text-lg text-pretty text-neutral-600 dark:text-neutral-400">{subTitle}</p> {subTitle}
) </p>
} )}
</div> </div>
<!-- Github Button --> <!-- Gitea Button -->
{ {url && (
url && ( <div class="smooth-reveal-2 flex justify-center mt-8 gap-3">
<div class="smooth-reveal-2 mt-8 flex justify-center gap-3"> <GiteaButton url={url}/>
<GiteaBtn url={url} title={btnTitle} /> </div>
</div> )}
)
}
</section> </section>
<style> <style>
@keyframes animate-hover { @keyframes gitea-animate-hover {
from { from {
transform: translateY(15px); transform: translateY(15px);
} }
to { to {
transform: translateY(-15px); transform: translateY(-15px);
} }
} }
.animate-hover { .gitea-animate-hover {
animation: animate-hover ease-in-out; animation: gitea-animate-hover ease-in-out;
animation-iteration-count: infinite; animation-iteration-count: infinite;
animation-direction: alternate; animation-direction: alternate;
} }
.animate-hover-1 { .gitea-animate-hover-1 {
animation-duration: 5s; animation-duration: 5s;
} }
.animate-hover-2 { .gitea-animate-hover-2 {
animation-duration: 5.5s; animation-duration: 5.5s;
} }
.animate-hover-3 { .gitea-animate-hover-3 {
animation-duration: 6s; animation-duration: 6s;
} }
</style> </style>

View File

@@ -0,0 +1,31 @@
---
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.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 card-text-header block lg:text-6xl">
{title}
</h1>
<p class="smooth-reveal card-text-header-description mt-4">
{subTitle}
</p>
{btnExists ? (
<div class="smooth-reveal mt-4 md:mt-8">
<GoLinkPrimaryButton title={btnTitle} url={btnURL}/>
</div>
) : null}
</div>
</div>
</section>

View File

@@ -0,0 +1,56 @@
---
import GoLinkPrimaryButton from '@components/buttons/GoLinkPrimaryButton.astro';
import GoLinkSecondaryButton from '@components/buttons/GoLinkSecondaryButton.astro';
import Image from '@components/ui/images/Image.astro';
interface Props {
title: string;
subTitle?: string;
primaryBtn?: string;
primaryBtnURL?: string;
secondaryBtn?: string;
secondaryBtnURL?: string;
src?: any;
alt?: string;
rounded?: boolean;
}
const { title, subTitle, primaryBtn, primaryBtnURL, secondaryBtn, secondaryBtnURL, src, alt } = Astro.props;
const roundedClasses = Astro.props.rounded ? "rounded-xl" : null;
---
<section class="mx-auto grid max-w-340 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 card-text-header block lg:text-7xl">
<Fragment set:html={title} />
</h1>
{subTitle && (
<p class="smooth-reveal card-text-header-description lg:w-4/5 mt-6">
{subTitle}
</p>
)}
<div class="smooth-reveal grid sm:inline-flex mt-7 w-full gap-3">
{primaryBtn && <GoLinkPrimaryButton title={primaryBtn} url={primaryBtnURL} />}
{secondaryBtn && <GoLinkSecondaryButton title={secondaryBtn} url={secondaryBtnURL} />}
</div>
</div>
<div class="smooth-reveal-fade md:block w-full hidden">
<div class="flex justify-center w-full top-12 md:ml-4 overflow-hidden">
{src && alt && (
<Image
src={src}
alt={alt}
class={`h-full w-105 scale-100 object-cover object-center ${roundedClasses}`}
draggable="false"
loading="eager"
format="webp"
quality="low"
widths={[840]}
disableBlur={true}
/>
)}
</div>
</div>
</section>

View File

@@ -1,8 +1,9 @@
--- ---
import { readItems } from '@directus/sdk'; import { readItems } from '@directus/sdk';
import directus from '@lib/directus';
import type { Post } from '@lib/directusTypes'; import type { Post } from '@lib/directusTypes';
import directus from '@lib/directus';
import BlogCard from '@components/blog/BlogCard.astro'; import BlogCard from '@components/blog/BlogCard.astro';
const posts = await directus.request( const posts = await directus.request(
@@ -20,11 +21,14 @@ const recentPosts = posts
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"> <section class="mx-auto mb-20 max-w-340 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"> <div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h1 <h1 class="smooth-reveal card-text-header block">
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 Latest Posts
</h1> </h1>
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
<span class="card-text-header-description">
Checkout my most recent thoughts here
</span>
</div>
</div> </div>
<div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3"> <div class="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
{recentPosts.map((b) => <BlogCard post={b} />)} {recentPosts.map((b) => <BlogCard post={b} />)}

View File

@@ -4,7 +4,7 @@ import { readItems } from '@directus/sdk';
import type { Project } from '@lib/directusTypes'; import type { Project } from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
import HighlightsCard from '@components/ui/cards/HighlightsCard.astro'; import HighlightsCard from '@components/cards/HighlightsCard.astro';
const projects = ((await directus.request( const projects = ((await directus.request(
readItems('site_projects' as any, { readItems('site_projects' as any, {

View File

@@ -6,86 +6,63 @@ import type { Skill } from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
const skills = await directus.request( const skills = ((await directus.request(
readItems('site_skills', { readItems('site_skills' as any, {
fields: ['*'], fields: ['*'],
sort: ['-date_created'], sort: ['-date_created'],
}) })
); )) as unknown) as Skill[];
const baseClasses = 'mx-2 min-w-[220px] sm:mx-4 sm:min-w-[280px]';
const borderClasses =
'border border-neutral-100 hover:border-neutral-200 dark:border-stone-500/20 dark:hover:border-neutral-800';
const bgColorClasses = 'bg-neutral-100/80 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90';
const hoverClasses = 'hover:-translate-y-2 hover:scale-105 ';
const shadowClasses = 'shadow-xs hover:shadow-lg';
--- ---
<section class:list={['flex flex-col gap-4', Astro.props.className]}> <section class:list={['flex flex-col gap-4', Astro.props.className]}>
<h3 <h3 class="smooth-reveal card-text-header flex relative items-center w-full gap-3 pb-5">
class="relative flex w-full items-center gap-3 pb-4 text-5xl text-neutral-800 dark:text-neutral-200"
>
Skills Skills
</h3> </h3>
<div class=""> <div class="">
<div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8"> <div class="tech-stack-slider relative overflow-hidden py-4 sm:py-8">
<!-- Main slider container --> <!-- Main slider container -->
<div class="slider-track animate-slide flex"> <div class="slider-track animate-slide flex">
{ {[...skills, ...skills, ...skills].map((skill: Skill) => {
[...skills, ...skills, ...skills].map((skill: Skill) => { return (
return ( <div class="skill-card card-base transform hover:-translate-y-2 hover:scale-105 transition-all duration-300 mx-2 min-w-55 sm:mx-4 sm:min-w-70">
<div <div class="p-4 sm:p-6">
class={`skill-card transform rounded-xl transition-all duration-300 ${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${shadowClasses}`} <div class="flex items-center justify-between mb-4 sm:mb-6">
> <div class="flex items-center gap-2 sm:gap-4">
<div class="p-4 sm:p-6"> <div class="flex items-center justify-center rounded-lg text-primary">
<div class="mb-4 flex items-center justify-between sm:mb-6"> <Icon name={skill.icon} class="h-8 w-8 sm:h-12 sm:w-12" />
<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-linear-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>
<h3 class="text-neutral-900 dark:text-neutral-100 text-base font-semibold sm:text-xl">
{skill.title}
</h3>
</div> </div>
); <span class=" bg-neutral-200 dark:bg-neutral-800 text-neutral-700 dark:text-neutral-300 font-mono text-xs sm:text-sm rounded-full px-2 sm:px-2.5 py-0.5 sm:py-1">
}) {skill.level}%
} </span>
</div>
<div class="relative bg-stone-500/20 dark:bg-stone-500/20 rounded-full h-1.5 sm:h-2 w-full overflow-hidden">
<div
class="progress-bar-animate bg-linear-to-r from-steel via-bermuda to-steel absolute top-0 left-0 h-full rounded-full transition-all duration-1000"
style={`width: ${skill.level}%`}
/>
</div>
<div class="flex justify-between text-secondary font-mono text-[10px] mt-1 sm:mt-2 sm:text-xs">
<span>Beginner</span>
<span>Advanced</span>
</div>
</div>
</div>
);
})}
</div> </div>
<!-- Gradient overlays --> <!-- Gradient overlays -->
<div <div class="bg-linear-to-r from-neutral-200 to-transparent dark:from-stone-700 absolute top-0 bottom-0 left-0 z-10 w-12 sm:w-24"/>
class="absolute top-0 bottom-0 left-0 z-10 w-12 bg-linear-to-r from-neutral-200 to-transparent sm:w-24 dark:from-stone-700" <div class="bg-linear-to-l from-neutral-200 to-transparent dark:from-stone-700 absolute top-0 bottom-0 right-0 z-10 w-12 sm:w-24"/>
>
</div>
<div
class="absolute top-0 right-0 bottom-0 z-10 w-12 bg-linear-to-l from-neutral-200 to-transparent sm:w-24 dark:from-stone-700"
>
</div>
</div> </div>
</div> </div>
</section> </section>
<script> <script>
document.addEventListener('astro:page-load', () => { document.addEventListener('astro:page-load', () => {
// Create infinite scrolling effect
function setupInfiniteScroll() { function setupInfiniteScroll() {
const cards = document.querySelectorAll('.skill-card'); const cards = document.querySelectorAll('.skill-card');
if (!cards.length) return; if (!cards.length) return;
@@ -93,7 +70,6 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
setupInfiniteScroll(); setupInfiniteScroll();
// Add hover effects to cards - only on non-touch devices
const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0; const isTouchDevice = 'ontouchstart' in window || navigator.maxTouchPoints > 0;
const cards = document.querySelectorAll('.skill-card'); const cards = document.querySelectorAll('.skill-card');
@@ -144,7 +120,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
</script> </script>
<style> <style>
/* Tech Stack Slider */ /* Specific css to enable sliding effect */
.slider-track { .slider-track {
width: fit-content; width: fit-content;
animation: scroll 40s linear infinite; animation: scroll 40s linear infinite;
@@ -155,7 +131,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
transform: translateX(0); transform: translateX(0);
} }
100% { 100% {
transform: translateX(calc(-220px * 6 - 16px * 6)); /* Card width + margin for mobile */ transform: translateX(calc(-220px * 6 - 16px * 6));
} }
} }
@@ -169,7 +145,7 @@ const shadowClasses = 'shadow-xs hover:shadow-lg';
transform: translateX(0); transform: translateX(0);
} }
100% { 100% {
transform: translateX(calc(-280px * 6 - 32px * 6)); /* Card width + margin for desktop */ transform: translateX(calc(-280px * 6 - 32px * 6));
} }
} }
} }

View File

@@ -1,5 +1,5 @@
--- ---
import WeatherCard from '@components/ui/cards/WeatherCard.astro'; import WeatherCard from '@components/cards/WeatherCard.astro';
import { getFiveDayForecast } from '@support/weather'; import { getFiveDayForecast } from '@support/weather';
const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props; const { latitude = "44.95", longitude = "-93.09", cityName = "St. Paul, Minnesota", timezone = "America/Chicago" } = Astro.props;
@@ -8,7 +8,7 @@ const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, ti
<section class="mx-auto mb-20 max-w-340 px-4 py-10 sm:px-6 lg:px-8 lg:py-14 2xl:max-w-full"> <section class="mx-auto mb-20 max-w-340 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"> <div class="mx-auto mb-10 max-w-2xl text-center lg:mb-14">
<h1 class="card-text-header smooth-reveal block font-bold md:leading-tight"> <h1 class="smooth-reveal card-text-header block">
Weather in my Area Weather in my Area
</h1> </h1>
<div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center"> <div class="smooth-reveal mx-auto mt-5 max-w-3xl text-center">
@@ -17,7 +17,6 @@ const { forecastDays, error } = await getFiveDayForecast(latitude, longitude, ti
</span> </span>
</div> </div>
</div> </div>
{error ? ( {error ? (
<div class="card-base p-10 text-accent text-center"> <div class="card-base p-10 text-accent text-center">
{error} {error}

View File

@@ -1,32 +0,0 @@
---
import { Icon } from 'astro-icon/components';
const { title, url } = Astro.props;
interface Props {
title?: string;
url?: string;
}
const baseClasses =
'group group-hover inline-flex items-center justify-center gap-x-3 rounded-full px-4 py-3 text-center text-sm font-medium text-neutral-200';
const borderClasses = 'border border-transparent';
const bgColorClasses =
'bg-gitea-primary hover:bg-gitea-secondary dark:bg-gitea-secondary dark:hover:bg-gitea-primary';
const shadowClasses = 'shadow-sm';
const fontSizeClasses = '2xl:text-base';
---
<a
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${shadowClasses} ${fontSizeClasses} `}
href={url}
target="_blank"
rel="noopener noreferrer"
>
<Icon name="pajamas:gitea" class="h-4 w-4 md:h-6 md:w-6" />
{title}
<Icon
name="mdi:keyboard-arrow-right"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
</a>

View File

@@ -1,35 +0,0 @@
---
import Icon from '@components/ui/icons/icon.astro';
const { title, noArrow } = Astro.props;
interface Props {
title?: string;
url?: string;
noArrow?: boolean;
addHome?: boolean;
}
const baseClasses =
'group inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-50 ring-neutral-500 transition duration-300 focus-visible:ring outline-none';
const borderClasses = 'border border-transparent';
const bgColorClasses = 'bg-steel hover:bg-sky-800 active:bg-orange-500 dark:focus:outline-none';
const disableClasses = 'disabled:pointer-events-none disabled:opacity-50';
const fontSizeClasses = '2xl:text-base';
const ringClasses = 'dark:ring-neutral-200';
---
<button
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses}`}
id="back-button"
data-astro-prefetch
>
{noArrow ? null : <Icon name="arrowLeft" />}
{title}
</button>
<script>
document.getElementById('back-button')?.addEventListener('click', () => {
window.history.back();
});
</script>

View File

@@ -1,45 +0,0 @@
---
import { Icon } from 'astro-icon/components';
const { title, url, noArrow, addHome, addClass } = Astro.props;
interface Props {
title?: string;
url?: string;
noArrow?: boolean;
addHome?: boolean;
addClass?: string;
}
const baseClasses =
'group inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-sm font-bold text-neutral-100 transition duration-300 ';
const borderClasses = 'border border-transparent';
const bgColorClasses = 'bg-bermuda hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda';
const disableClasses = 'disabled:pointer-events-none disabled:opacity-50';
const fontSizeClasses = '2xl:text-base';
const ringClasses = 'dark:ring-neutral-200';
---
<a
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses} ${addClass}`}
href={url}
data-astro-prefetch
>
{
addHome ? (
<Icon
name="mdi:home-variant-outline"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
) : null
}
{title}
{
noArrow ? null : (
<Icon
name="mdi:keyboard-arrow-right"
class="h-3 w-3 translate-y-0.25 transition duration-300 group-hover:translate-x-1 md:h-5 md:w-5"
/>
)
}
</a>

View File

@@ -1,26 +0,0 @@
---
const { title, url } = Astro.props;
interface Props {
title?: string;
url?: string;
}
const baseClasses =
'inline-flex items-center justify-center gap-x-2 rounded-lg px-4 py-3 text-center text-sm font-medium text-neutral-600 shadow-sm outline-none ring-neutral-500 focus-visible:ring transition duration-300';
const borderClasses = 'border border-neutral-200';
const bgColorClasses = 'bg-neutral-300';
const hoverClasses = 'hover:bg-neutral-400/50 hover:text-neutral-600 active:text-neutral-700';
const disableClasses = 'disabled:pointer-events-none disabled:opacity-50';
const fontSizeClasses = '2xl:text-base';
const ringClasses = 'ring-neutral-500';
const darkClasses =
'dark:border-neutral-700 dark:bg-neutral-700 dark:text-neutral-300 dark:ring-neutral-200 dark:hover:bg-neutral-600 dark:focus:outline-none';
---
<a
class={`${baseClasses} ${borderClasses} ${bgColorClasses} ${hoverClasses} ${disableClasses} ${fontSizeClasses} ${ringClasses} ${darkClasses}`}
href={url}
>
{title}
</a>

View File

@@ -1,150 +0,0 @@
---
import Icon from '@components/ui/icons/icon.astro';
const { pageTitle, title = 'Share' } = Astro.props;
interface Props {
pageTitle: string;
title?: string;
}
type SocialPlatform = {
name: string;
url: string;
svg: string;
};
const socialPlatforms: SocialPlatform[] = [
{
name: 'Facebook',
url: `https://www.facebook.com/share.php?u=${Astro.url}&title=${pageTitle}`,
svg: 'facebook',
},
{
name: 'X',
url: `https://twitter.com/home/?status=${pageTitle}${Astro.url}`,
svg: 'x',
},
{
name: 'LinkedIn',
url: `https://www.linkedin.com/shareArticle?mini=true&url=${Astro.url}&title=${pageTitle}`,
svg: 'linkedIn',
},
];
---
<div class="hs-dropdown relative inline-flex [--auto-close:inside] [--placement:top-left]">
<button
id="hs-dropup"
type="button"
class="hs-dropdown-toggle inline-flex items-center gap-x-2 rounded-lg px-4 py-3 text-sm font-medium text-neutral-600 ring-neutral-500 transition duration-300 outline-none hover:bg-neutral-100 hover:text-neutral-700 focus-visible:ring dark:text-neutral-400 dark:ring-neutral-200 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:outline-none"
>
<Icon name="share" />
{title}
</button>
<div
class="hs-dropdown-menu duration hs-dropdown-open:opacity-100 z-10 hidden w-72 divide-y divide-neutral-200 rounded-lg bg-neutral-50 p-2 opacity-0 shadow-md transition-[opacity,margin] dark:divide-neutral-700 dark:border dark:border-neutral-700 dark:bg-neutral-800"
aria-labelledby="hs-dropup"
>
<div class="py-2 first:pt-0 last:pb-0">
{
socialPlatforms.map((platform) => (
<a
class="flex items-center gap-x-3.5 rounded-lg px-3 py-2 text-sm text-neutral-700 hover:bg-neutral-200 focus:bg-neutral-100 focus:outline-none dark:text-neutral-300 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700"
href={platform.url}
>
<Icon name={platform.svg} />
Share on {platform.name}
</a>
))
}
</div>
<div class="py-2 first:pt-0 last:pb-0">
<button
type="button"
class="js-clipboard hover:text-dark focus-visible:ring-secondary group inline-flex w-full items-center gap-x-3.5 rounded-lg px-3 py-2 text-sm text-neutral-700 hover:bg-neutral-200 focus:bg-neutral-100 focus:outline-none focus-visible:ring-1 focus-visible:outline-none dark:text-neutral-300 dark:hover:bg-neutral-700 dark:hover:text-neutral-300 dark:focus:bg-neutral-700"
data-clipboard-success-text="Copied"
>
<svg
class="js-clipboard-default h-4 w-4 transition group-hover:rotate-6"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<rect width="8" height="4" x="8" y="2" rx="1" ry="1"></rect>
<path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"></path>
</svg>
<svg
class="js-clipboard-success hidden h-4 w-4 text-neutral-700 dark:text-neutral-300"
width="24"
height="24"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<polyline points="20 6 9 17 4 12"></polyline>
</svg>
<span class="js-clipboard-success-text">Copy link</span>
</button>
</div>
</div>
</div>
<!--Import the necessary Dropdown and Clipboard plugins-->
<!--https://preline.co/plugins/html/dropdown.html-->
<!--<script is:inline src="/scripts/vendor/preline/dropdown/index.js"></script>-->
<!-- https://clipboardjs.com/ -->
<!--<script is:inline src="/scripts/vendor/clipboard.min.js"></script>-->
<script is:inline>
(function () {
window.addEventListener('load', () => {
const $clipboards = document.querySelectorAll('.js-clipboard');
$clipboards.forEach((el) => {
const clipboard = new ClipboardJS(el, {
text: () => {
return window.location.href;
},
});
clipboard.on('success', () => {
const $default = el.querySelector('.js-clipboard-default');
const $success = el.querySelector('.js-clipboard-success');
const $successText = el.querySelector('.js-clipboard-success-text');
const successText = el.dataset.clipboardSuccessText || '';
let oldSuccessText;
if ($successText) {
oldSuccessText = $successText.textContent;
$successText.textContent = successText;
}
if ($default && $success) {
$default.style.display = 'none';
$success.style.display = 'block';
}
setTimeout(() => {
if ($successText && oldSuccessText) {
$successText.textContent = oldSuccessText;
}
if ($default && $success) {
$success.style.display = '';
$default.style.display = '';
}
}, 800);
});
});
});
})();
</script>

View File

@@ -1,152 +0,0 @@
---
import { Icon } from 'astro-icon/components';
import { readItems } from '@directus/sdk';
import type { Experience } from '@lib/directusTypes';
import directus from '@lib/directus';
const experiences = await directus.request(
readItems('site_experience', {
fields: ['*'],
sort: ['-endDate'],
})
);
---
<section
class:list={['flex flex-col gap-8', Astro.props.className]}
>
<h3 class="relative 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 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

@@ -1,35 +0,0 @@
---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro';
interface Props {
title: string;
subTitle: string;
btnExists?: boolean;
btnTitle?: string;
btnURL?: string;
}
const { title, subTitle, btnExists, btnTitle, btnURL } = Astro.props;
---
<section class="mx-auto mt-10 px-4 sm:px-6 lg:px-8 lg:pt-10 2xl:max-w-full">
<div class="flex-wrap md:flex md:items-center md:justify-between">
<div class="w-full md:w-auto">
<h1
class="smooth-reveal block text-4xl font-bold tracking-tight text-balance text-neutral-800 md:text-5xl lg:text-6xl dark:text-neutral-200"
>
{title}
</h1>
<p class="smooth-reveal mt-4 text-lg text-pretty text-neutral-600 dark:text-neutral-400">
{subTitle}
</p>
{
btnExists ? (
<div class="smooth-reveal mt-4 md:mt-8">
<PrimaryCTA title={btnTitle} url={btnURL} />
</div>
) : null
}
</div>
</div>
</section>

View File

@@ -1,66 +0,0 @@
---
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro';
import SecondaryCTA from '@components/ui/buttons/SecondaryCTA.astro';
import Image from '@components/ui/images/Image.astro';
const { title, subTitle, primaryBtn, primaryBtnURL, secondaryBtn, secondaryBtnURL, src, alt } =
Astro.props;
interface Props {
title: string;
subTitle?: string;
primaryBtn?: string;
primaryBtnURL?: string;
secondaryBtn?: string;
secondaryBtnURL?: string;
src?: any;
alt?: string;
rounded?: boolean;
}
const roundedClasses = Astro.props.rounded ? "rounded-xl" : null;
---
<section
class="mx-auto grid max-w-340 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 ${roundedClasses}`}
draggable="false"
loading="eager"
format="webp"
quality="low"
widths={[840]}
disableBlur={true}
/>
)
}
</div>
</div>
</section>

View File

@@ -3,8 +3,8 @@ import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import PrimaryCTA from '@components/ui/buttons/PrimaryCTA.astro'; import GoBackButton from '@/components/buttons/GoBackButton.astro';
import GoBack from '@/components/ui/buttons/GoBack.astro'; import GoHomeButton from '@/components/buttons/GoHomeButton.astro';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
--- ---
@@ -61,8 +61,8 @@ const global = await directus.request(readSingleton('site_global'));
<div <div
class="smooth-reveal mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row" class="smooth-reveal mt-10 flex flex-col items-center justify-center gap-4 sm:flex-row"
> >
<GoBack title="Go Back" /> <GoBackButton/>
<PrimaryCTA title="Return Home" url={global.site_url} noArrow addHome /> <GoHomeButton url={global.site_url} />
</div> </div>
</div> </div>
</div> </div>

View File

@@ -3,11 +3,11 @@ import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import HeroSection from '@components/ui/sections/HeroSection.astro'; import HeroSection from '@components/sections/HeroSection.astro';
import Experience from '@components/ui/sections/Experience.astro'; import ExperienceSection from '@components/sections/ExperienceSection.astro';
import EducationSection from '@components/ui/sections/EducationSection.astro'; import EducationSection from '@components/sections/EducationSection.astro';
import ProjectSection from '@components/ui/sections/ProjectSection.astro'; import ProjectSection from '@components/sections/ProjectSection.astro';
import SkillsSlider from '@components/ui/sections/SkillsSlider.astro'; import SkillsSliderSection from '@components/sections/SkillsSliderSection.astro';
import portraitImg from '@images/portrait.avif'; import portraitImg from '@images/portrait.avif';
@@ -44,10 +44,10 @@ const global = await directus.request(readSingleton('site_global'));
<section class="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14"> <section class="mx-auto max-w-7xl px-4 py-10 sm:px-6 lg:px-8 lg:py-14">
<div class="flex flex-col gap-y-24 md:gap-y-32"> <div class="flex flex-col gap-y-24 md:gap-y-32">
<Experience className="smooth-reveal" /> <ExperienceSection className="smooth-reveal" />
<EducationSection className="smooth-reveal" /> <EducationSection className="smooth-reveal" />
<ProjectSection className="smooth-reveal" /> <ProjectSection className="smooth-reveal" />
<SkillsSlider className="smooth-reveal" /> <SkillsSliderSection className="smooth-reveal" />
</div> </div>
</section> </section>

View File

@@ -3,8 +3,8 @@ import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import HeroSection from '@components/ui/sections/HeroSection.astro'; import HeroSection from '@components/sections/HeroSection.astro';
import ApplicationSection from '@components/ui/sections/ApplicationSection.astro'; import ApplicationSection from '@components/sections/ApplicationSection.astro';
import applicationImg from '@images/cedar_tree.png'; import applicationImg from '@images/cedar_tree.png';

View File

@@ -10,6 +10,7 @@ import { createHighlighter } from 'shiki';
import { getDirectusImageURL } from '@lib/directusFunctions'; import { getDirectusImageURL } from '@lib/directusFunctions';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import Image from '@components/ui/images/Image.astro'; import Image from '@components/ui/images/Image.astro';
import SocialShareButton from '@components/buttons/SocialShareButton.astro';
import { formatDateTime } from '@support/time'; import { formatDateTime } from '@support/time';
export async function getStaticPaths() { export async function getStaticPaths() {
@@ -162,6 +163,9 @@ const content = marked.parse(post.content);
)) ))
} }
</div> </div>
<SocialShareButton
pageTitle={post.title}
/>
</div> </div>
</div> </div>
</div> </div>

View File

@@ -7,7 +7,7 @@ import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import BlogSelectedArticles from '@components/blog/BlogSelectedArticles.astro'; import BlogSelectedArticles from '@components/blog/BlogSelectedArticles.astro';
import BlogRecentArticles from '@components/blog/BlogRecentArticles.astro'; import BlogRecentArticles from '@components/blog/BlogRecentArticles.astro';
import HeroSection from '@components/ui/sections/HeroSection.astro'; import HeroSection from '@components/sections/HeroSection.astro';
import blogImg from '@images/autumn_tree.png'; import blogImg from '@images/autumn_tree.png';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));

View File

@@ -6,7 +6,7 @@ import directus from '@lib/directus';
import type { Post } from '@lib/directusTypes'; import type { Post } from '@lib/directusTypes';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import BlogCard from '@components/blog/BlogCard.astro'; import BlogCard from '@components/blog/BlogCard.astro';
import HeaderSection from '@components/ui/sections/HeaderSection.astro'; import HeaderSection from '@components/sections/HeaderSection.astro';
export async function getStaticPaths() { export async function getStaticPaths() {
const categories = await getCollection('categories'); const categories = await getCollection('categories');

View File

@@ -7,7 +7,7 @@ import type { Post } from '@lib/directusTypes';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import BlogCategoryCard from '@components/blog/BlogCategoryCard.astro'; import BlogCategoryCard from '@components/blog/BlogCategoryCard.astro';
import HeroSection from '@components/ui/sections/HeroSection.astro'; import HeroSection from '@components/sections/HeroSection.astro';
import { timeago } from '@support/time'; import { timeago } from '@support/time';
import categoryImg from '@images/autumn_bench.png'; import categoryImg from '@images/autumn_bench.png';

View File

@@ -3,11 +3,12 @@ import { readSingleton } from '@directus/sdk';
import directus from '@lib/directus'; import directus from '@lib/directus';
import BaseLayout from '@layouts/BaseLayout.astro'; import BaseLayout from '@layouts/BaseLayout.astro';
import HeroSection from '@components/ui/sections/HeroSection.astro'; import HeroSection from '@components/sections/HeroSection.astro';
import FeaturesSection from '@components/ui/sections/FeaturesSection.astro'; import FeaturesSection from '@components/sections/FeaturesSection.astro';
import WeatherSection from '@components/ui/sections/WeatherSection.astro'; import WeatherSection from '@components/sections/WeatherSection.astro';
import LatestPosts from '@components/ui/sections/LatestPosts.astro'; import LatestPostsSection from '@components/sections/LatestPostsSection.astro';
import HeroSectionAlt from '@components/ui/sections/HeroSectionAlt.astro'; import GiteaSection from '@components/sections/GiteaSection.astro';
import homeImg from '@images/autumn_mountain.png'; import homeImg from '@images/autumn_mountain.png';
const global = await directus.request(readSingleton('site_global')); const global = await directus.request(readSingleton('site_global'));
@@ -53,9 +54,9 @@ const weather = await directus.request(readSingleton('site_weather'));
> >
</WeatherSection> </WeatherSection>
<LatestPosts /> <LatestPostsSection />
<HeroSectionAlt <GiteaSection
title="Follow me on Gitea" title="Follow me on Gitea"
subTitle="I love open source and have my code availabile on my Gitea server." subTitle="I love open source and have my code availabile on my Gitea server."
url="https://gitea.alexlebens.dev" url="https://gitea.alexlebens.dev"

View File

@@ -1,5 +1,7 @@
@import 'tailwindcss'; @import 'tailwindcss';
@import 'preline/variants.css'; @import 'preline/variants.css';
@import './utilities.css';
@plugin '@tailwindcss/typography'; @plugin '@tailwindcss/typography';
@plugin '@tailwindcss/forms'; @plugin '@tailwindcss/forms';
@@ -10,11 +12,17 @@
/* Custom colors */ /* Custom colors */
@theme { @theme {
--color-midnight: #0c354d; --color-midnight: #0c354d;
--color-turquoise: #0da797; --color-ocean: #134e70;
--color-cobalt: #6c9cb0;
--color-steel: #4682b4; --color-steel: #4682b4;
--color-turquoise: #0da797;
--color-bermuda: #7fbab4; --color-bermuda: #7fbab4;
--color-desert: #f9deb2; --color-desert: #f9deb2;
--color-bronze: #9e7f5e; --color-bronze: #9e7f5e;
--color-gitea-primary: #609926; --color-gitea-primary: #609926;
--color-gitea-secondary: #4c7a33; --color-gitea-secondary: #4c7a33;
@@ -23,6 +31,7 @@
--color-header: light-dark(var(--color-neutral-800), var(--color-neutral-200)); --color-header: light-dark(var(--color-neutral-800), var(--color-neutral-200));
--color-primary: light-dark(var(--color-neutral-600), var(--color-neutral-200)); --color-primary: light-dark(var(--color-neutral-600), var(--color-neutral-200));
--color-primary-hover: light-dark(var(--color-neutral-800), var(--color-neutral-400));
--color-secondary: light-dark(var(--color-neutral-500), var(--color-neutral-400)); --color-secondary: light-dark(var(--color-neutral-500), var(--color-neutral-400));
} }
@@ -98,63 +107,6 @@
} }
} }
@utility card-base {
@apply rounded-xl transition-all duration-300
border border-neutral-100 dark:border-stone-500/20
bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90
shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg
}
@utility card-hover-icon-color {
@apply transition-all duration-300
text-primary
group-hover:text-main
}
@utility card-hover-icon-scale {
@apply transition-all duration-300
drop-shadow-sm
group-hover:scale-110
}
@utility card-text-header {
@apply text-header
md:text-5xl
text-4xl
}
@utility card-text-header-minor {
@apply text-header
md:text-3xl
text-2xl
font-semibold
}
@utility card-text-header-description {
@apply text-primary
text-lg
text-pretty
}
@utility card-text-title {
@apply text-primary
font-bold
}
@utility card-text-description {
@apply text-secondary
}
@utility card-hover-text-title {
@apply transition-all duration-300
group-hover:text-main
}
@utility card-hover-text-gitea {
@apply transition-all duration-300
group-hover:text-gitea-primary
}
/* Content reveal animations */ /* Content reveal animations */
.smooth-reveal, .smooth-reveal,
.smooth-reveal-2, .smooth-reveal-2,

110
src/styles/utilities.css Normal file
View File

@@ -0,0 +1,110 @@
/* Button classes */
@utility button-base {
@apply transition-all duration-300
border border-transparent
shadow-sm hover:shadow-md dark:shadow-md dark:hover:shadow-lg
px-4 py-3
}
@utility button-base-hidden {
@apply transition-all duration-300
border border-transparent
hover:bg-neutral-100 dark:hover:bg-neutral-700
p-2
}
@utility button-hover-arrow {
@apply translate-y-px transition duration-300
group-hover:translate-x-1
h-3 w-3 md:h-5 md:w-5
}
@utility button-text-title {
@apply text-neutral-200 2xl:text-base
text-sm font-bold
}
@utility button-text-title-hidden {
@apply transition-all duration-300
text-neutral-600 group-hover:text-neutral-700 dark:text-neutral-400 dark:group-hover:text-neutral-300 2xl:text-base
text-sm font-medium
}
@utility button-bg-blue {
@apply transition-all duration-300
bg-cobalt hover:bg-steel dark:bg-steel dark:hover:bg-cobalt
}
@utility button-bg-teal {
@apply transition-all duration-300
bg-bermuda hover:bg-turquoise dark:bg-turquoise dark:hover:bg-bermuda
}
@utility button-bg-neutral {
@apply transition-all duration-300
border border-neutral-100 dark:border-stone-500/20
bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90
}
@utility button-bg-gitea {
@apply transition-all duration-300
bg-gitea-primary hover:bg-gitea-secondary dark:bg-gitea-secondary dark:hover:bg-gitea-primary
}
/* Card classes */
@utility card-base {
@apply rounded-xl
border border-neutral-100 dark:border-stone-500/20
bg-neutral-100/80 hover:bg-neutral-100 dark:bg-neutral-800/60 dark:hover:bg-neutral-800/90
shadow-xs hover:shadow-md dark:shadow-md dark:hover:shadow-lg
}
@utility card-hover-icon-color {
@apply transition-all duration-300
text-primary
group-hover:text-main
}
@utility card-hover-icon-scale {
@apply transition-all duration-300
drop-shadow-sm
group-hover:scale-110
}
@utility card-text-header {
@apply text-header
text-4xl md:text-5xl
font-bold leading-tight tracking-tight text-balance
}
@utility card-text-header-minor {
@apply text-header
md:text-3xl
text-2xl
font-semibold
}
@utility card-text-header-description {
@apply text-primary
text-lg
text-pretty leading-relaxed
}
@utility card-text-title {
@apply text-primary
font-bold
}
@utility card-hover-text-title {
@apply transition-all duration-300
group-hover:text-main
}
@utility card-hover-text-gitea {
@apply transition-all duration-300
group-hover:text-gitea-primary
}
@utility card-text-description {
@apply text-secondary
}