Compare commits

...

71 Commits

Author SHA1 Message Date
4cd7e565eb Trying a couple more flags
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 32s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m35s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 6s
2025-11-08 01:26:57 -08:00
cf3b740907 Remove caching changes for now, put script back
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 26s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m30s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 6s
2025-11-08 01:06:15 -08:00
297a56e7d6 Try making script load earlier
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 26s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m22s
Build and Test - Staging / deploy_staging (pull_request) Successful in 5s
2025-11-08 00:59:53 -08:00
ee76972667 Titles for all pages, custom entrypoint with cache invalidator etag setup so site doesn't do bad things on redeploy
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 35s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m26s
Build and Test - Staging / test (pull_request) Successful in 3s
Build and Test - Staging / deploy_staging (pull_request) Successful in 6s
2025-11-08 00:52:32 -08:00
005e092344 Fixed logo title and favicon
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 8m0s
Build and Test - Staging / build_and_push (pull_request) Successful in 14m22s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 2s
2025-11-08 00:20:04 -08:00
ffbc3beaf7 About me page complete, tweak to default size of carousel on large screens, extra photo for homepage.
Some checks failed
Build and Test - Staging / determine_version (pull_request) Successful in 8m25s
Build and Test - Staging / test (pull_request) Has been cancelled
Build and Test - Staging / deploy_staging (pull_request) Has been cancelled
Build and Test - Staging / build_and_push (pull_request) Has been cancelled
2025-11-08 00:06:41 -08:00
506987c164 Trying nginx instead
All checks were successful
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
Build and Test - Staging / determine_version (pull_request) Successful in 27s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m35s
2025-11-07 22:26:38 -08:00
a807a39b09 Remove project version, set fixed httpd version
All checks were successful
Build and Test - Staging / build_and_push (pull_request) Successful in 2m24s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 5s
Build and Test - Staging / determine_version (pull_request) Successful in 6m20s
2025-11-07 21:59:49 -08:00
40a673a418 Compartmentalized Timeline and Carousel, applied www-data permissions to all files in built docker container, for deploys to work
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 30s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m21s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 5s
2025-11-07 21:36:08 -08:00
9a4ecdd073 Move inline typescript to dedicated scripts directory
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 24s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m5s
Build and Test - Staging / test (pull_request) Successful in 3s
Build and Test - Staging / deploy_staging (pull_request) Successful in 5s
2025-11-07 20:58:03 -08:00
d24a3054c1 Move script import to end of body
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 26s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m4s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 4s
2025-11-07 20:26:18 -08:00
d1bc55e556 Comment vitest config
All checks were successful
Build and Test - Staging / build_and_push (pull_request) Successful in 2m7s
Build and Test - Staging / test (pull_request) Successful in 3s
Build and Test - Staging / deploy_staging (pull_request) Successful in 6s
Build and Test - Staging / determine_version (pull_request) Successful in 27s
2025-11-07 20:11:30 -08:00
f0319c4446 Put tailwindcss vite back into regular deps
All checks were successful
Build and Test - Staging / build_and_push (pull_request) Successful in 2m0s
Build and Test - Staging / test (pull_request) Successful in 3s
Build and Test - Staging / determine_version (pull_request) Successful in 24s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
2025-11-07 20:03:25 -08:00
221587eff9 Disable playwright
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 27s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m8s
Build and Test - Staging / deploy_staging (pull_request) Successful in 4s
Build and Test - Staging / test (pull_request) Successful in 2s
2025-11-07 19:54:04 -08:00
9ac3295127 Disable playwright
Some checks failed
Build and Test - Staging / determine_version (pull_request) Has been cancelled
Build and Test - Staging / build_and_push (pull_request) Has been cancelled
Build and Test - Staging / test (pull_request) Has been cancelled
Build and Test - Staging / deploy_staging (pull_request) Has been cancelled
2025-11-07 19:52:22 -08:00
c5dde92023 Made event handlers attach after page load
Some checks failed
Build and Test - Staging / determine_version (pull_request) Successful in 29s
Playwright Tests / test (pull_request) Has been cancelled
Build and Test - Staging / test (pull_request) Has been cancelled
Build and Test - Staging / deploy_staging (pull_request) Has been cancelled
Build and Test - Staging / build_and_push (pull_request) Has been cancelled
2025-11-07 19:50:46 -08:00
f91be707d9 Upgraded astro and npm packages
Some checks failed
Build and Test - Staging / determine_version (pull_request) Successful in 8m56s
Playwright Tests / test (pull_request) Failing after 9m22s
Build and Test - Staging / build_and_push (pull_request) Successful in 16m18s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
2025-11-07 00:04:04 -08:00
7fe9303d5e No typing for script import
Some checks failed
Build and Test - Staging / determine_version (pull_request) Successful in 27s
Playwright Tests / test (pull_request) Failing after 2m21s
Build and Test - Staging / build_and_push (pull_request) Successful in 2m13s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 6s
2025-11-06 23:40:38 -08:00
33388f59ee Added media for kube, home server rack, body mods, chubby buttons, and home page
Some checks failed
Build and Test - Staging / determine_version (pull_request) Successful in 1m47s
Playwright Tests / test (pull_request) Failing after 2m49s
Build and Test - Staging / build_and_push (pull_request) Successful in 4m36s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 7s
2025-11-06 23:14:19 -08:00
3e34b94ec5 Dark footer, added photos for offsite backup rack 2025-11-06 17:37:33 -08:00
128dc14459 Education page finished, improvements to carousel, placeholder content from old website 2025-11-06 16:47:10 -08:00
d6e75ae2ea Made a baseline working carousel, timeline, and started flushing out content for primary spacex experience 2025-11-06 01:21:27 -08:00
6f728ad146 Responsive navigation fully working, placeholders for all major items on site, resume pages working, abstracted site-layout, preparing for unit and e2e testing, added flotbite + tailwind + vitest + playwright 2025-11-05 03:13:05 -08:00
b342f7b1cb Favicon, better screen responsive, starting experience layouts
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 6s
Build and Test - Staging / build_and_push (pull_request) Successful in 43s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
2025-07-09 04:46:17 -07:00
5b0829eac4 Make build environment public for merge
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 6s
Build and Test - Staging / build_and_push (pull_request) Successful in 42s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 2s
2025-07-09 04:00:57 -07:00
f7825ed740 Fix wrong quote escape
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 5s
Build and Test - Staging / build_and_push (pull_request) Successful in 40s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 2s
2025-07-09 03:52:42 -07:00
3d88f23e88 Fix broken .env write in Dockerfile, add footer border style
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 6s
Build and Test - Staging / build_and_push (pull_request) Successful in 42s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
2025-07-09 03:48:40 -07:00
efaa02ae20 Footer with environment, build, and hash
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 5s
Build and Test - Staging / build_and_push (pull_request) Successful in 41s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
2025-07-09 03:42:23 -07:00
396b6d210b Merge pull request 'Add version, hash, and environment as build variables' (#4) from staging-build into main
All checks were successful
Build and Test - Production / determine_version (push) Successful in 5s
Build and Test - Production / build_and_push (push) Successful in 42s
Build and Test - Production / test (push) Successful in 2s
Build and Test - Production / deploy_production (push) Successful in 3s
Reviewed-on: #4
2025-07-09 10:14:03 +00:00
594bc9bc4f No sleep, just need to call start and not stop...
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 6s
Build and Test - Staging / build_and_push (pull_request) Successful in 42s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 2s
2025-07-09 03:12:21 -07:00
e4a87c3941 Deployment needs sleep after stopping
All checks were successful
Build and Test - Staging / build_and_push (pull_request) Successful in 41s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / determine_version (pull_request) Successful in 5s
Build and Test - Staging / deploy_staging (pull_request) Successful in 7s
2025-07-09 03:09:49 -07:00
3ba6db7f83 Make variables public
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 6s
Build and Test - Staging / build_and_push (pull_request) Successful in 40s
Build and Test - Staging / test (pull_request) Successful in 2s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
2025-07-09 03:06:30 -07:00
b37ee3f94c Add version, hash, and environment as build variables 2025-07-09 03:04:22 -07:00
da2fc876d4 Merge pull request 'Commonize builds, add staging build' (#3) from staging-build into main
All checks were successful
Build and Test - Production / determine_version (push) Successful in 5s
Build and Test - Production / build_and_push (push) Successful in 44s
Build and Test - Production / test (push) Successful in 2s
Build and Test - Production / deploy_production (push) Successful in 3s
Reviewed-on: #3
2025-07-09 09:45:13 +00:00
4c6aa09c12 Staging to stg
All checks were successful
Build and Test - Staging / determine_version (pull_request) Successful in 6s
Build and Test - Staging / build_and_push (pull_request) Successful in 41s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / deploy_staging (pull_request) Successful in 2s
2025-07-09 02:40:34 -07:00
88996012d7 Revert to copy/paste for now for staging
All checks were successful
Build and Test - Staging / build_and_push (pull_request) Successful in 42s
Build and Test - Staging / test (pull_request) Successful in 1s
Build and Test - Staging / determine_version (pull_request) Successful in 6s
Build and Test - Staging / deploy_staging (pull_request) Successful in 3s
2025-07-09 02:37:04 -07:00
cb2c9dece3 Add manual jobs from common
All checks were successful
Build and Test Staging / determine_version (pull_request) Successful in 7s
Build and Test Staging / build_and_push (pull_request) Successful in 6s
Build and Test Staging / deploy_production (pull_request) Successful in 6s
Build and Test Staging / test (pull_request) Successful in 5s
2025-07-09 02:32:18 -07:00
d6a3341944 Actually assign environment variable for deploy targets and docker tags
All checks were successful
Build and Test Staging / build_test_deploy (pull_request) Successful in 6s
2025-07-09 02:24:55 -07:00
bb39ed567f No longer using array in bash docker tag generation
All checks were successful
Build and Test Staging / build_test_deploy (pull_request) Has been skipped
2025-07-09 02:22:05 -07:00
548118e13e Add missing $
All checks were successful
Build and Test Staging / build_test_deploy (pull_request) Has been skipped
2025-07-09 02:18:05 -07:00
3ecb9b2994 Commonize builds, add staging build
All checks were successful
Build and Test Staging / build_test_deploy (pull_request) Has been skipped
2025-07-09 02:11:04 -07:00
7de03b565a Merge pull request 'Import styles from correct path' (#2) from website-content-updates into main
All checks were successful
Build and Test / determine_version (push) Successful in 11s
Build and Test / build_and_push (push) Successful in 2m58s
Build and Test / test (push) Successful in 2s
Build and Test / deploy_production (push) Successful in 3s
Reviewed-on: #2
2025-07-04 07:36:42 +00:00
ba121c6479 Import styles from correct path 2025-07-04 00:36:12 -07:00
0924c7ba00 Merge pull request 'website-content-updates' (#1) from website-content-updates into main
Some checks failed
Build and Test / determine_version (push) Successful in 47s
Build and Test / build_and_push (push) Failing after 2m15s
Build and Test / test (push) Has been skipped
Build and Test / deploy_production (push) Has been skipped
Reviewed-on: #1
2025-07-04 07:31:55 +00:00
a0e66ec8e2 Super basic home and contact page to be able to put something live 2025-07-04 00:31:09 -07:00
d515e581b5 Playing around with options for base structure and navigation 2025-07-03 23:50:20 -07:00
65e05066ea Add .gitignore, updated config.ts, makefile, and npm package file updates 2025-07-03 19:55:41 -07:00
4aa7a85f6b Add experiences demo
All checks were successful
Build and Test / determine_version (push) Successful in 7s
Build and Test / build_and_push (push) Successful in 1m15s
Build and Test / test (push) Successful in 2s
Build and Test / deploy_production (push) Successful in 3s
2025-03-31 15:38:39 -07:00
46bcd35375 Remove true from app start stop on deploy
All checks were successful
Build and Test / determine_version (push) Successful in 6s
Build and Test / build_and_push (push) Successful in 1m14s
Build and Test / test (push) Successful in 2s
Build and Test / deploy_production (push) Successful in 3s
2025-03-31 15:35:19 -07:00
603a6a0887 Ignore cert errors on deploy
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / build_and_push (push) Successful in 1m15s
Build and Test / test (push) Successful in 2s
Build and Test / deploy_production (push) Failing after 3s
2025-03-31 15:31:25 -07:00
cd397b1d15 Automated deployment
Some checks failed
Build and Test / determine_version (push) Successful in 9s
Build and Test / build_and_push (push) Successful in 1m6s
Build and Test / test (push) Successful in 1s
Build and Test / deploy_production (push) Failing after 2s
2025-03-31 15:28:57 -07:00
d860a03aaa Fix broken variable
All checks were successful
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Successful in 1m23s
2025-03-23 23:31:42 -07:00
5d56e7eeed Add experience pages
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 1s
Build and Test / build_and_push (push) Has been cancelled
2025-03-23 23:31:05 -07:00
b12968a3ab Maintain config ts
All checks were successful
Build and Test / determine_version (push) Successful in 7s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Successful in 1m17s
2025-03-23 23:22:54 -07:00
41ca21416e Debugging
All checks were successful
Build and Test / determine_version (push) Successful in 7s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Successful in 1m27s
2025-03-23 23:16:26 -07:00
088af03276 Debugging
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Failing after 42s
2025-03-23 22:27:59 -07:00
9e69bb6195 Debugging
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 1s
Build and Test / build_and_push (push) Failing after 42s
2025-03-23 22:20:48 -07:00
8f968de567 Debugging
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Failing after 43s
2025-03-23 22:18:50 -07:00
a14cf2c004 Debugging
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Failing after 42s
2025-03-23 22:14:18 -07:00
dfc7ca20bf Update content paths
Some checks failed
Build and Test / determine_version (push) Successful in 7s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Failing after 37s
2025-03-23 22:12:05 -07:00
3987e21804 New actions token
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Failing after 41s
2025-03-23 22:07:37 -07:00
a355bb1bcd Set token for private repo access
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Failing after 34s
2025-03-23 22:02:56 -07:00
94b55eb245 Set branch name
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Failing after 42s
2025-03-23 21:55:59 -07:00
076cd05388 Pull in obsidian website content
Some checks failed
Build and Test / determine_version (push) Successful in 9s
Build and Test / test (push) Successful in 1s
Build and Test / build_and_push (push) Failing after 41s
2025-03-23 21:51:26 -07:00
462b08911d Improved tagging
All checks were successful
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Successful in 39s
2025-03-08 01:14:48 -08:00
9050faa239 Improved tagging
All checks were successful
Build and Test / determine_version (push) Successful in 6s
Build and Test / test (push) Successful in 2s
Build and Test / build_and_push (push) Successful in 38s
2025-03-08 00:50:44 -08:00
04e08a8873 Improved tagging
Some checks failed
Build and Test / determine_version (push) Successful in 6s
Build and Test / build_and_push (push) Failing after 12s
Build and Test / test (push) Successful in 1s
2025-03-08 00:49:46 -08:00
87c8a15fe6 Improved tagging
Some checks failed
Build and Test / determine_version (push) Successful in 5s
Build and Test / build_and_push (push) Failing after 13s
Build and Test / test (push) Successful in 2s
2025-03-08 00:40:50 -08:00
5544a08add Improved tagging
Some checks failed
Build and Test / determine_version (push) Successful in 41s
Build and Test / build_and_push (push) Failing after 13s
Build and Test / test (push) Successful in 1s
2025-03-08 00:32:52 -08:00
ef4572e0a6 Merge remote-tracking branch 'origin/main'
All checks were successful
Build and Test / build_and_push (push) Successful in 57s
Build and Test / test (push) Successful in 2s
2025-03-08 00:03:54 -08:00
b319386274 Add missing node build files 2025-03-08 00:03:09 -08:00
121 changed files with 4176 additions and 1456 deletions

View File

@@ -1,3 +1,7 @@
.DS_Store
node_modules
dist
.idea
.astro
*/build/
*/node_modules/

View File

@@ -0,0 +1,27 @@
name: Playwright Tests
on:
push:
branches: [ main, master ]
pull_request:
branches: [ main, master ]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: lts/*
- name: Install dependencies
run: npm ci
- name: Install Playwright Browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30

View File

@@ -0,0 +1,94 @@
name: Build and Test - Production
on:
push:
branches: [main]
jobs:
determine_version:
runs-on: ubuntu-latest
outputs:
repo_name: ${{ steps.project_metadata.outputs.REPO_NAME }}
repo_version_hash: ${{ steps.project_metadata.outputs.REPO_VERSION_HASH }}
project_version: ${{ steps.project_metadata.outputs.PROJECT_VERSION }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node Environment
uses: actions/setup-node@v4
- name: Acquire Project Metadata
id: project_metadata
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION_HASH=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
build_and_push:
runs-on: ubuntu-latest
needs: determine_version
steps:
- name: Checkout caperren-com Repository
uses: actions/checkout@v4
with:
path: caperren-com
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: gitea.perren.cloud
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.ACTIONS_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: caperren-com
push: true
tags: |
gitea.perren.cloud/caperren/${{ needs.determine_version.outputs.repo_name }}:${{ needs.determine_version.outputs.repo_version_hash }}
gitea.perren.cloud/caperren/${{ needs.determine_version.outputs.repo_name }}:${{ needs.determine_version.outputs.project_version }}
gitea.perren.cloud/caperren/caperren-com:latest
build-args: |
REPO_VERSION_HASH=${{ needs.determine_version.outputs.repo_version_hash }}
BUILD_ENVIRONMENT=production
test:
runs-on: ubuntu-latest
needs: build_and_push
steps:
- run: echo "Placeholder"
deploy_production:
runs-on: ubuntu-latest
needs: test
steps:
- name: Pull New Image For App
run: |
curl -k -X 'POST' \
'https://caperren.com:444/api/v2.0/app/pull_images' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
-H 'Content-Type: application/json' \
-d '{
"name": "caperren-com",
"options": {}
}'
- name: Stop App
run: |
curl -k -X 'POST' \
'https://caperren.com:444/api/v2.0/app/stop' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
-H 'Content-Type: application/json' \
-d '"caperren-com"'
- name: Start App
run: |
curl -k -X 'POST' \
'https://caperren.com:444/api/v2.0/app/start' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
-H 'Content-Type: application/json' \
-d '"caperren-com"'

View File

@@ -0,0 +1,93 @@
name: Build and Test - Staging
on:
pull_request:
types: [ opened, synchronize, reopened ]
jobs:
determine_version:
runs-on: ubuntu-latest
outputs:
repo_name: ${{ steps.project_metadata.outputs.REPO_NAME }}
repo_version_hash: ${{ steps.project_metadata.outputs.REPO_VERSION_HASH }}
project_version: ${{ steps.project_metadata.outputs.PROJECT_VERSION }}
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Setup Node Environment
uses: actions/setup-node@v4
- name: Acquire Project Metadata
id: project_metadata
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION_HASH=$(git describe --tags --always | sed 's/^v//') >> $GITHUB_OUTPUT
build_and_push:
runs-on: ubuntu-latest
needs: determine_version
steps:
- name: Checkout caperren-com Repository
uses: actions/checkout@v4
with:
path: caperren-com
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: gitea.perren.cloud
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.ACTIONS_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: caperren-com
push: true
tags: |
gitea.perren.cloud/caperren/caperren-com:latest-staging
build-args: |
REPO_VERSION_HASH=${{ needs.determine_version.outputs.repo_version_hash }}
BUILD_ENVIRONMENT=staging
test:
runs-on: ubuntu-latest
needs: build_and_push
steps:
- run: echo "Placeholder"
deploy_staging:
runs-on: ubuntu-latest
needs: test
steps:
- name: Pull New Image For App
run: |
curl -k -X 'POST' \
'https://caperren.com:444/api/v2.0/app/pull_images' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
-H 'Content-Type: application/json' \
-d '{
"name": "caperren-com-stg",
"options": {}
}'
- name: Stop App
run: |
curl -k -X 'POST' \
'https://caperren.com:444/api/v2.0/app/stop' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
-H 'Content-Type: application/json' \
-d '"caperren-com-stg"'
- name: Start App
run: |
curl -k -X 'POST' \
'https://caperren.com:444/api/v2.0/app/start' \
-H 'accept: */*' \
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
-H 'Content-Type: application/json' \
-d '"caperren-com-stg"'

View File

@@ -1,42 +0,0 @@
name: Build and Test
run-name: ${{ gitea.actor }} is triggered a new
on: [ push ]
jobs:
build_and_push:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Login to Docker Registry
uses: docker/login-action@v3
with:
registry: gitea.perren.cloud
username: ${{ secrets.REGISTRY_USERNAME }}
password: ${{ secrets.REGISTRY_TOKEN }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3
- name: Build and Push
uses: docker/build-push-action@v5
with:
context: .
push: true
tags: gitea.perren.cloud/caperren/caperren-com:latest
test:
runs-on: ubuntu-latest
steps:
- run: echo "Tests would go here"
# - run: echo "🎉 The job was automatically triggered by a ${{ gitea.event_name }} event."
# - run: echo "🐧 This job is now running on a ${{ runner.os }} server hosted by Gitea!"
# - run: echo "🔎 The name of your branch is ${{ gitea.ref }} and your repository is ${{ gitea.repository }}."
# - name: Check out repository code
# uses: actions/checkout@v4
# - run: echo "💡 The ${{ gitea.repository }} repository has been cloned to the runner."
# - run: echo "🖥️ The workflow is now ready to test your code on the runner."
# - name: List files in the repository
# run: |
# ls ${{ gitea.workspace }}
# - run: echo "🍏 This job's status is ${{ job.status }}."

13
.gitignore vendored
View File

@@ -1,3 +1,9 @@
# Ignore everything under src/content, as they are dynamically added from obsidian
src/content/*
# Do not ignore config.ts in src/content since that necessary to import the dynamic content
!src/content/config.ts
# build output
dist/
@@ -22,3 +28,10 @@ pnpm-debug.log*
# jetbrains setting folder
.idea/
# Playwright
/test-results/
/playwright-report/
/blob-report/
/playwright/.cache/
/playwright/.auth/

View File

@@ -8,18 +8,26 @@ COPY package.json package-lock.json tsconfig.json astro.config.mjs ./
FROM base AS prod-deps
RUN npm install --omit=dev
FROM base AS build-deps
RUN npm install
FROM prod-deps AS build
FROM build-deps AS build
COPY . .
ARG REPO_VERSION_HASH
ARG BUILD_ENVIRONMENT
RUN echo "PUBLIC_REPO_VERSION_HASH=\"${REPO_VERSION_HASH}\" \n\
PUBLIC_BUILD_ENVIRONMENT=\"${BUILD_ENVIRONMENT}\"" >> .env
RUN npm run build
FROM httpd:latest AS runtime
WORKDIR /usr/local/apache2/htdocs
FROM nginx:alpine AS runtime
RUN rm index.html
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
COPY --from=build /app/dist /usr/share/nginx/html
COPY --from=build /app/dist .
RUN chown -R nginx:nginx /usr/share/nginx/html
COPY entrypoint.sh /entrypoint.sh
EXPOSE 80
ENTRYPOINT ["/entrypoint.sh"]

30
Makefile Normal file
View File

@@ -0,0 +1,30 @@
# Makefile for caperren.com
.PHONY: \
install \
fix \
astro_upgrade \
build \
dev \
dev-hosted
default: dev
install:
npm install
fix:
npm audit fix
astro_upgrade:
npx @astrojs/upgrade
build:
npm run build
dev:
npm run dev
dev-hosted:
npm run dev-hosted

View File

@@ -1,11 +1,13 @@
// @ts-check
import { defineConfig } from 'astro/config';
import rehypeAstroRelativeMarkdownLinks from "astro-rehype-relative-markdown-links";
import tailwindcss from "@tailwindcss/vite";
// https://astro.build/config
export default defineConfig({
integrations: [],
markdown: {
rehypePlugins: [rehypeAstroRelativeMarkdownLinks],
vite: {
plugins: [tailwindcss()],
},
});

View File

@@ -0,0 +1,8 @@
import { test, expect } from '@playwright/test';
test('Has Title', async ({ page }) => {
await page.goto('/');
// Expect a title "to contain" a substring.
await expect(page).toHaveTitle(/Corwin Perren/);
});

14
entrypoint.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/usr/bin/env sh
set -e
# Generate a unique token per container launch
TOKEN="$(cat /proc/sys/kernel/random/uuid)" # or: TOKEN="$(date +%s%N)"
# Write it into a file NGINX will include
cat >/etc/nginx/conf.d/_release_token.conf <<EOF
# auto-generated at container start
set \$release_token "$TOKEN";
EOF
# Then exec nginx
exec nginx -g 'daemon off;'

40
nginx/nginx.conf Normal file
View File

@@ -0,0 +1,40 @@
worker_processes 4;
events {
worker_connections 1024;
}
http {
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=my_cache:10m max_size=1g inactive=60m use_temp_path=off;
server {
listen 80;
server_name _;
root /usr/share/nginx/html;
index index.html index.htm;
include /etc/nginx/mime.types;
#include /etc/nginx/conf.d/_release_token.conf;
#etag off;
#add_header ETag "\"W/$release_token\"" always;
#
gzip on;
gzip_proxied any;
gzip_types text/plain text/css application/json application/javascript application/x-javascript text/xml application/xml application/xml+rss text/javascript;
error_page 404 /404.html;
location = /404.html {
root /usr/share/nginx/html;
internal;
}
location / {
proxy_cache my_cache;
proxy_cache_valid 200 1h;
add_header Cache-Control "max-age=0, must-revalidate" always;
try_files $uri $uri/index.html =404;
}
}
}

3326
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -1,15 +1,28 @@
{
"name": "my-project",
"name": "caperren-com",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"dev-hosted": "astro dev --host",
"build": "astro build",
"preview": "astro preview",
"astro": "astro"
"astro": "astro",
"test": "vitest"
},
"dependencies": {
"astro": "^5.4.2",
"astro-rehype-relative-markdown-links": "^0.18.1"
"astro": "^5.15.4",
"flowbite": "^3.1.2",
"leader-line-new": "^1.1.9",
"luxon": "^3.7.2",
"tailwindcss": "^4.1.11",
"@tailwindcss/vite": "^4.1.11",
"uuid": "^13.0.0"
},
"devDependencies": {
"@playwright/test": "^1.56.1",
"@types/luxon": "^3.7.1",
"@types/node": "^24.10.0",
"vitest": "^4.0.7"
}
}

79
playwright.config.ts Normal file
View File

@@ -0,0 +1,79 @@
import { defineConfig, devices } from '@playwright/test';
/**
* Read environment variables from file.
* https://github.com/motdotla/dotenv
*/
// import dotenv from 'dotenv';
// import path from 'path';
// dotenv.config({ path: path.resolve(__dirname, '.env') });
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
testDir: './e2e',
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 2 : 0,
/* Opt out of parallel tests on CI. */
workers: process.env.CI ? 1 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: 'html',
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('')`. */
baseURL: 'http://localhost:4321',
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
},
/* Configure projects for major browsers */
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
// {
// name: 'firefox',
// use: { ...devices['Desktop Firefox'] },
// },
//
// {
// name: 'webkit',
// use: { ...devices['Desktop Safari'] },
// },
/* Test against mobile viewports. */
{
name: 'Mobile Chrome',
use: { ...devices['Pixel 5'] },
},
// {
// name: 'Mobile Safari',
// use: { ...devices['iPhone 12'] },
// },
/* Test against branded browsers. */
// {
// name: 'Microsoft Edge',
// use: { ...devices['Desktop Edge'], channel: 'msedge' },
// },
// {
// name: 'Google Chrome',
// use: { ...devices['Desktop Chrome'], channel: 'chrome' },
// },
],
/* Run your local dev server before starting the tests */
// webServer: {
// command: 'npm run start',
// url: 'http://localhost:3000',
// reuseExistingServer: !process.env.CI,
// },
});

BIN
public/favicon-solid.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 B

View File

@@ -1,9 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 128 128">
<path d="M50.4 78.5a75.1 75.1 0 0 0-28.5 6.9l24.2-65.7c.7-2 1.9-3.2 3.4-3.2h29c1.5 0 2.7 1.2 3.4 3.2l24.2 65.7s-11.6-7-28.5-7L67 45.5c-.4-1.7-1.6-2.8-2.9-2.8-1.3 0-2.5 1.1-2.9 2.7L50.4 78.5Zm-1.1 28.2Zm-4.2-20.2c-2 6.6-.6 15.8 4.2 20.2a17.5 17.5 0 0 1 .2-.7 5.5 5.5 0 0 1 5.7-4.5c2.8.1 4.3 1.5 4.7 4.7.2 1.1.2 2.3.2 3.5v.4c0 2.7.7 5.2 2.2 7.4a13 13 0 0 0 5.7 4.9v-.3l-.2-.3c-1.8-5.6-.5-9.5 4.4-12.8l1.5-1a73 73 0 0 0 3.2-2.2 16 16 0 0 0 6.8-11.4c.3-2 .1-4-.6-6l-.8.6-1.6 1a37 37 0 0 1-22.4 2.7c-5-.7-9.7-2-13.2-6.2Z" />
<style>
path { fill: #000; }
@media (prefers-color-scheme: dark) {
path { fill: #FFF; }
}
</style>
</svg>

Before

Width:  |  Height:  |  Size: 749 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 956 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" fill="none" width="115" height="48"><path fill="#17191E" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="url(#a)" d="M7.77 36.35C6.4 35.11 6 32.51 6.57 30.62c.99 1.2 2.35 1.57 3.75 1.78 2.18.33 4.31.2 6.33-.78.23-.12.44-.27.7-.42.18.55.23 1.1.17 1.67a4.56 4.56 0 0 1-1.94 3.23c-.43.32-.9.61-1.34.91-1.38.94-1.76 2.03-1.24 3.62l.05.17a3.63 3.63 0 0 1-1.6-1.38 3.87 3.87 0 0 1-.63-2.1c0-.37 0-.74-.05-1.1-.13-.9-.55-1.3-1.33-1.32a1.56 1.56 0 0 0-1.63 1.26c0 .06-.03.12-.05.2Z"/><path fill="#17191E" d="M.02 30.31s4.02-1.95 8.05-1.95l3.04-9.4c.11-.45.44-.76.82-.76.37 0 .7.31.82.76l3.04 9.4c4.77 0 8.05 1.95 8.05 1.95L17 11.71c-.2-.56-.53-.91-.98-.91H7.83c-.44 0-.76.35-.97.9L.02 30.31Zm42.37-5.97c0 1.64-2.05 2.62-4.88 2.62-1.85 0-2.5-.45-2.5-1.41 0-1 .8-1.49 2.65-1.49 1.67 0 3.09.03 4.73.23v.05Zm.03-2.04a21.37 21.37 0 0 0-4.37-.36c-5.32 0-7.82 1.25-7.82 4.18 0 3.04 1.71 4.2 5.68 4.2 3.35 0 5.63-.84 6.46-2.92h.14c-.03.5-.05 1-.05 1.4 0 1.07.18 1.16 1.06 1.16h4.15a16.9 16.9 0 0 1-.36-4c0-1.67.06-2.93.06-4.62 0-3.45-2.07-5.64-8.56-5.64-2.8 0-5.9.48-8.26 1.19.22.93.54 2.83.7 4.06 2.04-.96 4.95-1.37 7.2-1.37 3.11 0 3.97.71 3.97 2.15v.57Zm11.37 3c-.56.07-1.33.07-2.12.07-.83 0-1.6-.03-2.12-.1l-.02.58c0 2.85 1.87 4.52 8.45 4.52 6.2 0 8.2-1.64 8.2-4.55 0-2.74-1.33-4.09-7.2-4.39-4.58-.2-4.99-.7-4.99-1.28 0-.66.59-1 3.65-1 3.18 0 4.03.43 4.03 1.35v.2a46.13 46.13 0 0 1 4.24.03l.02-.55c0-3.36-2.8-4.46-8.2-4.46-6.08 0-8.13 1.49-8.13 4.39 0 2.6 1.64 4.23 7.48 4.48 4.3.14 4.77.62 4.77 1.28 0 .7-.7 1.03-3.71 1.03-3.47 0-4.35-.48-4.35-1.47v-.13Zm19.82-12.05a17.5 17.5 0 0 1-6.24 3.48c.03.84.03 2.4.03 3.24l1.5.02c-.02 1.63-.04 3.6-.04 4.9 0 3.04 1.6 5.32 6.58 5.32 2.1 0 3.5-.23 5.23-.6a43.77 43.77 0 0 1-.46-4.13c-1.03.34-2.34.53-3.78.53-2 0-2.82-.55-2.82-2.13 0-1.37 0-2.65.03-3.84 2.57.02 5.13.07 6.64.11-.02-1.18.03-2.9.1-4.04-2.2.04-4.65.07-6.68.07l.07-2.93h-.16Zm13.46 6.04a767.33 767.33 0 0 1 .07-3.18H82.6c.07 1.96.07 3.98.07 6.92 0 2.95-.03 4.99-.07 6.93h5.18c-.09-1.37-.11-3.68-.11-5.65 0-3.1 1.26-4 4.12-4 1.33 0 2.28.16 3.1.46.03-1.16.26-3.43.4-4.43-.86-.25-1.81-.41-2.96-.41-2.46-.03-4.26.98-5.1 3.38l-.17-.02Zm22.55 3.65c0 2.5-1.8 3.66-4.64 3.66-2.81 0-4.61-1.1-4.61-3.66s1.82-3.52 4.61-3.52c2.82 0 4.64 1.03 4.64 3.52Zm4.71-.11c0-4.96-3.87-7.18-9.35-7.18-5.5 0-9.23 2.22-9.23 7.18 0 4.94 3.49 7.59 9.21 7.59 5.77 0 9.37-2.65 9.37-7.6Z"/><defs><linearGradient id="a" x1="6.33" x2="19.43" y1="40.8" y2="34.6" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 2.8 KiB

View File

@@ -1 +0,0 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1440" height="1024" fill="none"><path fill="url(#a)" fill-rule="evenodd" d="M-217.58 475.75c91.82-72.02 225.52-29.38 341.2-44.74C240 415.56 372.33 315.14 466.77 384.9c102.9 76.02 44.74 246.76 90.31 366.31 29.83 78.24 90.48 136.14 129.48 210.23 57.92 109.99 169.67 208.23 155.9 331.77-13.52 121.26-103.42 264.33-224.23 281.37-141.96 20.03-232.72-220.96-374.06-196.99-151.7 25.73-172.68 330.24-325.85 315.72-128.6-12.2-110.9-230.73-128.15-358.76-12.16-90.14 65.87-176.25 44.1-264.57-26.42-107.2-167.12-163.46-176.72-273.45-10.15-116.29 33.01-248.75 124.87-320.79Z" clip-rule="evenodd" style="opacity:.154"/><path fill="url(#b)" fill-rule="evenodd" d="M1103.43 115.43c146.42-19.45 275.33-155.84 413.5-103.59 188.09 71.13 409 212.64 407.06 413.88-1.94 201.25-259.28 278.6-414.96 405.96-130 106.35-240.24 294.39-405.6 265.3-163.7-28.8-161.93-274.12-284.34-386.66-134.95-124.06-436-101.46-445.82-284.6-9.68-180.38 247.41-246.3 413.54-316.9 101.01-42.93 207.83 21.06 316.62 6.61Z" clip-rule="evenodd" style="opacity:.154"/><defs><linearGradient id="b" x1="373" x2="1995.44" y1="1100" y2="118.03" gradientUnits="userSpaceOnUse"><stop stop-color="#D83333"/><stop offset="1" stop-color="#F041FF"/></linearGradient><linearGradient id="a" x1="107.37" x2="1130.66" y1="1993.35" y2="1026.31" gradientUnits="userSpaceOnUse"><stop stop-color="#3245FF"/><stop offset="1" stop-color="#BC52EE"/></linearGradient></defs></svg>

Before

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 124 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 288 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 721 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 344 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 382 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 922 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 186 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

151
src/assets/logo-title.svg Normal file
View File

@@ -0,0 +1,151 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!-- Created with Inkscape (http://www.inkscape.org/) -->
<svg
class="logo-title"
width="21.129419mm"
height="9.3295746mm"
viewBox="0 0 74.868021 33.057548"
id="svg4376"
version="1.1"
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
sodipodi:docname="logo-title.svg"
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
xmlns="http://www.w3.org/2000/svg"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:dc="http://purl.org/dc/elements/1.1/">
<defs
id="defs4378" />
<sodipodi:namedview
id="base"
pagecolor="#000000"
bordercolor="#666666"
borderopacity="1.0"
inkscape:pageopacity="1"
inkscape:pageshadow="2"
inkscape:zoom="11.313709"
inkscape:cx="329.77692"
inkscape:cy="530.2417"
inkscape:document-units="px"
inkscape:current-layer="layer2"
showgrid="false"
inkscape:window-width="2546"
inkscape:window-height="1386"
inkscape:window-x="0"
inkscape:window-y="0"
inkscape:window-maximized="1"
inkscape:showpageshadow="true"
inkscape:pagecheckerboard="1"
inkscape:deskcolor="#505050"
inkscape:export-bgcolor="#ffffff00">
<inkscape:page
x="0"
y="0"
width="74.868019"
height="33.057549"
id="page2"
margin="0"
bleed="0" />
</sodipodi:namedview>
<metadata
id="metadata4381">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title />
</cc:Work>
</rdf:RDF>
</metadata>
<g
inkscape:label="Layer 1"
inkscape:groupmode="layer"
id="layer1"
transform="translate(-262.90298,-476.37904)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="279.60336"
y="517.86682"
id="text4159-4"
transform="scale(1.0283363,0.97244452)"
inkscape:export-xdpi="120.07"
inkscape:export-ydpi="120.07"
inkscape:export-filename="text4159-4.svg"><tspan
sodipodi:role="line"
x="279.60336"
y="517.86682"
style="font-size:15.425px;line-height:1.25"
id="tspan4167-2">Perren</tspan></text>
</g>
<g
inkscape:groupmode="layer"
id="layer2"
inkscape:label="Layer 2"
transform="translate(-262.90298,-476.37904)">
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="260.65689"
y="506.06656"
id="text3344-4-5"
inkscape:export-xdpi="120.07"
inkscape:export-ydpi="120.07"><tspan
sodipodi:role="line"
id="tspan3346-7-6"
x="260.65689"
y="506.06656"
style="font-size:40px;line-height:1.25">C</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="270.34253"
y="498.55893"
id="text3348-1-8"
transform="scale(1.0028223,0.99718564)"
inkscape:export-xdpi="120.07"
inkscape:export-ydpi="120.07"><tspan
sodipodi:role="line"
id="tspan3350-3-1"
x="270.34253"
y="498.55893"
style="font-size:16.283px;line-height:1.25">A</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="96.614212"
y="537.43079"
id="text3344-5-7-6"
transform="matrix(0.88187751,-0.35574603,0.36069903,0.98843971,0,0)"
inkscape:export-xdpi="120.07"
inkscape:export-ydpi="120.07"><tspan
sodipodi:role="line"
id="tspan3370-7-8"
x="96.614212"
y="537.43079"
style="font-size:14.587px;line-height:1.25">P</tspan></text>
<text
xml:space="preserve"
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
x="287.28516"
y="490.68661"
id="text4159"
inkscape:export-xdpi="120.07"
inkscape:export-ydpi="120.07"><tspan
sodipodi:role="line"
id="tspan4161"
x="287.28516"
y="490.68661"
style="font-size:15px;line-height:1.25">Corwin</tspan><tspan
sodipodi:role="line"
x="287.28516"
y="509.43661"
style="font-size:15px;line-height:1.25"
id="tspan4167"> </tspan></text>
</g>
</svg>

After

Width:  |  Height:  |  Size: 5.6 KiB

View File

@@ -0,0 +1,96 @@
---
import {Image} from 'astro:assets';
import type {carouselGroup} from "@interfaces/image-carousel.ts";
const groupToShow: carouselGroup = Astro.props.carouselGroup;
const limitByHeightClasses = "sm:max-w-xl md:max-w-3xl lg:max-w-5xl xl:max-w-7xl";
const limitByWidthClasses = "max-h-fit";
---
<custom-carousel class="flex flex-col relative w-full"
data-custom-carousel={groupToShow.animation}
data-custom-carousel-interval={groupToShow.interval}>
<!-- Carousel wrapper -->
<div class="relative overflow-hidden w-full h-56 md:h-120">
{
groupToShow.images.map((image, index) => (
<div class="hidden duration-700 ease-in-out" data-custom-carousel-item={index}>
<Image src={image}
class="absolute inset-0 m-auto h-full w-auto max-h-full max-w-full object-contain"
alt="..."
layout='constrained'
loading="eager"/>
</div>
))
}
</div>
{(groupToShow.images.length > 1) && (
<!-- Slider indicators -->
<div class="absolute z-30 flex -translate-x-1/2 bottom-2 left-1/2 space-x-3 rounded-full">
{
groupToShow.images.map((_, index) => (
<button type="button"
class="w-3 h-3 rounded-full bg-black hover:bg-caperren-green-light"
aria-current={index ? "false" : "true"}
aria-label={index.toString()}
data-custom-carousel-slide-to={index}></button>
))
}
</div>
<!-- Slider controls -->
<button
type="button"
class="absolute top-0 start-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
data-custom-carousel-prev
>
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full ring-2 ring-caperren-green/25 bg-black/25 group-hover:bg-black/75">
<svg
class="h-4 w-4 text-caperren-green group-hover:text-caperren-green-light"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="M5 1 1 5l4 4"
/>
</svg>
<span class="hidden">Previous</span>
</span>
</button>
<button
type="button"
class="absolute top-0 end-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
data-custom-carousel-next
>
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full ring-2 ring-caperren-green/25 bg-black/25 group-hover:bg-black/75">
<svg
class="h-4 w-4 text-caperren-green group-hover:text-caperren-green-light "
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 6 10"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 9 4-4-4-4"
/>
</svg>
<span class="hidden">Next</span>
</span>
</button>
)}
</custom-carousel>
<script src="./custom-carousel.ts"/>

View File

@@ -0,0 +1,80 @@
import {Carousel, type CarouselItem, type CarouselOptions, type IndicatorItem} from 'flowbite';
class CustomCarousel extends HTMLElement {
_slide: boolean;
_items: CarouselItem[];
_options: CarouselOptions;
_carousel: Carousel;
constructor() {
super();
this._slide = this.getAttribute('data-custom-carousel') === 'slide';
this._items = this._getItems();
this._options = this._getOptions();
this._carousel = new Carousel(this, this._items, this._options);
if (this._slide && this._items.length > 0) this._carousel.cycle();
window.addEventListener("load", this._attachHandlers);
}
_getItems = (): CarouselItem[] => {
let customItems = this.querySelectorAll('[data-custom-carousel-item]') || [];
return Array.from(customItems).map(
(item, index): CarouselItem => {
return {el: item as HTMLElement, position: index}
}
)
}
_getOptions = (): CarouselOptions => {
let customIndicators = this.querySelectorAll('[data-custom-carousel-slide-to]') || [];
return {
defaultPosition: 0,
interval: this.dataset.customCarouselInterval ? Number(this.dataset.customCarouselInterval) : 8000,
indicators: {
activeClasses: 'border-2 border-caperren-green bg-black',
inactiveClasses: 'bg-caperren-green/40 hover:bg-caperren-green-light',
items: Array.from(customIndicators).map(
(item, index): IndicatorItem => {
return {
el: item as HTMLElement,
position: Number(item.getAttribute('data-custom-carousel-slide-to'))
}
}
)
}
}
}
_attachHandlers = (): void => {
// Controls
const carouselNextEl = this.querySelector(
'[data-custom-carousel-next]'
);
const carouselPrevEl = this.querySelector(
'[data-custom-carousel-prev]'
);
if (carouselNextEl) {
carouselNextEl.addEventListener('click', () => {
this._carousel.next();
});
}
if (carouselPrevEl) {
carouselPrevEl.addEventListener('click', () => {
this._carousel.prev();
});
}
}
}
customElements.define('custom-carousel', CustomCarousel)

View File

@@ -0,0 +1,7 @@
---
---
<footer class="fixed bottom-0 left-0 z-20 w-full px-6 py-2 bg-black border-t border-t-caperren-green-dark text-caperren-green-dark text-sm flex items-center justify-between">
<span>{import.meta.env.PUBLIC_BUILD_ENVIRONMENT || "development"}</span>
<span>{import.meta.env.PUBLIC_REPO_VERSION_HASH || "invalid"}</span>
</footer>

View File

@@ -0,0 +1,34 @@
---
import NestedNavbarEntry from "@components/NestedNavbarEntries.astro";
import logo_title_large from "@assets/logo-title-large.png";
import {siteLayout} from "@data/site-layout.ts";
import {Image} from "astro:assets";
---
<nav class="border-b-4 border-b-caperren-green text-caperren-green">
<div class="flex flex-wrap items-center justify-between mx-auto p-6">
<a href="/">
<Image src={logo_title_large}
height="56"
width="139"
class="h-14 w-auto"
alt="logo title"
loading="eager"
/>
</a>
<button data-collapse-toggle="navbar-multi-level" type="button"
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm md:hidden focus:ring-2 focus:ring-caperren-green"
aria-controls="navbar-multi-level" aria-expanded="false">
<span class="sr-only">Open main menu</span>
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
d="M1 1h15M1 7h15M1 13h15"/>
</svg>
</button>
<div class="z-40 hidden w-full md:block md:w-auto" id="navbar-multi-level">
<NestedNavbarEntry items={siteLayout}/>
</div>
</div>
</nav>

View File

@@ -0,0 +1,47 @@
---
import type {navLink} from "@interfaces/site-layout.ts";
const items: navLink[] = Astro.props.items;
const depth: number = Astro.props.depth ?? 0;
const paths: string[] = Astro.props.paths ?? [];
const getNavLinkSuffix = (entry: navLink): string => {
return "-" + [...paths, entry.path].join("-")
}
const getHrefPath = (entry: navLink): string => {
return entry.pubpath ? entry.pubpath : ("/" + (paths && paths.length ? [...paths, entry.path].join("/") : entry.path));
}
---
<ul class={"flex flex-col p-4 bg-black border-caperren-green " + (depth ? "" : "md:flex-row md:space-x-8 md:mt-0")}>
{
items.map((entry, index) => (
<li >
{Array.isArray(entry.children) && entry.children.length ? (
<button id={"dropdownNavbarLink" + getNavLinkSuffix(entry)}
data-dropdown-toggle={"dropdownNavbar" + getNavLinkSuffix(entry)}
data-dropdown-placement="bottom"
class="flex items-center justify-between py-2 px-3 w-full hover:text-caperren-green-light md:hover:bg-transparent md:border-0 md:hover:text-caperren-green-light md:p-0 ">
{entry.navText}
<svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
fill="none" viewBox="0 0 10 6">
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
stroke-width="2"
d="m1 1 4 4 4-4"/>
</svg>
</button>
<div id={"dropdownNavbar" + getNavLinkSuffix(entry)}
class="z-10 hidden bg-black border border-caperren-green shadow-sm w-screen md:w-max">
<Astro.self items={entry.children} paths={[...paths, entry.path]} depth={depth + 1}/>
</div>
) : (
<a href={getHrefPath(entry)}
class="block py-2 px-3 bg-transparent hover:text-caperren-green-light ring-caperren-green-dark md:p-0"
aria-current="page">{entry.navText}</a>
)}
</li>
))}
</ul>

View File

@@ -0,0 +1,34 @@
---
import type {tableData} from "@interfaces/table.ts";
const data: tableData = Astro.props.data;
const columnPadding: number = data.columnPadding || 2;
const rowPadding: number = data.columnPadding || 2;
const paddingClasses: string = `px-${columnPadding} py-${rowPadding}`;
---
<div class="relative max-w-full overflow-x-auto">
<table class="w-full text-sm text-left">
<thead class="text-xs border-b-3 border-caperren-green uppercase bg-black">
<tr>
{data.header.map(headingText => (
<th scope="col" class={paddingClasses}>
{headingText}
</th>
))}
</tr>
</thead>
<tbody>
{data.rows.map(row => (
<tr class=" border-b dark:bg-black border-caperren-green">
{row.map(rowColumnText => (
<th scope="row"
class={paddingClasses + " font-medium whitespace-nowrap"}>
{rowColumnText}
</th>
))}
</tr>
))}
</tbody>
</table>
</div>

View File

@@ -0,0 +1,34 @@
---
import type {timelineEntry} from "@interfaces/timeline.ts";
const timeline: timelineEntry[] = Astro.props.timeline || [];
---
<custom-timeline>
<div class="relative z-10 grid gap-6 grid-flow-row sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 3xl:grid-cols-6"
data-timeline>
{timeline.map((entry, index) => (
<div class="pt-1 border bg-black border-caperren-green rounded-lg min-w-s max-w-s px-2 pb-2"
data-timeline-node-index={index}
>
<h3 class="text-lg font-bold">
{entry.event}
</h3>
<h4 class="font-semibold leading-none">
{entry.eventDetail}
</h4>
<time class="mb-2 mt-1 text-sm italic leading-none">
{entry.date}
</time>
{entry.description && (
<p class="text-sm font-normal">
{entry.description}
</p>
)}
</div>
))}
</div>
</custom-timeline>
<script src="./timeline.ts"/>

View File

@@ -0,0 +1,44 @@
import LeaderLine from "leader-line-new";
class CustomTimeline extends HTMLElement {
_eventElements: Element[];
constructor() {
super();
this._eventElements = this._getNodeElements();
window.addEventListener("load", this._paintLeaderLines);
}
_getNodeElements = (): Element[] =>
Array.from(this.querySelectorAll('[data-timeline-node-index]')).sort(
(elementA, elementB) =>
Number(elementA.getAttribute('data-timeline-node-index')) - Number(elementB.getAttribute('data-timeline-node-index'))
);
_paintLeaderLines = () => {
let pairs = this._eventElements.map((entry, index) => {
if (index < this._eventElements.length - 1) {
return [entry, this._eventElements[index + 1]];
}
}).filter(pair => pair !== undefined)
pairs.forEach(pair => {
new LeaderLine({
start: pair[0],
end: pair[1],
color: '#10ac25',
size: 3,
startSocket: "right",
endSocket: "left",
startPlug: "square",
endPlug: "arrow3"
});
});
}
}
customElements.define('custom-timeline', CustomTimeline)

View File

@@ -1,211 +0,0 @@
---
import astroLogo from '../assets/astro.svg';
import background from '../assets/background.svg';
---
<div id="container">
<!--<img id="background" src={background.src} alt="" fetchpriority="high" />-->
<main>
<h1>This is a test</h1>
<!--<section id="hero">-->
<!-- <a href="https://astro.build"-->
<!-- ><img src={astroLogo.src} width="115" height="48" alt="Astro Homepage" /></a-->
<!-- >-->
<!-- <h1>-->
<!-- To get started, open the <code><pre>src/pages</pre></code> directory in your project.-->
<!-- </h1>-->
<!-- <section id="links">-->
<!-- <a class="button" href="https://docs.astro.build">Read our docs</a>-->
<!-- <a href="https://astro.build/chat"-->
<!-- >Join our Discord <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 127.14 96.36"-->
<!-- ><path-->
<!-- fill="currentColor"-->
<!-- d="M107.7 8.07A105.15 105.15 0 0 0 81.47 0a72.06 72.06 0 0 0-3.36 6.83 97.68 97.68 0 0 0-29.11 0A72.37 72.37 0 0 0 45.64 0a105.89 105.89 0 0 0-26.25 8.09C2.79 32.65-1.71 56.6.54 80.21a105.73 105.73 0 0 0 32.17 16.15 77.7 77.7 0 0 0 6.89-11.11 68.42 68.42 0 0 1-10.85-5.18c.91-.66 1.8-1.34 2.66-2a75.57 75.57 0 0 0 64.32 0c.87.71 1.76 1.39 2.66 2a68.68 68.68 0 0 1-10.87 5.19 77 77 0 0 0 6.89 11.1 105.25 105.25 0 0 0 32.19-16.14c2.64-27.38-4.51-51.11-18.9-72.15ZM42.45 65.69C36.18 65.69 31 60 31 53s5-12.74 11.43-12.74S54 46 53.89 53s-5.05 12.69-11.44 12.69Zm42.24 0C78.41 65.69 73.25 60 73.25 53s5-12.74 11.44-12.74S96.23 46 96.12 53s-5.04 12.69-11.43 12.69Z"-->
<!-- ></path></svg-->
<!-- >-->
<!-- </a>-->
<!-- </section>-->
<!--</section>-->
</main>
<!--<a href="https://astro.build/blog/astro-5/" id="news" class="box">-->
<!-- <svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"-->
<!-- ><path-->
<!-- d="M24.667 12c1.333 1.414 2 3.192 2 5.334 0 4.62-4.934 5.7-7.334 12C18.444 28.567 18 27.456 18 26c0-4.642 6.667-7.053 6.667-14Zm-5.334-5.333c1.6 1.65 2.4 3.43 2.4 5.333 0 6.602-8.06 7.59-6.4 17.334C13.111 27.787 12 25.564 12 22.666c0-4.434 7.333-8 7.333-16Zm-6-5.333C15.111 3.555 16 5.556 16 7.333c0 8.333-11.333 10.962-5.333 22-3.488-.774-6-4-6-8 0-8.667 8.666-10 8.666-20Z"-->
<!-- fill="#111827"></path></svg-->
<!-- >-->
<!-- <h2>What's New in Astro 5.0?</h2>-->
<!-- <p>-->
<!-- From content layers to server islands, click to learn more about the new features and-->
<!-- improvements in Astro 5.0-->
<!-- </p>-->
<!--</a>-->
</div>
<style>
#background {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: -1;
filter: blur(100px);
}
#container {
font-family: Inter, Roboto, 'Helvetica Neue', 'Arial Nova', 'Nimbus Sans', Arial, sans-serif;
height: 100%;
}
main {
height: 100%;
display: flex;
justify-content: center;
}
#hero {
display: flex;
align-items: start;
flex-direction: column;
justify-content: center;
padding: 16px;
}
h1 {
font-size: 22px;
margin-top: 0.25em;
}
#links {
display: flex;
gap: 16px;
}
#links a {
display: flex;
align-items: center;
padding: 10px 12px;
color: #111827;
text-decoration: none;
transition: color 0.2s;
}
#links a:hover {
color: rgb(78, 80, 86);
}
#links a svg {
height: 1em;
margin-left: 8px;
}
#links a.button {
color: white;
background: linear-gradient(83.21deg, #3245ff 0%, #bc52ee 100%);
box-shadow:
inset 0 0 0 1px rgba(255, 255, 255, 0.12),
inset 0 -2px 0 rgba(0, 0, 0, 0.24);
border-radius: 10px;
}
#links a.button:hover {
color: rgb(230, 230, 230);
box-shadow: none;
}
pre {
font-family:
ui-monospace, 'Cascadia Code', 'Source Code Pro', Menlo, Consolas, 'DejaVu Sans Mono',
monospace;
font-weight: normal;
background: linear-gradient(14deg, #d83333 0%, #f041ff 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
margin: 0;
}
h2 {
margin: 0 0 1em;
font-weight: normal;
color: #111827;
font-size: 20px;
}
p {
color: #4b5563;
font-size: 16px;
line-height: 24px;
letter-spacing: -0.006em;
margin: 0;
}
code {
display: inline-block;
background:
linear-gradient(66.77deg, #f3cddd 0%, #f5cee7 100%) padding-box,
linear-gradient(155deg, #d83333 0%, #f041ff 18%, #f5cee7 45%) border-box;
border-radius: 8px;
border: 1px solid transparent;
padding: 6px 8px;
}
.box {
padding: 16px;
background: rgba(255, 255, 255, 1);
border-radius: 16px;
border: 1px solid white;
}
#news {
position: absolute;
bottom: 16px;
right: 16px;
max-width: 300px;
text-decoration: none;
transition: background 0.2s;
backdrop-filter: blur(50px);
}
#news:hover {
background: rgba(255, 255, 255, 0.55);
}
@media screen and (max-height: 368px) {
#news {
display: none;
}
}
@media screen and (max-width: 768px) {
#container {
display: flex;
flex-direction: column;
}
#hero {
display: block;
padding-top: 10%;
}
#links {
flex-wrap: wrap;
}
#links a.button {
padding: 14px 18px;
}
#news {
right: 16px;
left: 16px;
bottom: 2.5rem;
max-width: 100%;
}
h1 {
line-height: 1.5;
}
}
</style>

View File

@@ -1 +0,0 @@
/home/caperren/obsidian/00002 - Projects/Websites/caperren-com

174
src/data/site-layout.ts Normal file
View File

@@ -0,0 +1,174 @@
import type {navLink} from "@interfaces/site-layout.ts"
export const siteLayout: navLink[] = [
{navText: "About", path: ""},
{navText: "Education", path: "education"},
{
navText: "Experiences",
path: "experience",
children: [
{
navText: "SpaceX",
path: "spacex",
children: [
{
navText: "Hardware Test Engineer I/II",
path: "hardware-test-engineer-i-ii"
},
{
navText: "Avionics Test Engineering Internship",
path: "avionics-test-engineering-internship"
}
]
},
{
navText: "OSU CEOAS",
path: "osu-ceoas-ocean-mixing-group",
children: [
{
navText: "Robotics Oceanographic Surface Sampler",
path: "robotic-oceanographic-surface-sampler",
},
{
navText: "LeConte Glacier Deployments",
path: "leconte-glacier-deployments",
}
]
},
{
navText: "OSU SARL",
path: "osu-sinnhuber-aquatic-research-laboratory",
children: [
{
navText: "Team Lead",
path: "team-lead",
},
{
navText: "Zebrafish Embryo Pick and Plate",
path: "zebrafish-embryo-pick-and-plate",
},
{
navText: "Shuttlebox Behavior System",
path: "shuttlebox-behavior-system",
},
{
navText: "Dechorionator",
path: "dechorionator",
},
{
navText: "Denso Embryo Pick and Plate",
path: "denso-embryo-pick-and-plate",
},
{
navText: "ZScan Processor",
path: "zscan-processor",
}
]
},
{
navText: "OSU Robotics Club",
path: "osu-robotics-club",
children: [
{
navText: "Mars Rover Software Team Lead",
path: "mars-rover-software-team-lead",
},
{
navText: "Mars Rover Emergency Software Team Lead",
path: "mars-rover-emergency-software-team-lead",
},
{
navText: "Mars Rover Electrical Team Lead",
path: "mars-rover-electrical-team-lead",
},
{
navText: "Club Officer",
path: "club-officer",
}
]
},
]
},
{
navText: "Hobbies",
path: "hobby",
children: [
{
navText: "Homelab", path: "homelab",
children: [
{navText: "Home Server Rack", path: "home-server-rack"},
{navText: "Offsite Backup Rack", path: "offsite-backup-rack"},
{navText: "Kubernetes Cluster", path: "kubernetes-cluster"},
{navText: "Home Automation", path: "home-automation"},
]
},
{
navText: "Motorcycling",
path: "motorcycling",
children: [
{navText: "Lineup", path: "lineup"},
{
navText: "Custom Accessories",
path: "custom-accessories",
children: [
{navText: "Chubby Buttons 2 Mount", path: "chubby-buttons-2-mount"},
]
},
// {
// title: "Trips",
// path: "trips",
// children: [
// {title: "2025-08 | Alaska ", path: "2025-08-alaska"},
// {title: "2024-10 | Norway ", path: "2024-10-norway"}
// ]
// },
]
},
// {
// title: "Projects", path: "projects",
// children: [
// {title: "Shed Solar", path: "shed-solar"},
// {title: "OSSM Overkill Edition", path: "ossm-overkill-edition"},
// ]
// },
{navText: "NixOS", path: "nixos"},
{navText: "Body Mods", path: "body-mods"},
]
},
{
navText: "Resumes",
path: "resume",
children: [
// {title: "2025-11-10 | Complete CV", path: "2025-11-10-complete-cv"},
{navText: "2025-11-10 | Infrastructure Engineer", path: "2025-11-10-infrastructure-engineer"},
{navText: "2019-07-01 | Hardware Test Engineer", path: "2019-07-01-hardware-test-engineer"},
]
},
{navText: "Github", pubpath: "https://github.com/caperren"},
{navText: "LinkedIn", pubpath: "https://www.linkedin.com/in/caperren/"}
]
export const pathToMetadata = (path: string): navLink => {
const paths = path.split("/").filter((entry) => entry);
let currentEntries: navLink[] = siteLayout;
let foundEntry: navLink | undefined;
for (const path of paths) {
for (const currentEntry of currentEntries) {
if (currentEntry.path === path) {
foundEntry = currentEntry;
if (foundEntry.children && foundEntry.children.length > 0) {
currentEntries = foundEntry.children;
}
}
}
}
if (foundEntry === undefined) {
throw new Error(`${path} not found in site layout!`);
}
return foundEntry;
}

8
src/env.d.ts vendored Normal file
View File

@@ -0,0 +1,8 @@
interface ImportMetaEnv {
readonly PUBLIC_REPO_VERSION_HASH: string;
readonly PUBLIC_BUILD_ENVIRONMENT: string;
}
interface ImportMeta {
readonly env: ImportMetaEnv;
}

View File

@@ -0,0 +1,13 @@
import { DateTime } from 'luxon';
export interface experience {
}
export interface subExperience {
name: string;
description: string;
startDate: DateTime;
endDate?: DateTime;
}

View File

@@ -0,0 +1,5 @@
export interface carouselGroup {
animation: "static" | "slide";
interval?: number;
images: ImageMetadata[];
}

View File

@@ -0,0 +1,6 @@
export interface navLink {
navText: string;
path?: string;
pubpath?: string;
children?: navLink[];
}

6
src/interfaces/table.ts Normal file
View File

@@ -0,0 +1,6 @@
export interface tableData {
header: string[];
columnPadding?: number;
rowPadding?: number;
rows: Array<Array<any>>;
}

View File

@@ -0,0 +1,6 @@
export interface timelineEntry {
event: string;
eventDetail?: string
date: string;
description?: string;
}

View File

@@ -1,15 +1,29 @@
---
const pageTitle = Astro.props.title || "Corwin Perren";
import '@styles/global.css'
import Navbar from '@components/Navbar.astro';
import Footer from '@components/Footer.astro';
const pageTitle = Astro.props.title ? `${Astro.props.title} - Corwin Perren` : "Corwin Perren";
---
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="viewport" content="width=device-width" />
<meta name="generator" content={Astro.generator} />
<meta charset="UTF-8"/>
<link rel="icon" href="/favicon-solid.png" type="image/png"/>
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<title>{pageTitle}</title>
</head>
<body class="container mx-auto">
<body class="bg-black text-white">
<Navbar/>
<main class="mx-6 mt-6 mb-14">
{(Astro.props.title) && (
<h1 class="font-extrabold md:text-3xl md:mb-6">{Astro.props.title}</h1>
)}
<slot/>
</main>
<Footer/>
<script src="../scripts/main.ts"></script>
</body>
</html>

View File

@@ -0,0 +1,6 @@
---
import BaseLayout from './BaseLayout.astro';
---
<BaseLayout title={Astro.props.title}>
<slot/>
</BaseLayout>

View File

@@ -0,0 +1,6 @@
---
import BaseLayout from './BaseLayout.astro';
---
<BaseLayout title={Astro.props.title}>
<slot/>
</BaseLayout>

View File

@@ -1,22 +0,0 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} />
<title>Corwin Perren</title>
</head>
<body>
<slot />
</body>
</html>
<style>
html,
body {
margin: 0;
width: 100%;
height: 100%;
}
</style>

View File

@@ -0,0 +1,10 @@
---
import BaseLayout from './BaseLayout.astro';
const resume = Astro.props.resume;
---
<BaseLayout>
<div class="h-dvh">
<iframe src={resume} class="mx-auto w-9/10 md:w-3/5 h-7/10 md:h-4/5"/>
</div>
</BaseLayout>

117
src/pages/education.astro Normal file
View File

@@ -0,0 +1,117 @@
---
import BaseLayout from "@layouts/BaseLayout.astro";
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
import Timeline from "@components/Timeline/Timeline.astro";
import Table from "@components/Table.astro";
import type {carouselGroup} from "@interfaces/image-carousel.ts";
import type {tableData} from "@interfaces/table.ts";
import type {timelineEntry} from "@interfaces/timeline.ts";
import fhhs_diploma from "@assets/education/fhhs-diploma.jpg";
import osu_bs_cs_diploma from "@assets/education/osu-bs-cs-diploma.jpg"
const diplomaCarouselGroup: carouselGroup = {
animation: "slide",
images: [
fhhs_diploma,
osu_bs_cs_diploma
]
}
const timeline: timelineEntry[] = [
{
event: "High School Diploma",
eventDetail: "Friday Harbor High School",
date: "June 2011"
},
{
event: "B.S. Computer Science",
eventDetail: "Oregon State University",
date: "June 2019"
},
];
const courseTable: tableData = {
header: ["Program", "Course", "Description"],
rows: [
["CS", "LDT", "INTRO TO LINUX"],
["CS", "261", "DATA STRUCTURES"],
["CS", "271", "COMPUTER ARCH & ASSEM LANGUAGE"],
["CS", "290", "WEB DEVELOPMENT"],
["CS", "312", "SYSTEM ADMINISTRATION"],
["CS", "325", "ANALYSIS OF ALGORITHMS"],
["CS", "331", "INTRO ARTIFICIAL INTELLIGENCE"],
["CS", "340", "INTRODUCTION TO DATABASES"],
["CS", "344", "OPERATING SYSTEMS I"],
["CS", "352", "INTRO TO USABILITY ENGINEERING"],
["CS", "361", "SOFTWARE ENGINEERING I"],
["CS", "362", "SOFTWARE ENGINEERING II"],
["CS", "370", "INTRODUCTION TO SECURITY"],
["CS", "372", "INTRO TO COMPUTER NETWORKS"],
["CS", "381", "PROGRAMMING LANGUAGE FUND"],
["CS", "391", "SOC & ETHICAL ISSUES IN COMSC"],
["CS", "444", "OPERATING SYSTEMS II"],
["CS", "461", "SENIOR SOFTWARE ENGR PROJECT I"],
["CS", "462", "SENIOR SOFTWARE ENGR PROJECT II"],
["CS", "463", "SENIOR SOFTWARE ENGR PROJECT III"],
["CS", "464", "OPEN SOURCE SOFTWARE"],
["CS", "468", "INCLUSIVE DESIGN (HCI)"],
["CS", "496", "MOBILE/CLOUD SOFTWARE DEVEL"],
["ECE", "111", "INTRODUCTION TO ECE: TOOLS"],
["ECE", "112", "INTRODUCTION TO ECE: CONCEPTS"],
["ECE", "151", "PROGRAMMING I/EMBED CONTR LAB"],
["ECE", "152", "PROGRAMMING II/EMBED CONTR LAB"],
["ECE", "271", "DIGITAL LOGIC DESIGN"],
["ECE", "272", "DIGITAL LOGIC DESIGN LAB"],
["ECE", "375", "COMPUTER ORG & ASSEMBLY LANG"],
["ENGR", "201", "ELECTRICAL FUNDAMENTALS I"],
["ENGR", "202", "ELECTRICAL FUNDAMENTALS II"],
["ENGR", "391", "ENGINEERING ECON & PROJ MGMT"],
["ROB", "421", "APPLIED ROBOTICS"],
["ROB", "456", "INTELLIGENT ROBOTS"],
["MTH", "231", "ELEMENTS DISCRETE MATH"],
["MTH", "241", "CALC FOR MGT & SOCIAL SCI"],
["MTH", "251", "DIFFERENTIAL CALCULUS"],
["MTH", "252", "INTEGRAL CALCULUS"],
["MTH", "254", "VECTOR CALCULUS I"],
["MTH", "306", "MATRIX & POWER SERIES METHODS"],
["ST", "314", "INTRO TO STATS FOR ENGINEERS"],
["PH", "211", "GENERAL PHYSICS WITH CALCULUS I"],
["PH", "212", "GENERAL PHYSICS WITH CALCULUS II"],
["PH", "213", "GENERAL PHYSICS WITH CALCULUS III"],
["WR", "LDT", "COMPOSITION III"],
["WR", "LDT", "CREATIVE WRITING"],
["WR", "121", "ENGLISH COMPOSITION"],
["WR", "214", "WRITING IN BUSINESS"],
["WR", "327", "TECHNICAL WRITING"],
["BI", "102", "GENERAL BIOLOGY"],
["BI", "349", "BIODIVERSITY: CAUSES, CONSERV"],
["CH", "201", "CHEMISTRY FOR ENGINEERING MAJ"],
["CH", "211", "RECITATION FOR CHEMISTRY 201"],
["COMM", "114", "ARGUMENT & CRITICAL DISCOURSE"],
["GEO", "LDT", "SURVEY EARTH SCIENCE"],
["HDFS", "240", "HUMAN SEXUALITY"],
["HHS", "231", "LIFETIME FITNESS FOR HEALTH"],
["HHS", "246", "LIFETIME FITNESS: WALKING"],
["MUS", "LDT", "LA: MUSIC APPRECIATION"],
["MUS", "108", "MUSIC CULTURES/ NAT AM FLUTE"],
["PAC", "123", "BOWLING I"],
["PHL", "205", "ETHICS"],
["PS", "LDT", "AMERICAN GOVT"],
["PSY", "201", "GENERAL PSYCHOLOGY"],
["QS", "262", "INTRODUCTION TO QUEER STUDIES"]
]
};
---
<BaseLayout title="Education">
<Carousel carouselGroup={diplomaCarouselGroup}/>
<h2 class="font-bold md:text-2xl my-4 underline">Timeline</h2>
<Timeline timeline={timeline}/>
<h2 class="font-bold md:text-2xl my-4 underline">Oregon State University</h2>
<a class="font-bold md:text-lg my-4 text-blue-500 underline hover:text-blue-300"
href="https://github.com/caperren/school_archives/tree/master/OSU%20Coursework">Coursework Archives</a>
<h3 class="font-bold md:text-lg my-4 underline">Course Listing</h3>
<Table data={courseTable}/>
</BaseLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="CEOAS - LeConte Glacier Deployments">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="CEOAS - Robotic Oceanographic Surface Sampler">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="OSURC - Officer">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="OSURC - Electrical Team Lead">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="OSURC - Emergency Software Team Lead">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="OSURC - Software Team Lead">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="SARL - Dechorionator">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="SARL - Denso Embryo Pick and Plate">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="SARL - Shuttlebox Behavior System">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="SARL - Team Lead">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="SARL - Zebrafish Embryo Pick and Plate">
</ExperienceLayout>

View File

@@ -0,0 +1,6 @@
---
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
---
<ExperienceLayout title="SARL - ZScan Processor">
</ExperienceLayout>

View File

@@ -0,0 +1,66 @@
---
import ExperienceLayout from '@layouts/ExperienceLayout.astro';
import Timeline from '@components/Timeline/Timeline.astro';
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
import spring_2019_interns from "@assets/experience/spacex/avionics-test-engineering-internship/spring-2019-interns.jpg";
import type {carouselGroup} from "@interfaces/image-carousel.ts";
import type {timelineEntry} from "@interfaces/timeline.ts";
const headerCarouselGroup: carouselGroup = {
animation: "slide",
images: [
spring_2019_interns
]
}
const timeline: timelineEntry[] = [
{
event: "Started",
date: "January 2019",
},
{
event: "Finished",
date: "March 2019",
}
];
---
<ExperienceLayout title="SpaceX - Avionics Test Engineering Internship">
<Carousel carouselGroup={headerCarouselGroup}/>
<h2 class="font-bold md:text-2xl my-4">Summary</h2>
<h3 class="font-bold md:text-lg my-4">Timeline</h3>
<Timeline timeline={timeline}/>
<h3 class="font-bold md:text-lg my-4">Key Takeaways</h3>
<ul class="list-disc list-inside">
<li></li>
</ul>
<h3 class="font-bold md:text-lg my-4">Skills Used</h3>
<div class="relative grid gap-6 grid-flow-row sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 border-caperren-green">
<div>
<div class="font-extrabold text-sm">Software</div>
<hr class="text-caperren-green"/>
<ul class="list-disc list-inside text-sm">
<li>Python</li>
<li>Test Driven Development</li>
</ul>
</div>
<div>
<div class="font-extrabold text-sm">Electrical</div>
<hr class="text-caperren-green"/>
</div>
<div>
<div class="font-extrabold text-sm">Other</div>
<hr class="text-caperren-green"/>
</div>
</div>
<h2 class="font-bold md:text-2xl my-4">Details</h2>
Though I did get to work on some really fun projects during my internship at SpaceX, I unfortunately cant go into much detail due to NDAs and ITAR restrictions. What I can say is that I mainly wrote Python for a new avionics hardware test system. My experience with writing Python in the numerous other projects Ive done really helped me out here, as the framework SpaceX has created was quite complex and would otherwise have been fairly difficult to write code for. I also wrote a simple tool for automating the creation of Jira work tickets so that the two teams that ended up using it wouldnt have to have their members manually creating dozens of them as work and issues came in through a separate system.
I was also quite happy in that I got to perform some circuit debugging on avionics test system hardware, both for my project and for a separate test system. A final experience I had here was getting to work directly with the head engineer from a company that supplied a piece of test hardware I was interfacing with. It was quite incredible to see just how much weight a SpaceX email address had when trying to solve problems I had found with the hardware. Not only were they responsive, but in fact were willing to fast-track firmware updates for us to get things working. Coming from clubs and small labs where a support email might not even get a response for months, it was quite a refreshing experience.
</ExperienceLayout>

View File

@@ -0,0 +1,87 @@
---
import ExperienceLayout from '@layouts/ExperienceLayout.astro';
import Timeline from '@components/Timeline/Timeline.astro';
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
import starlink_headquarters_selfie
from "@assets/experience/spacex/hardware-test-engineer-i-ii/starlink-headquarters-selfie.jpg";
import type {carouselGroup} from "@interfaces/image-carousel.ts";
import type {timelineEntry} from "@interfaces/timeline.ts";
const headerCarouselGroup: carouselGroup = {
animation: "slide",
images: [
starlink_headquarters_selfie
]
}
const timeline: timelineEntry[] = [
{
event: "Started",
eventDetail: "Satellite Hardware Test Team",
date: "September 2019",
description: "Owned test systems for four generations of Starlink flight computers and two generations of power boards"
},
{
event: "Transitioned To Remote",
eventDetail: "Moved To Oregon",
date: "August 2022",
description: "Personal decision, but I was allowed to work on tools for the build reliability engineering team"
},
{
event: "Changed Teams",
eventDetail: "Components Test Infra Team",
date: "March 2024 - VERIFY",
description: "Vertical move that allowed for broader application of my skills"
},
{
event: "Finished",
eventDetail: "Thanks for all the fish!",
date: "April 2025",
description: "Celebrated five and a half years of helping put thousands of satellites, and dozens of rockets, into orbit"
}
];
---
<ExperienceLayout title="SpaceX - Hardware Test Engineer I/II">
<Carousel carouselGroup={headerCarouselGroup}/>
<h2 class="font-bold md:text-2xl my-4">Summary</h2>
<h3 class="font-bold md:text-lg my-4">Timeline</h3>
<Timeline timeline={timeline}/>
<h3 class="font-bold md:text-lg my-4">Key Takeaways</h3>
<ul class="list-disc list-inside">
<li>Created test systems which validated ~4500 Starlink satellite flight computers, and ~4000 power boards</li>
<li>Developed program-critical infrastructure that enabled efficient triage, management, and tracking of hardware failures</li>
<li>Designed and deployed automated, unified, and containerized infrastructure to greatly increase application reliability and development speed </li>
</ul>
<h3 class="font-bold md:text-lg my-4">Skills Used</h3>
<div class="relative grid gap-6 grid-flow-row sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 border-caperren-green">
<div>
<div class="font-extrabold text-sm">Software</div>
<hr class="text-caperren-green"/>
<ul class="list-disc list-inside text-sm">
<li>Python</li>
<li>Test Driven Development</li>
</ul>
</div>
<div>
<div class="font-extrabold text-sm">Electrical</div>
<hr class="text-caperren-green"/>
</div>
<div>
<div class="font-extrabold text-sm">Mechanical</div>
<hr class="text-caperren-green"/>
</div>
<div>
<div class="font-extrabold text-sm">Other</div>
<hr class="text-caperren-green"/>
</div>
</div>
<h2 class="font-bold md:text-2xl my-4">Details By Team</h2>
<h3 class="font-bold md:text-lg my-4">Starlink Hardware Test</h3>
<h3 class="font-bold md:text-lg my-4">Build Reliability Engineering</h3>
<h3 class="font-bold md:text-lg my-4">Components Test Infrastructure</h3>
</ExperienceLayout>

View File

@@ -1,17 +0,0 @@
---
import { getCollection, render } from 'astro:content';
// 1. Generate a new path for every collection entry
export async function getStaticPaths() {
const hobbies = await getCollection('hobbies');
return hobbies.map(hobby => ({
params: { id: hobby.id },
props: { post: hobby },
}));
}
// 2. For your template, you can get the entry directly from the prop
const { hobby } = Astro.props;
const { Content } = await render(hobby);
---
<h1>{hobby.id}</h1>
<Content />

View File

@@ -0,0 +1,28 @@
---
import HobbyLayout from "@layouts/HobbyLayout.astro";
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
import type {carouselGroup} from "@interfaces/image-carousel.ts";
import injection_site from "@assets/hobby/body-mods/rfid-implant/injection-site.jpg";
import injector_exploded from "@assets/hobby/body-mods/rfid-implant/injector-exploded.jpg";
import quarter_euro_transponder from "@assets/hobby/body-mods/rfid-implant/quarter-euro-transponder.png";
import xem_pouch from "@assets/hobby/body-mods/rfid-implant/xem-pouch.jpg";
const rfidImplantCarouselGroup: carouselGroup = {
animation: "slide",
images: [
xem_pouch,
injector_exploded,
quarter_euro_transponder,
injection_site
]
}
---
<HobbyLayout title="Body Mods">
<h2 class="font-bold md:text-2xl my-4 underline">RFID Implant</h2>
<Carousel carouselGroup={rfidImplantCarouselGroup}/>
</HobbyLayout>

View File

@@ -0,0 +1,6 @@
---
import HobbyLayout from "@layouts/HobbyLayout.astro";
---
<HobbyLayout title="Homelab - Home Automation">
</HobbyLayout>

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