Started refactoring, added prettier and checks and reformatted project, added cspell and checks and custom project words, beginning of robotic oceanographic surface sampler content
This commit is contained in:
@@ -1,7 +1,17 @@
|
||||
.DS_Store
|
||||
|
||||
.idea
|
||||
.astro
|
||||
.gitea/
|
||||
.astro/
|
||||
.idea/
|
||||
|
||||
*/dist/
|
||||
*/build/
|
||||
*/node_modules/
|
||||
*/playwright-report/
|
||||
*/test-results/
|
||||
|
||||
.gitignore
|
||||
Dockerfile
|
||||
Makefile
|
||||
new-words.txt
|
||||
README.md
|
||||
@@ -1,9 +1,9 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
branches: [main, master]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Build and Test - Production
|
||||
on:
|
||||
push:
|
||||
branches: [ main ]
|
||||
branches: [main]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -22,6 +22,12 @@ jobs:
|
||||
npm ci
|
||||
npx playwright install --with-deps
|
||||
|
||||
- name: Code Formatting Check
|
||||
run: npx prettier . --check
|
||||
|
||||
- name: Spelling Check
|
||||
run: npx cspell .
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Build and Test - Staging
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened ]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -24,6 +24,12 @@ jobs:
|
||||
npm ci
|
||||
npx playwright install --with-deps
|
||||
|
||||
- name: Code Formatting Check
|
||||
run: npx prettier . --check
|
||||
|
||||
- name: Spelling Check
|
||||
run: npx cspell .
|
||||
|
||||
- name: Build Project
|
||||
run: npm run build
|
||||
|
||||
|
||||
9
.gitignore
vendored
9
.gitignore
vendored
@@ -1,9 +1,3 @@
|
||||
# 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/
|
||||
|
||||
@@ -35,3 +29,6 @@ pnpm-debug.log*
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/playwright/.auth/
|
||||
|
||||
# Local temporary storage files
|
||||
new-words.txt
|
||||
|
||||
34
.prettierignore
Normal file
34
.prettierignore
Normal file
@@ -0,0 +1,34 @@
|
||||
# build output
|
||||
dist/
|
||||
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
# dependencies
|
||||
node_modules/
|
||||
|
||||
# logs
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
|
||||
# environment variables
|
||||
.env
|
||||
.env.production
|
||||
|
||||
# macOS-specific files
|
||||
.DS_Store
|
||||
|
||||
# jetbrains setting folder
|
||||
.idea/
|
||||
|
||||
# Playwright
|
||||
/test-results/
|
||||
/playwright-report/
|
||||
/blob-report/
|
||||
/playwright/.cache/
|
||||
/playwright/.auth/
|
||||
|
||||
# Local temporary storage files
|
||||
new-words.txt
|
||||
11
.prettierrc
Normal file
11
.prettierrc
Normal file
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"plugins": ["prettier-plugin-astro", "prettier-plugin-tailwindcss"],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.astro",
|
||||
"options": {
|
||||
"parser": "astro"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
27
Dockerfile
27
Dockerfile
@@ -5,12 +5,27 @@ WORKDIR /app
|
||||
# Therefore, the `-deps` steps will be skipped if only the source code changes.
|
||||
COPY package.json package-lock.json tsconfig.json astro.config.mjs ./
|
||||
|
||||
CMD [ "/bin/bash" ]
|
||||
|
||||
FROM base AS prod-deps
|
||||
RUN npm install --omit=dev
|
||||
|
||||
FROM prod-deps AS test-base
|
||||
|
||||
RUN npm ci
|
||||
RUN npx playwright install --with-deps
|
||||
|
||||
FROM prod-deps AS build
|
||||
|
||||
COPY . .
|
||||
COPY --exclude=test \
|
||||
--exclude=test-e2e \
|
||||
--exclude=playwright.config.ts \
|
||||
--exclude=vitest.config.ts \
|
||||
--exclude=.prettierrc \
|
||||
--exclude=.prettierignore \
|
||||
--exclude=cspell.json \
|
||||
--exclude=project-words.txt \
|
||||
. .
|
||||
|
||||
ARG REPO_VERSION_HASH
|
||||
ARG BUILD_ENVIRONMENT
|
||||
@@ -19,6 +34,16 @@ RUN echo "PUBLIC_REPO_VERSION_HASH=\"${REPO_VERSION_HASH}\" \n\
|
||||
PUBLIC_BUILD_ENVIRONMENT=\"${BUILD_ENVIRONMENT}\"" >> .env
|
||||
RUN npm run build
|
||||
|
||||
FROM test-base AS test
|
||||
|
||||
COPY . .
|
||||
COPY --from=build /app/dist /app/dist
|
||||
|
||||
RUN npx prettier . --check
|
||||
RUN npx cspell .
|
||||
RUN npm run test
|
||||
RUN npm run e2e-test
|
||||
|
||||
FROM nginx:alpine AS runtime
|
||||
|
||||
COPY ./nginx/nginx.conf /etc/nginx/nginx.conf
|
||||
|
||||
36
Makefile
36
Makefile
@@ -6,7 +6,14 @@
|
||||
astro_upgrade \
|
||||
build \
|
||||
dev \
|
||||
dev-hosted
|
||||
dev-hosted \
|
||||
test \
|
||||
_spelling-generate-new-words \
|
||||
spelling-find-new-words \
|
||||
spelling-add-new-words \
|
||||
spelling-check \
|
||||
cleanup-check \
|
||||
cleanup-code
|
||||
|
||||
default: dev
|
||||
|
||||
@@ -28,3 +35,30 @@ dev:
|
||||
dev-hosted:
|
||||
npm run dev-hosted
|
||||
|
||||
test: spelling-check
|
||||
@npx playwright install --with-deps
|
||||
npm run test --ui
|
||||
npx playwright test --ui
|
||||
|
||||
_spelling-generate-new-words:
|
||||
@cspell --words-only --unique . 2>/dev/null | sort --ignore-case -o new-words.txt
|
||||
|
||||
spelling-find-new-words: _spelling-generate-new-words
|
||||
@echo "Found the following new words:"
|
||||
@cat new-words.txt
|
||||
@rm -f new-words.txt
|
||||
|
||||
spelling-add-new-words: _spelling-generate-new-words
|
||||
@echo "Adding to project-words.txt"
|
||||
@cat new-words.txt >> project-words.txt
|
||||
@rm -f new-words.txt
|
||||
@cat project-words.txt | sort --ignore-case -o project-words.txt
|
||||
|
||||
spelling-check:
|
||||
npx cspell .
|
||||
|
||||
cleanup-check:
|
||||
npx prettier . --check
|
||||
|
||||
cleanup-code:
|
||||
npx prettier . --write
|
||||
@@ -1,3 +1,3 @@
|
||||
# Corwin Perren's Personal Portfolio Website
|
||||
|
||||
Check the Makfile and/or package.json for the commands needed to build and run this project.
|
||||
Check the Makefile and/or package.json for the commands needed to build and run this project.
|
||||
|
||||
@@ -1,31 +1,31 @@
|
||||
// @ts-check
|
||||
import {defineConfig} from 'astro/config';
|
||||
import { defineConfig } from "astro/config";
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
|
||||
// We don't have access to short imports this early in the build chain
|
||||
// noinspection ES6PreferShortImport
|
||||
import {siteLayout, getPaths} from "./src/data/site-layout.ts";
|
||||
import { siteLayout, getPaths } from "./src/data/site-layout.ts";
|
||||
|
||||
const disabledPaths = getPaths(siteLayout, [], true)
|
||||
const disabledPaths = getPaths(siteLayout, [], true);
|
||||
|
||||
// https://astro.build/config
|
||||
|
||||
export default defineConfig({
|
||||
site: "https://caperren.com",
|
||||
prefetch: {
|
||||
prefetchAll: true
|
||||
},
|
||||
integrations: [
|
||||
sitemap({
|
||||
filter: (pagePath) =>
|
||||
!disabledPaths.some(disabledPath => pagePath.includes(disabledPath))
|
||||
})
|
||||
site: "https://caperren.com",
|
||||
prefetch: {
|
||||
prefetchAll: true,
|
||||
},
|
||||
integrations: [
|
||||
sitemap({
|
||||
filter: (pagePath) =>
|
||||
!disabledPaths.some((disabledPath) => pagePath.includes(disabledPath)),
|
||||
}),
|
||||
],
|
||||
vite: {
|
||||
plugins: [
|
||||
// @ts-ignore
|
||||
tailwindcss(),
|
||||
],
|
||||
vite: {
|
||||
plugins: [
|
||||
// @ts-ignore
|
||||
tailwindcss()
|
||||
],
|
||||
},
|
||||
},
|
||||
});
|
||||
23
cspell.json
Normal file
23
cspell.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"$schema": "https://raw.githubusercontent.com/streetsidesoftware/cspell/main/cspell.schema.json",
|
||||
"version": "0.2",
|
||||
"dictionaryDefinitions": [
|
||||
{
|
||||
"name": "project-words",
|
||||
"path": "./project-words.txt",
|
||||
"addWords": true
|
||||
}
|
||||
],
|
||||
"dictionaries": ["project-words"],
|
||||
"ignorePaths": [
|
||||
".astro",
|
||||
".idea",
|
||||
"dist",
|
||||
"node_modules",
|
||||
"playwright-report",
|
||||
"test-results",
|
||||
"new-words.txt",
|
||||
"playwright.config.ts",
|
||||
"/project-words.txt"
|
||||
]
|
||||
}
|
||||
1091
package-lock.json
generated
1091
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,7 @@
|
||||
"@astrojs/sitemap": "^3.6.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"astro": "^5.16.3",
|
||||
"cspell": "^9.3.2",
|
||||
"flowbite": "^3.1.2",
|
||||
"leader-line-new": "^1.1.9",
|
||||
"luxon": "^3.7.2",
|
||||
@@ -25,6 +26,9 @@
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@types/luxon": "^3.7.1",
|
||||
"@types/node": "^24.10.0",
|
||||
"prettier": "3.7.3",
|
||||
"prettier-plugin-astro": "0.14.1",
|
||||
"prettier-plugin-tailwindcss": "0.7.1",
|
||||
"vitest": "^4.0.7"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import {defineConfig, devices} from '@playwright/test';
|
||||
import { defineConfig, devices } from "@playwright/test";
|
||||
|
||||
/**
|
||||
* Read environment variables from file.
|
||||
@@ -12,69 +12,69 @@ import {defineConfig, devices} from '@playwright/test';
|
||||
* See https://playwright.dev/docs/test-configuration.
|
||||
*/
|
||||
export default defineConfig({
|
||||
testDir: './test-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',
|
||||
testDir: "./test-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',
|
||||
/* 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"] },
|
||||
},
|
||||
|
||||
/* 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 preview',
|
||||
url: 'http://localhost:4321',
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
{
|
||||
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 preview",
|
||||
url: "http://localhost:4321",
|
||||
timeout: 120 * 1000,
|
||||
reuseExistingServer: !process.env.CI,
|
||||
},
|
||||
});
|
||||
|
||||
41
project-words.txt
Normal file
41
project-words.txt
Normal file
@@ -0,0 +1,41 @@
|
||||
ASSEM
|
||||
astrojs
|
||||
Candian
|
||||
caperren
|
||||
CEOAS
|
||||
COMSC
|
||||
Concours
|
||||
CONSERV
|
||||
Corwin
|
||||
dangerousthings
|
||||
Dechorionator
|
||||
fhhs
|
||||
flowbite
|
||||
HDFS
|
||||
headshot
|
||||
Homelab
|
||||
ITAR
|
||||
Jetson
|
||||
leconte
|
||||
Loctite
|
||||
luxon
|
||||
MGMT
|
||||
nixos
|
||||
Onshape
|
||||
OSSM
|
||||
OSURC
|
||||
Perren
|
||||
Perren's
|
||||
pubpath
|
||||
RFID
|
||||
RSSI
|
||||
SARL
|
||||
Shuttlebox
|
||||
sinnhuber
|
||||
sitemapindex
|
||||
ssds
|
||||
Starlink
|
||||
Unstow
|
||||
vitest
|
||||
Zebrafish
|
||||
zscan
|
||||
Binary file not shown.
@@ -1,117 +0,0 @@
|
||||
---
|
||||
import {Image} from 'astro:assets';
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
|
||||
const groupToShow: carouselGroup = Astro.props.carouselGroup;
|
||||
---
|
||||
|
||||
<custom-carousel class="flex flex-col relative w-full"
|
||||
data-custom-carousel={groupToShow.animation}
|
||||
data-custom-carousel-interval={groupToShow.interval}>
|
||||
<!-- Modal for fullscreen viewing -->
|
||||
<div tabindex="-1" aria-hidden="true"
|
||||
class="hidden fixed z-100 justify-center items-center w-full inset-0"
|
||||
data-custom-carousel-modal>
|
||||
<div class="relative p-4 w-full h-full">
|
||||
<!-- Modal content -->
|
||||
<div class="relative h-full max-h-screen max-w-screen bg-black rounded-lg shadow-sm border-2 border-caperren-green">
|
||||
<!-- Modal header -->
|
||||
<div class="flex items-center justify-between p-1 rounded-t">
|
||||
<button type="button"
|
||||
class="z-100 text-caperren-green bg-transparent ring-2 ring-caperren-green-dark hover:ring-caperren-green-light hover:text-caperren-green-light rounded-lg text-sm size-8 ms-auto inline-flex justify-center items-center"
|
||||
data-custom-carousel-modal-hide>
|
||||
<svg class="w-3 h-3" aria-hidden="true" xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"/>
|
||||
</svg>
|
||||
<span class="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Modal body -->
|
||||
<div class="flex items-center justify-center overflow-hidden h-full w-full"
|
||||
data-custom-carousel-modal-image/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carousel wrapper -->
|
||||
<div class="relative overflow-hidden w-full h-56 md:h-120">
|
||||
{
|
||||
groupToShow.images.map((image, index) => (
|
||||
<div class="hidden duration-1500 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"
|
||||
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"
|
||||
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"/>
|
||||
@@ -1,100 +0,0 @@
|
||||
import {
|
||||
Carousel,
|
||||
type CarouselItem,
|
||||
type CarouselInterface,
|
||||
type CarouselOptions,
|
||||
type IndicatorItem,
|
||||
Modal,
|
||||
type ModalInterface,
|
||||
} from 'flowbite';
|
||||
|
||||
class CustomCarousel extends HTMLElement {
|
||||
_slide: boolean;
|
||||
_items: CarouselItem[];
|
||||
_options: CarouselOptions;
|
||||
_carousel: CarouselInterface;
|
||||
|
||||
_modalEl: HTMLElement;
|
||||
_modal: ModalInterface;
|
||||
|
||||
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();
|
||||
|
||||
this._modalEl = this.querySelector('[data-custom-carousel-modal]') as HTMLElement || undefined;
|
||||
this._modal = new Modal(this._modalEl);
|
||||
|
||||
window.addEventListener("load", this._attachHandlers);
|
||||
|
||||
}
|
||||
|
||||
_getItems = (): CarouselItem[] => {
|
||||
let customItems = this.querySelectorAll('[data-custom-carousel-item]') || [];
|
||||
|
||||
return Array.from(customItems).map(
|
||||
(item): CarouselItem => {
|
||||
return {
|
||||
el: item as HTMLElement,
|
||||
position: Number(item.getAttribute("data-custom-carousel-item"))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
_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 => {
|
||||
// Carousel controls
|
||||
this.querySelector(
|
||||
'[data-custom-carousel-next]'
|
||||
)?.addEventListener('click', () => this._carousel.next());
|
||||
this.querySelector(
|
||||
'[data-custom-carousel-prev]'
|
||||
)?.addEventListener('click', () => this._carousel.prev());
|
||||
|
||||
// Close fullscreen modal
|
||||
this._modalEl.querySelector('[data-custom-carousel-modal-hide]')?.addEventListener('click', () => this._modal.hide())
|
||||
|
||||
// Click to open fullscreen modal
|
||||
this._items.forEach((item) => {
|
||||
item.el.addEventListener('click', () => {
|
||||
const imgCloned = item.el.querySelector('img')?.cloneNode() as Node;
|
||||
const imageDiv = this._modalEl.querySelector('[data-custom-carousel-modal-image]') as HTMLElement || undefined;
|
||||
|
||||
imageDiv.innerHTML = '';
|
||||
imageDiv?.appendChild(imgCloned);
|
||||
|
||||
this._modal.show();
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('custom-carousel', CustomCarousel)
|
||||
5
src/components/CustomHtmlWrappers/H2.astro
Normal file
5
src/components/CustomHtmlWrappers/H2.astro
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<h2 class="my-4 font-bold md:text-2xl">{Astro.props.text}</h2>
|
||||
5
src/components/CustomHtmlWrappers/H3.astro
Normal file
5
src/components/CustomHtmlWrappers/H3.astro
Normal file
@@ -0,0 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<h3 class="my-4 font-bold md:text-lg">{Astro.props.text}</h3>
|
||||
@@ -1,7 +1,10 @@
|
||||
---
|
||||
---
|
||||
|
||||
<footer class="z-50 w-full max-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
|
||||
class="border-t-caperren-green-dark text-caperren-green-dark z-50 flex w-full max-w-full items-center justify-between border-t bg-black px-6 py-2 text-sm"
|
||||
>
|
||||
<span>{import.meta.env.PUBLIC_BUILD_ENVIRONMENT || "development"}</span>
|
||||
<span>{import.meta.env.PUBLIC_REPO_VERSION_HASH || "invalid"}</span>
|
||||
</footer>
|
||||
|
||||
11
src/components/LinkButton.astro
Normal file
11
src/components/LinkButton.astro
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<a
|
||||
class="text-caperren-green border-caperren-green hover:border-caperren-green-light hover:text-caperren-green-light rounded-2xl border-2 bg-black p-2"
|
||||
href={Astro.props.href}
|
||||
target={Astro.props.target || "_blank"}
|
||||
>
|
||||
{Astro.props.title}
|
||||
</a>
|
||||
145
src/components/Media/CustomCarousel/CustomCarousel.astro
Normal file
145
src/components/Media/CustomCarousel/CustomCarousel.astro
Normal file
@@ -0,0 +1,145 @@
|
||||
---
|
||||
import { Image } from "astro:assets";
|
||||
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
const groupToShow: carouselGroup = Astro.props.carouselGroup;
|
||||
---
|
||||
|
||||
<custom-carousel
|
||||
class="relative flex w-full flex-col"
|
||||
data-custom-carousel={groupToShow.animation}
|
||||
data-custom-carousel-interval={groupToShow.interval}
|
||||
>
|
||||
<!-- Modal for fullscreen viewing -->
|
||||
<div
|
||||
tabindex="-1"
|
||||
aria-hidden="true"
|
||||
class="fixed inset-0 z-100 hidden w-full items-center justify-center"
|
||||
data-custom-carousel-modal
|
||||
>
|
||||
<div class="relative h-full w-full p-4">
|
||||
<!-- Modal content -->
|
||||
<div
|
||||
class="border-caperren-green relative h-full max-h-screen max-w-screen rounded-lg border-2 bg-black shadow-sm"
|
||||
>
|
||||
<!-- Modal header -->
|
||||
<div class="flex items-center justify-between rounded-t p-1">
|
||||
<button
|
||||
type="button"
|
||||
class="text-caperren-green ring-caperren-green-dark hover:ring-caperren-green-light hover:text-caperren-green-light z-100 ms-auto inline-flex size-8 items-center justify-center rounded-lg bg-transparent text-sm ring-2"
|
||||
data-custom-carousel-modal-hide
|
||||
>
|
||||
<svg
|
||||
class="h-3 w-3"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 14 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"></path>
|
||||
</svg>
|
||||
<span class="sr-only">Close modal</span>
|
||||
</button>
|
||||
</div>
|
||||
<!-- Modal body -->
|
||||
<div
|
||||
class="flex h-full w-full items-center justify-center overflow-hidden"
|
||||
data-custom-carousel-modal-image
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Carousel wrapper -->
|
||||
<div class="relative h-56 w-full overflow-hidden md:h-120">
|
||||
{
|
||||
groupToShow.images.map((image, index) => (
|
||||
<div
|
||||
class="hidden duration-1500 ease-in-out"
|
||||
data-custom-carousel-item={index}
|
||||
>
|
||||
<Image
|
||||
src={image}
|
||||
class="absolute inset-0 m-auto h-full max-h-full w-auto max-w-full object-contain"
|
||||
alt="..."
|
||||
layout="constrained"
|
||||
loading="eager"
|
||||
/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
<!-- Slider indicators -->
|
||||
{
|
||||
groupToShow.images.length > 1 && (
|
||||
<div>
|
||||
<div class="absolute bottom-2 left-1/2 z-30 flex -translate-x-1/2 space-x-3 rounded-full">
|
||||
{groupToShow.images.map((_, index) => (
|
||||
<button
|
||||
type="button"
|
||||
class="hover:bg-caperren-green-light h-3 w-3 rounded-full bg-black"
|
||||
aria-current={index ? "false" : "true"}
|
||||
aria-label={index.toString()}
|
||||
data-custom-carousel-slide-to={index}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
<button
|
||||
type="button"
|
||||
class="group absolute start-0 top-0 z-30 flex h-full cursor-pointer items-center justify-center px-4 focus:outline-none"
|
||||
data-custom-carousel-prev
|
||||
>
|
||||
<span class="ring-caperren-green/25 inline-flex h-10 w-10 items-center justify-center rounded-full bg-black/25 ring-2 group-hover:bg-black/75">
|
||||
<svg
|
||||
class="text-caperren-green group-hover:text-caperren-green-light h-4 w-4"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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="group absolute end-0 top-0 z-30 flex h-full cursor-pointer items-center justify-center px-4 focus:outline-none"
|
||||
data-custom-carousel-next
|
||||
>
|
||||
<span class="ring-caperren-green/25 inline-flex h-10 w-10 items-center justify-center rounded-full bg-black/25 ring-2 group-hover:bg-black/75">
|
||||
<svg
|
||||
class="text-caperren-green group-hover:text-caperren-green-light h-4 w-4"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</custom-carousel>
|
||||
|
||||
<script src="./custom-carousel.ts"></script>
|
||||
111
src/components/Media/CustomCarousel/custom-carousel.ts
Normal file
111
src/components/Media/CustomCarousel/custom-carousel.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import {
|
||||
Carousel,
|
||||
type CarouselItem,
|
||||
type CarouselInterface,
|
||||
type CarouselOptions,
|
||||
type IndicatorItem,
|
||||
Modal,
|
||||
type ModalInterface,
|
||||
} from "flowbite";
|
||||
|
||||
class CustomCarousel extends HTMLElement {
|
||||
_slide: boolean;
|
||||
_items: CarouselItem[];
|
||||
_options: CarouselOptions;
|
||||
_carousel: CarouselInterface;
|
||||
|
||||
_modalEl: HTMLElement;
|
||||
_modal: ModalInterface;
|
||||
|
||||
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();
|
||||
|
||||
this._modalEl =
|
||||
(this.querySelector("[data-custom-carousel-modal]") as HTMLElement) ||
|
||||
undefined;
|
||||
this._modal = new Modal(this._modalEl);
|
||||
|
||||
window.addEventListener("load", this._attachHandlers);
|
||||
}
|
||||
|
||||
_getItems = (): CarouselItem[] => {
|
||||
let customItems =
|
||||
this.querySelectorAll("[data-custom-carousel-item]") || [];
|
||||
|
||||
return Array.from(customItems).map((item): CarouselItem => {
|
||||
return {
|
||||
el: item as HTMLElement,
|
||||
position: Number(item.getAttribute("data-custom-carousel-item")),
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
_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): IndicatorItem => {
|
||||
return {
|
||||
el: item as HTMLElement,
|
||||
position: Number(
|
||||
item.getAttribute("data-custom-carousel-slide-to"),
|
||||
),
|
||||
};
|
||||
},
|
||||
),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
_attachHandlers = (): void => {
|
||||
// Carousel controls
|
||||
this.querySelector("[data-custom-carousel-next]")?.addEventListener(
|
||||
"click",
|
||||
() => this._carousel.next(),
|
||||
);
|
||||
this.querySelector("[data-custom-carousel-prev]")?.addEventListener(
|
||||
"click",
|
||||
() => this._carousel.prev(),
|
||||
);
|
||||
|
||||
// Close fullscreen modal
|
||||
this._modalEl
|
||||
.querySelector("[data-custom-carousel-modal-hide]")
|
||||
?.addEventListener("click", () => this._modal.hide());
|
||||
|
||||
// Click to open fullscreen modal
|
||||
this._items.forEach((item) => {
|
||||
item.el.addEventListener("click", () => {
|
||||
const imgCloned = item.el.querySelector("img")?.cloneNode() as Node;
|
||||
const imageDiv =
|
||||
(this._modalEl.querySelector(
|
||||
"[data-custom-carousel-modal-image]",
|
||||
) as HTMLElement) || undefined;
|
||||
|
||||
imageDiv.innerHTML = "";
|
||||
imageDiv?.appendChild(imgCloned);
|
||||
|
||||
this._modal.show();
|
||||
});
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define("custom-carousel", CustomCarousel);
|
||||
7
src/components/Media/PdfViewer.astro
Normal file
7
src/components/Media/PdfViewer.astro
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<iframe
|
||||
src={Astro.props.pdf}
|
||||
class={"w-9/10 md:w-3/5 h-7/10 md:h-4/5 " + Astro.props.class || ""}></iframe>
|
||||
11
src/components/Media/Video.astro
Normal file
11
src/components/Media/Video.astro
Normal file
@@ -0,0 +1,11 @@
|
||||
---
|
||||
import { type videoConfig } from "@interfaces/video.ts";
|
||||
|
||||
const config: videoConfig = Astro.props.videoConfig;
|
||||
console.log(config);
|
||||
---
|
||||
|
||||
<video class="h-auto w-full max-w-1/2" controls>
|
||||
<source src={config.videoPath} type={config.videoType ?? "video/mp4"} />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
13
src/components/Media/YtVideo.astro
Normal file
13
src/components/Media/YtVideo.astro
Normal file
@@ -0,0 +1,13 @@
|
||||
---
|
||||
import type { videoConfig } from "@interfaces/video.ts";
|
||||
|
||||
const config: videoConfig = Astro.props.videoConfig;
|
||||
---
|
||||
|
||||
<iframe
|
||||
class="h-128 w-full max-w-1/2"
|
||||
src={config.videoPath}
|
||||
title={config.videoTitle ?? ""}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin"
|
||||
allowfullscreen></iframe>
|
||||
@@ -2,31 +2,47 @@
|
||||
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";
|
||||
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}
|
||||
class="h-10 lg: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 lg: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" 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 mt-1 w-full lg:block lg:w-auto" id="navbar-multi-level">
|
||||
<NestedNavbarEntry items={siteLayout}/>
|
||||
</div>
|
||||
<nav class="border-b-caperren-green text-caperren-green border-b-4">
|
||||
<div class="mx-auto flex flex-wrap items-center justify-between p-6">
|
||||
<a href="/">
|
||||
<Image
|
||||
src={logo_title_large}
|
||||
class="h-10 w-auto lg:h-14"
|
||||
alt="logo title"
|
||||
loading="eager"
|
||||
/>
|
||||
</a>
|
||||
<button
|
||||
data-collapse-toggle="navbar-multi-level"
|
||||
type="button"
|
||||
class="focus:ring-caperren-green inline-flex h-10 w-10 items-center justify-center p-2 text-sm focus:ring-2 lg:hidden"
|
||||
aria-controls="navbar-multi-level"
|
||||
aria-expanded="false"
|
||||
>
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg
|
||||
class="h-5 w-5"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 17 14"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"></path>
|
||||
</svg>
|
||||
</button>
|
||||
<div
|
||||
class="z-40 mt-1 hidden w-full lg:block lg:w-auto"
|
||||
id="navbar-multi-level"
|
||||
>
|
||||
<NestedNavbarEntry items={siteLayout} />
|
||||
</div>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,51 +1,78 @@
|
||||
---
|
||||
import type {navLink} from "@interfaces/site-layout.ts";
|
||||
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("-")
|
||||
}
|
||||
return "-" + [...paths, entry.path].join("-");
|
||||
};
|
||||
const getHrefPath = (entry: navLink): string => {
|
||||
return entry.pubpath ? entry.pubpath : ("/" + (paths && paths.length ? [...paths, entry.path].join("/") : entry.path));
|
||||
}
|
||||
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 ? "space-y-2" : "items-start lg:flex-row lg:space-x-8 lg:mt-0 ")}>
|
||||
{
|
||||
items.map((entry) => (
|
||||
(entry.enabled ?? true) && (
|
||||
<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 lg:hover:bg-transparent lg:border-0 lg:hover:text-caperren-green-light lg: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"
|
||||
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 lg: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 lg:p-0"
|
||||
aria-current="page">{entry.navText}</a>
|
||||
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
))}
|
||||
<ul
|
||||
class={"flex flex-col p-4 bg-black border-caperren-green " +
|
||||
(depth ? "space-y-2" : "items-start lg:flex-row lg:space-x-8 lg:mt-0 ")}
|
||||
>
|
||||
{
|
||||
items.map(
|
||||
(entry) =>
|
||||
(entry.enabled ?? true) && (
|
||||
<li>
|
||||
{Array.isArray(entry.children) && entry.children.length ? (
|
||||
<div>
|
||||
<button
|
||||
id={"dropdownNavbarLink" + getNavLinkSuffix(entry)}
|
||||
data-dropdown-toggle={
|
||||
"dropdownNavbar" + getNavLinkSuffix(entry)
|
||||
}
|
||||
data-dropdown-placement="bottom"
|
||||
class="hover:text-caperren-green-light lg:hover:text-caperren-green-light flex w-full items-center justify-between px-3 py-2 lg:border-0 lg:p-0 lg:hover:bg-transparent"
|
||||
>
|
||||
{entry.navText}
|
||||
<svg
|
||||
class="ms-2.5 h-2.5 w-2.5"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
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="border-caperren-green z-10 hidden w-screen border bg-black shadow-sm lg:w-max"
|
||||
>
|
||||
<Astro.self
|
||||
items={entry.children}
|
||||
paths={[...paths, entry.path]}
|
||||
depth={depth + 1}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<a
|
||||
href={getHrefPath(entry)}
|
||||
class="hover:text-caperren-green-light ring-caperren-green-dark block bg-transparent px-3 py-2 lg:p-0"
|
||||
aria-current="page"
|
||||
>
|
||||
{entry.navText}
|
||||
</a>
|
||||
)}
|
||||
</li>
|
||||
),
|
||||
)
|
||||
}
|
||||
</ul>
|
||||
@@ -1,5 +1,5 @@
|
||||
---
|
||||
import type {tableData} from "@interfaces/table.ts";
|
||||
import type { tableData } from "@interfaces/table.ts";
|
||||
|
||||
const data: tableData = Astro.props.data;
|
||||
const columnPadding: number = data.columnPadding || 2;
|
||||
@@ -8,27 +8,33 @@ const paddingClasses: string = `px-${columnPadding} py-${rowPadding}`;
|
||||
---
|
||||
|
||||
<div class="relative max-w-full overflow-x-auto">
|
||||
<table class="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>
|
||||
<table class="text-left text-sm">
|
||||
<thead class="border-caperren-green border-b-3 bg-black text-xs uppercase">
|
||||
<tr>
|
||||
{
|
||||
data.header.map((headingText) => (
|
||||
<th scope="col" class={paddingClasses}>
|
||||
{headingText}
|
||||
</th>
|
||||
))
|
||||
}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{
|
||||
data.rows.map((row) => (
|
||||
<tr class="border-caperren-green border-b dark:bg-black">
|
||||
{row.map((rowColumnText) => (
|
||||
<th
|
||||
scope="row"
|
||||
class={paddingClasses + " font-medium whitespace-nowrap"}
|
||||
>
|
||||
{rowColumnText}
|
||||
</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>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
@@ -1,36 +1,39 @@
|
||||
---
|
||||
import type {timelineEntry} from "@interfaces/timeline.ts";
|
||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||
|
||||
const timeline: timelineEntry[] = Astro.props.timeline || [];
|
||||
---
|
||||
|
||||
<custom-timeline>
|
||||
<div class="flex flex-col w-full">
|
||||
<div class="z-10 grid gap-6 grid-flow-row max-w-full 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>
|
||||
<div class="z-0 w-full max-w-full overflow-x-visible" data-custom-timeline-line-wrapper/>
|
||||
<div class="flex w-full flex-col">
|
||||
<div
|
||||
class="3xl:grid-cols-6 z-10 grid max-w-full grid-flow-row gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
data-timeline
|
||||
>
|
||||
{
|
||||
timeline.map((entry, index) => (
|
||||
<div
|
||||
class="border-caperren-green min-w-s max-w-s rounded-lg border bg-black px-2 pt-1 pb-2"
|
||||
data-timeline-node-index={index}
|
||||
>
|
||||
<h3 class="text-lg font-bold">{entry.event}</h3>
|
||||
<h4 class="leading-none font-semibold">{entry.eventDetail}</h4>
|
||||
<time class="mt-1 mb-2 text-sm leading-none italic">
|
||||
{entry.date}
|
||||
</time>
|
||||
{entry.description && (
|
||||
<p class="text-sm font-normal">{entry.description}</p>
|
||||
)}
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<div
|
||||
class="z-0 w-full max-w-full overflow-x-visible"
|
||||
data-custom-timeline-line-wrapper
|
||||
>
|
||||
</div>
|
||||
</div>
|
||||
</custom-timeline>
|
||||
|
||||
<script src="./timeline.ts"/>
|
||||
<script src="./timeline.ts"></script>
|
||||
|
||||
@@ -1,70 +1,80 @@
|
||||
import LeaderLine from "leader-line-new";
|
||||
|
||||
class CustomTimeline extends HTMLElement {
|
||||
_eventElements: Element[];
|
||||
_eventElements: Element[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._eventElements = this._getNodeElements();
|
||||
window.addEventListener("load", this._paintLeaderLines);
|
||||
}
|
||||
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)
|
||||
|
||||
let contentBodyScrolling = document.getElementById("content-body-scrolling");
|
||||
let wrapper = this.querySelector("[data-custom-timeline-line-wrapper]") as HTMLElement;
|
||||
|
||||
const position = (line: LeaderLine) => {
|
||||
wrapper.style.transform = "none";
|
||||
let rectWrapper = wrapper.getBoundingClientRect();
|
||||
|
||||
wrapper.style.transform = "translate(-" +
|
||||
(Number(rectWrapper.left)) + "px, -"
|
||||
+
|
||||
(Number(rectWrapper.top)) + "px)"
|
||||
|
||||
line.position()
|
||||
_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 => {
|
||||
let line = new LeaderLine({
|
||||
start: pair[0],
|
||||
end: pair[1],
|
||||
color: '#10ac25',
|
||||
size: 3,
|
||||
startSocket: "right",
|
||||
endSocket: "left",
|
||||
startPlug: "square",
|
||||
endPlug: "arrow3"
|
||||
});
|
||||
let contentBodyScrolling = document.getElementById(
|
||||
"content-body-scrolling",
|
||||
);
|
||||
let wrapper = this.querySelector(
|
||||
"[data-custom-timeline-line-wrapper]",
|
||||
) as HTMLElement;
|
||||
|
||||
// Find new element by hidden id and add to wrapper div
|
||||
const newLineEl = document.querySelector(`body .leader-line:has(path#leader-line-${line._id}-line-path)`);
|
||||
if (newLineEl) wrapper.appendChild(newLineEl);
|
||||
const position = (line: LeaderLine) => {
|
||||
wrapper.style.transform = "none";
|
||||
let rectWrapper = wrapper.getBoundingClientRect();
|
||||
|
||||
// Add position updaters for scrolling and resize events
|
||||
contentBodyScrolling?.addEventListener("scroll", () => position(line));
|
||||
window.addEventListener("resize", () => position(line));
|
||||
wrapper.style.transform =
|
||||
"translate(-" +
|
||||
Number(rectWrapper.left) +
|
||||
"px, -" +
|
||||
Number(rectWrapper.top) +
|
||||
"px)";
|
||||
|
||||
// Perform the initial positioning
|
||||
position(line);
|
||||
});
|
||||
line.position();
|
||||
};
|
||||
|
||||
}
|
||||
pairs.forEach((pair) => {
|
||||
let line = new LeaderLine({
|
||||
start: pair[0],
|
||||
end: pair[1],
|
||||
color: "#10ac25",
|
||||
size: 3,
|
||||
startSocket: "right",
|
||||
endSocket: "left",
|
||||
startPlug: "square",
|
||||
endPlug: "arrow3",
|
||||
});
|
||||
|
||||
// Find new element by hidden id and add to wrapper div
|
||||
// noinspection TypeScriptUnresolvedReference
|
||||
const lineId = line._id; // @ts-ignore
|
||||
const newLineEl = document.querySelector(
|
||||
`body .leader-line:has(path#leader-line-${lineId}-line-path)`,
|
||||
);
|
||||
if (newLineEl) wrapper.appendChild(newLineEl);
|
||||
|
||||
// Add position updaters for scrolling and resize events
|
||||
contentBodyScrolling?.addEventListener("scroll", () => position(line));
|
||||
window.addEventListener("resize", () => position(line));
|
||||
|
||||
// Perform the initial positioning
|
||||
position(line);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
customElements.define('custom-timeline', CustomTimeline)
|
||||
customElements.define("custom-timeline", CustomTimeline);
|
||||
|
||||
@@ -1,12 +0,0 @@
|
||||
---
|
||||
import {type videoConfig} from "@interfaces/video.ts";
|
||||
|
||||
const config: videoConfig = Astro.props.videoConfig;
|
||||
console.log(config);
|
||||
---
|
||||
|
||||
<video class="w-full h-auto max-w-1/2" controls>
|
||||
<source src={config.videoPath} type={config.videoType ?? "video/mp4"}/>
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
import type {videoConfig} from "@interfaces/video.ts";
|
||||
|
||||
const config: videoConfig = Astro.props.videoConfig;
|
||||
---
|
||||
<iframe class="h-128 w-full max-w-1/2" src={config.videoPath}
|
||||
title={config.videoTitle ?? ""}
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share"
|
||||
referrerpolicy="strict-origin-when-cross-origin" allowfullscreen></iframe>
|
||||
@@ -1,223 +1,270 @@
|
||||
import type {navLink} from "@interfaces/site-layout.ts"
|
||||
import type { navLink } from "@interfaces/site-layout.ts";
|
||||
|
||||
export const siteLayout: navLink[] = [
|
||||
{navText: "About", path: ""},
|
||||
{navText: "Education", path: "education"},
|
||||
{
|
||||
navText: "Experiences",
|
||||
path: "experience",
|
||||
{ navText: "About", path: "" },
|
||||
{ navText: "Education", path: "education" },
|
||||
{
|
||||
navText: "Experiences",
|
||||
path: "experience",
|
||||
children: [
|
||||
{
|
||||
navText: "SpaceX",
|
||||
path: "spacex",
|
||||
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"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "OSU CEOAS",
|
||||
path: "osu-ceoas-ocean-mixing-group",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Robotics Oceanographic Surface Sampler",
|
||||
path: "robotic-oceanographic-surface-sampler",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "LeConte Glacier Deployments",
|
||||
path: "leconte-glacier-deployments",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "OSU SARL",
|
||||
path: "osu-sinnhuber-aquatic-research-laboratory",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Team Lead",
|
||||
path: "team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Zebrafish Embryo Pick and Plate",
|
||||
path: "zebrafish-embryo-pick-and-plate",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Shuttlebox Behavior System",
|
||||
path: "shuttlebox-behavior-system",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Dechorionator",
|
||||
path: "dechorionator",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Denso Embryo Pick and Plate",
|
||||
path: "denso-embryo-pick-and-plate",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "ZScan Processor",
|
||||
path: "zscan-processor",
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "OSU Robotics Club",
|
||||
path: "osu-robotics-club",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Mars Rover Software Team Lead",
|
||||
path: "mars-rover-software-team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Mars Rover Emergency Software Team Lead",
|
||||
path: "mars-rover-emergency-software-team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Mars Rover Electrical Team Lead",
|
||||
path: "mars-rover-electrical-team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Club Officer",
|
||||
path: "club-officer",
|
||||
}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
navText: "Hobbies",
|
||||
path: "hobby",
|
||||
{
|
||||
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 Ocean Mixing Group",
|
||||
path: "osu-ceoas-ocean-mixing-group",
|
||||
children: [
|
||||
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Homelab", path: "homelab",
|
||||
children: [
|
||||
{enabled: false, navText: "Home Server Rack", path: "home-server-rack"},
|
||||
{enabled: false, navText: "Offsite Backup Rack", path: "offsite-backup-rack"},
|
||||
{enabled: false, navText: "Kubernetes Cluster", path: "kubernetes-cluster"},
|
||||
{enabled: false, 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"},
|
||||
]
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Trips",
|
||||
path: "trips",
|
||||
children: [
|
||||
{navText: "2025-08 | Alaska ", path: "2025-08-alaska", enabled: false,},
|
||||
{navText: "2024-10 | Norway ", path: "2024-10-norway", enabled: false,}
|
||||
]
|
||||
},
|
||||
]
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Projects",
|
||||
path: "projects",
|
||||
children: [
|
||||
{navText: "OSSM Overkill Edition", path: "ossm-overkill-edition", enabled: false},
|
||||
{navText: "Rachael Ray Light Box", path: "rachael-ray-light-box", enabled: false},
|
||||
{navText: "Shed Solar", path: "shed-solar", enabled: false},
|
||||
]
|
||||
},
|
||||
{enabled: false, navText: "NixOS", path: "nixos"},
|
||||
{navText: "Body Mods", path: "body-mods"},
|
||||
]
|
||||
},
|
||||
{
|
||||
navText: "Resumes",
|
||||
path: "resume",
|
||||
{
|
||||
navText: "Robotics Oceanographic Surface Sampler",
|
||||
path: "robotic-oceanographic-surface-sampler",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "LeConte Glacier Deployments",
|
||||
path: "leconte-glacier-deployments",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "OSU SARL",
|
||||
path: "osu-sinnhuber-aquatic-research-laboratory",
|
||||
children: [
|
||||
{enabled: false, navText: "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/"}
|
||||
]
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Team Lead",
|
||||
path: "team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Zebrafish Embryo Pick and Plate",
|
||||
path: "zebrafish-embryo-pick-and-plate",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Shuttlebox Behavior System",
|
||||
path: "shuttlebox-behavior-system",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Dechorionator",
|
||||
path: "dechorionator",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Denso Embryo Pick and Plate",
|
||||
path: "denso-embryo-pick-and-plate",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "ZScan Processor",
|
||||
path: "zscan-processor",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "OSU Robotics Club",
|
||||
path: "osu-robotics-club",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Mars Rover Software Team Lead",
|
||||
path: "mars-rover-software-team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Mars Rover Emergency Software Team Lead",
|
||||
path: "mars-rover-emergency-software-team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Mars Rover Electrical Team Lead",
|
||||
path: "mars-rover-electrical-team-lead",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Club Officer",
|
||||
path: "club-officer",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
navText: "Hobbies",
|
||||
path: "hobby",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Homelab",
|
||||
path: "homelab",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Home Server Rack",
|
||||
path: "home-server-rack",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Offsite Backup Rack",
|
||||
path: "offsite-backup-rack",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Kubernetes Cluster",
|
||||
path: "kubernetes-cluster",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
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",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Trips",
|
||||
path: "trips",
|
||||
children: [
|
||||
{
|
||||
navText: "2025-08 | Alaska ",
|
||||
path: "2025-08-alaska",
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
navText: "2024-10 | Norway ",
|
||||
path: "2024-10-norway",
|
||||
enabled: false,
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Projects",
|
||||
path: "projects",
|
||||
children: [
|
||||
{
|
||||
navText: "OSSM Overkill Edition",
|
||||
path: "ossm-overkill-edition",
|
||||
enabled: false,
|
||||
},
|
||||
{
|
||||
navText: "Rachael Ray Light Box",
|
||||
path: "rachael-ray-light-box",
|
||||
enabled: false,
|
||||
},
|
||||
{ navText: "Shed Solar", path: "shed-solar", enabled: false },
|
||||
],
|
||||
},
|
||||
{ enabled: false, navText: "NixOS", path: "nixos" },
|
||||
{ navText: "Body Mods", path: "body-mods" },
|
||||
],
|
||||
},
|
||||
{
|
||||
navText: "Resumes",
|
||||
path: "resume",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "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 => {
|
||||
let paths = path.split("/").filter((entry) => entry);
|
||||
let paths = path.split("/").filter((entry) => entry);
|
||||
|
||||
// Handle root path of /
|
||||
if (paths.length < 1) {
|
||||
paths = [""]
|
||||
}
|
||||
// Handle root path of /
|
||||
if (paths.length < 1) {
|
||||
paths = [""];
|
||||
}
|
||||
|
||||
let currentEntries: navLink[] = siteLayout;
|
||||
let foundEntry: navLink | undefined;
|
||||
let currentEntries: navLink[] = siteLayout;
|
||||
let foundEntry: navLink | undefined;
|
||||
|
||||
for (const path of paths) {
|
||||
for (const currentEntry of currentEntries) {
|
||||
if (currentEntry.path === path) {
|
||||
foundEntry = currentEntry;
|
||||
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.children && foundEntry.children.length > 0) {
|
||||
currentEntries = foundEntry.children;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundEntry === undefined) {
|
||||
throw new Error(`${path} not found in site layout!`);
|
||||
}
|
||||
if (foundEntry === undefined) {
|
||||
throw new Error(`${path} not found in site layout!`);
|
||||
}
|
||||
|
||||
return foundEntry;
|
||||
}
|
||||
return foundEntry;
|
||||
};
|
||||
|
||||
export const getPaths = (
|
||||
currentEntries: navLink[] = siteLayout,
|
||||
paths: string[] = [],
|
||||
disabledOnly = false
|
||||
currentEntries: navLink[] = siteLayout,
|
||||
paths: string[] = [],
|
||||
disabledOnly = false,
|
||||
): string[] => {
|
||||
let foundPaths: string[] = [];
|
||||
let foundPaths: string[] = [];
|
||||
|
||||
for (const currentEntry of currentEntries) {
|
||||
if (currentEntry.children && currentEntry.children.length > 0) {
|
||||
foundPaths = [
|
||||
...foundPaths,
|
||||
...getPaths(currentEntry.children, [...paths, currentEntry.path || ""], disabledOnly)
|
||||
]
|
||||
} else {
|
||||
let enabled = currentEntry.enabled ?? true;
|
||||
if (disabledOnly ? !enabled : enabled) {
|
||||
foundPaths.push("/" + [...paths, currentEntry.path || ""].join("/"));
|
||||
}
|
||||
}
|
||||
for (const currentEntry of currentEntries) {
|
||||
if (currentEntry.children && currentEntry.children.length > 0) {
|
||||
foundPaths = [
|
||||
...foundPaths,
|
||||
...getPaths(
|
||||
currentEntry.children,
|
||||
[...paths, currentEntry.path || ""],
|
||||
disabledOnly,
|
||||
),
|
||||
];
|
||||
} else {
|
||||
let enabled = currentEntry.enabled ?? true;
|
||||
if (disabledOnly ? !enabled : enabled) {
|
||||
foundPaths.push("/" + [...paths, currentEntry.path || ""].join("/"));
|
||||
}
|
||||
}
|
||||
return [...new Set(foundPaths)];
|
||||
}
|
||||
}
|
||||
return [...new Set(foundPaths)];
|
||||
};
|
||||
|
||||
6
src/env.d.ts
vendored
6
src/env.d.ts
vendored
@@ -1,8 +1,8 @@
|
||||
interface ImportMetaEnv {
|
||||
readonly PUBLIC_REPO_VERSION_HASH: string;
|
||||
readonly PUBLIC_BUILD_ENVIRONMENT: string;
|
||||
readonly PUBLIC_REPO_VERSION_HASH: string;
|
||||
readonly PUBLIC_BUILD_ENVIRONMENT: string;
|
||||
}
|
||||
|
||||
interface ImportMeta {
|
||||
readonly env: ImportMetaEnv;
|
||||
readonly env: ImportMetaEnv;
|
||||
}
|
||||
@@ -1,14 +1,11 @@
|
||||
import {DateTime} from 'luxon';
|
||||
import { DateTime } from "luxon";
|
||||
|
||||
export interface experience {
|
||||
|
||||
|
||||
}
|
||||
export interface experience {}
|
||||
|
||||
export interface subExperience {
|
||||
name: string;
|
||||
description: string;
|
||||
name: string;
|
||||
description: string;
|
||||
|
||||
startDate: DateTime;
|
||||
endDate?: DateTime;
|
||||
startDate: DateTime;
|
||||
endDate?: DateTime;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface carouselGroup {
|
||||
animation: "static" | "slide";
|
||||
interval?: number;
|
||||
images: ImageMetadata[];
|
||||
animation: "static" | "slide";
|
||||
interval?: number;
|
||||
images: ImageMetadata[];
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
export interface navLink {
|
||||
enabled?: boolean;
|
||||
navText: string;
|
||||
path?: string;
|
||||
pubpath?: string;
|
||||
children?: navLink[];
|
||||
enabled?: boolean;
|
||||
navText: string;
|
||||
path?: string;
|
||||
pubpath?: string;
|
||||
children?: navLink[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
export interface tableData {
|
||||
header: string[];
|
||||
columnPadding?: number;
|
||||
rowPadding?: number;
|
||||
rows: Array<Array<any>>;
|
||||
header: string[];
|
||||
columnPadding?: number;
|
||||
rowPadding?: number;
|
||||
rows: Array<Array<any>>;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
export interface timelineEntry {
|
||||
event: string;
|
||||
eventDetail?: string
|
||||
date: string;
|
||||
description?: string;
|
||||
event: string;
|
||||
eventDetail?: string;
|
||||
date: string;
|
||||
description?: string;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
export interface videoConfig {
|
||||
videoTitle?: string
|
||||
videoPath: string;
|
||||
videoType?: string;
|
||||
videoTitle?: string;
|
||||
videoPath: string;
|
||||
videoType?: string;
|
||||
}
|
||||
@@ -1,46 +1,53 @@
|
||||
---
|
||||
import '@styles/global.css'
|
||||
import "@styles/global.css";
|
||||
|
||||
import Navbar from '@components/Navbar.astro';
|
||||
import Footer from '@components/Footer.astro';
|
||||
import Navbar from "@components/Navbar.astro";
|
||||
import Footer from "@components/Footer.astro";
|
||||
|
||||
import {pathToMetadata} from "@data/site-layout.ts";
|
||||
import { pathToMetadata } from "@data/site-layout.ts";
|
||||
|
||||
|
||||
const pageTitle = Astro.props.title ? `${Astro.props.title} - Corwin Perren` : "Corwin Perren";
|
||||
const pageTitle = Astro.props.title
|
||||
? `${Astro.props.title} - Corwin Perren`
|
||||
: "Corwin Perren";
|
||||
const showTitle = Astro.props.showTitle ?? true;
|
||||
const pageEnabled = pathToMetadata(Astro.url.pathname).enabled ?? true;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
|
||||
<link rel="icon" href="/48x48-favicon.png" type="image/x-icon"/>
|
||||
<link rel="icon" href="/512x512-favicon.png" type="image/png"/>
|
||||
<link rel="icon" href="/192x192-favicon.png" type="image/png"/>
|
||||
<link rel="icon" href="/180x180-favicon.png" type="image/png"/>
|
||||
<link rel="icon" href="/48x48-favicon.png" type="image/png"/>
|
||||
<link rel="icon" href="/32x32-favicon.png" type="image/png"/>
|
||||
<link rel="icon" href="/16x16-favicon.png" type="image/png"/>
|
||||
<link rel="icon" href="/48x48-favicon.png" type="image/x-icon" />
|
||||
<link rel="icon" href="/512x512-favicon.png" type="image/png" />
|
||||
<link rel="icon" href="/192x192-favicon.png" type="image/png" />
|
||||
<link rel="icon" href="/180x180-favicon.png" type="image/png" />
|
||||
<link rel="icon" href="/48x48-favicon.png" type="image/png" />
|
||||
<link rel="icon" href="/32x32-favicon.png" type="image/png" />
|
||||
<link rel="icon" href="/16x16-favicon.png" type="image/png" />
|
||||
|
||||
<link rel="sitemap" href="/sitemap-index.xml"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<link rel="sitemap" href="/sitemap-index.xml" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>{pageEnabled ? pageTitle : "Corwin Perren"}</title>
|
||||
</head>
|
||||
<body class="flex flex-col bg-black text-white h-dvh w-full max-w-full">
|
||||
<div id="content-body-scrolling" class="grow overflow-x-hidden overflow-y-scroll">
|
||||
<Navbar/>
|
||||
<main class="mx-6 my-6">
|
||||
{(Astro.props.title && showTitle && pageEnabled) && (
|
||||
<h1 class="font-extrabold md:text-3xl md:mb-6">{Astro.props.title}</h1>
|
||||
)}
|
||||
{pageEnabled && (
|
||||
<slot/>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
<Footer/>
|
||||
<script src="../scripts/main.ts"></script>
|
||||
</body>
|
||||
</head>
|
||||
<body class="flex h-dvh w-full max-w-full flex-col bg-black text-white">
|
||||
<div
|
||||
id="content-body-scrolling"
|
||||
class="grow overflow-x-hidden overflow-y-scroll"
|
||||
>
|
||||
<Navbar />
|
||||
<main class="mx-6 my-6">
|
||||
{
|
||||
Astro.props.title && showTitle && pageEnabled && (
|
||||
<h1 class="font-extrabold md:mb-6 md:text-3xl">
|
||||
{Astro.props.title}
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
{pageEnabled && <slot />}
|
||||
</main>
|
||||
</div>
|
||||
<Footer />
|
||||
<script src="../scripts/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
---
|
||||
|
||||
<BaseLayout title={Astro.props.title}>
|
||||
<slot/>
|
||||
<slot />
|
||||
</BaseLayout>
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
---
|
||||
|
||||
<BaseLayout title={Astro.props.title}>
|
||||
<slot/>
|
||||
<slot />
|
||||
</BaseLayout>
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import PdfViewer from "@components/Media/PdfViewer.astro";
|
||||
---
|
||||
|
||||
const resume = Astro.props.resume;
|
||||
---
|
||||
<BaseLayout title={Astro.props.title}>
|
||||
<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>
|
||||
<div class="h-dvh">
|
||||
<PdfViewer class="mx-auto" pdf={Astro.props.resume} />
|
||||
</div>
|
||||
</BaseLayout>
|
||||
@@ -1,117 +1,117 @@
|
||||
---
|
||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import Carousel from "@components/Media/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 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"
|
||||
|
||||
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
|
||||
]
|
||||
}
|
||||
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"
|
||||
},
|
||||
{
|
||||
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"]
|
||||
]
|
||||
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}/>
|
||||
<Carousel carouselGroup={diplomaCarouselGroup} />
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">Timeline</h2>
|
||||
<Timeline timeline={timeline} />
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">Oregon State University</h2>
|
||||
<a
|
||||
class="my-4 font-bold text-blue-500 underline hover:text-blue-300 md:text-lg"
|
||||
href="https://github.com/caperren/school_archives/tree/master/OSU%20Coursework"
|
||||
>Coursework Archives</a
|
||||
>
|
||||
<h3 class="my-4 font-bold underline md:text-lg">Course Listing</h3>
|
||||
<Table data={courseTable} />
|
||||
</BaseLayout>
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="CEOAS - LeConte Glacier Deployments">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="CEOAS - LeConte Glacier Deployments" />
|
||||
|
||||
@@ -1,6 +1,98 @@
|
||||
---
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
import H2 from "@components/CustomHtmlWrappers/H2.astro";
|
||||
import LinkButton from "@components/LinkButton.astro";
|
||||
import PdfViewer from "@components/Media/PdfViewer.astro";
|
||||
import Timeline from "@components/Timeline/Timeline.astro";
|
||||
|
||||
import publication from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ross-publication.pdf";
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [],
|
||||
};
|
||||
|
||||
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="CEOAS - Robotic Oceanographic Surface Sampler">
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
<div class="mt-4 flex items-center justify-center">
|
||||
<LinkButton
|
||||
href="https://tos.org/oceanography/article/autonomous-ctd-profiling-from-the-robotic-oceanographic-surface-sampler"
|
||||
title="Official Scientific Publication"
|
||||
/>
|
||||
</div>
|
||||
<h2 class="my-4 font-bold md:text-2xl">Summary</h2>
|
||||
<h3 class="my-4 font-bold md:text-lg">Timeline</h3>
|
||||
<Timeline timeline={timeline} />
|
||||
<h3 class="my-4 font-bold md:text-lg">Key Takeaways</h3>
|
||||
<ul class="list-inside list-disc">
|
||||
<li>One</li>
|
||||
<li>Two</li>
|
||||
<li>Three</li>
|
||||
</ul>
|
||||
<h3 class="my-4 font-bold md:text-lg">Skills Used</h3>
|
||||
<div
|
||||
class="border-caperren-green relative grid grid-flow-row gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Software</div>
|
||||
<hr class="text-caperren-green" />
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
<li>One</li>
|
||||
<li>Two</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Electrical</div>
|
||||
<hr class="text-caperren-green" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Mechanical</div>
|
||||
<hr class="text-caperren-green" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Other</div>
|
||||
<hr class="text-caperren-green" />
|
||||
</div>
|
||||
</div>
|
||||
<H2 text="Official Scientific Publication" />
|
||||
<div class="h-334">
|
||||
<PdfViewer pdf={publication} />
|
||||
</div>
|
||||
</ExperienceLayout>
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="OSURC - Officer">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="OSURC - Officer" />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="OSURC - Electrical Team Lead">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="OSURC - Electrical Team Lead" />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="OSURC - Emergency Software Team Lead">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="OSURC - Emergency Software Team Lead" />
|
||||
|
||||
@@ -1,245 +1,279 @@
|
||||
---
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
import YtVideo from "@components/YtVideo.astro";
|
||||
import type {videoConfig} from "@interfaces/video.ts";
|
||||
|
||||
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
import YtVideo from "@components/Media/YtVideo.astro";
|
||||
import type { videoConfig } from "@interfaces/video.ts";
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: []
|
||||
}
|
||||
|
||||
animation: "slide",
|
||||
images: [],
|
||||
};
|
||||
|
||||
const videoList: videoConfig[] = [
|
||||
{
|
||||
videoTitle: "Ground Station Software Quick Overview",
|
||||
videoPath: "https://www.youtube-nocookie.com/embed/ZjGW-HWapVA"
|
||||
},
|
||||
{
|
||||
videoTitle: "Rover Software Environment And Full Code Overview",
|
||||
videoPath: "https://www.youtube-nocookie.com/embed/sceA2ZbEV8Y"
|
||||
}
|
||||
]
|
||||
{
|
||||
videoTitle: "Ground Station Software Quick Overview",
|
||||
videoPath: "https://www.youtube-nocookie.com/embed/ZjGW-HWapVA",
|
||||
},
|
||||
{
|
||||
videoTitle: "Rover Software Environment And Full Code Overview",
|
||||
videoPath: "https://www.youtube-nocookie.com/embed/sceA2ZbEV8Y",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<ExperienceLayout title="OSURC - Software Team Lead">
|
||||
<Carousel carouselGroup={headerCarouselGroup}/>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
|
||||
<h2 class="font-bold md:text-2xl my-4 underline">Ground Station Readouts & Features</h2>
|
||||
<ul class="space-y-1 list-disc list-inside">
|
||||
<li>Clock</li>
|
||||
<li>Event Timer</li>
|
||||
<li>Status Indication
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Rover Connection</li>
|
||||
<li>Controller Connection Info</li>
|
||||
<li>Radio Stats</li>
|
||||
<li>GPS Stats</li>
|
||||
<li>NVidia Jetson TX2 Computer Stats</li>
|
||||
<li>Battery Voltage w/Low Battery Warning</li>
|
||||
<li>Wheel Connections</li>
|
||||
<li>Camera Connections</li>
|
||||
</ul>
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">
|
||||
Ground Station Readouts & Features
|
||||
</h2>
|
||||
<ul class="list-inside list-disc space-y-1">
|
||||
<li>Clock</li>
|
||||
<li>Event Timer</li>
|
||||
<li>
|
||||
Status Indication
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Rover Connection</li>
|
||||
<li>Controller Connection Info</li>
|
||||
<li>Radio Stats</li>
|
||||
<li>GPS Stats</li>
|
||||
<li>NVidia Jetson TX2 Computer Stats</li>
|
||||
<li>Battery Voltage w/Low Battery Warning</li>
|
||||
<li>Wheel Connections</li>
|
||||
<li>Camera Connections</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Radio Direction Finding
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Raw Radio RSSI Indication</li>
|
||||
<li>Radio RSSI Pulse Frequency w/Validity Indication</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Arm
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>
|
||||
Special Movements
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Stow Arm</li>
|
||||
<li>Unstow Arm</li>
|
||||
<li>Upright Arm</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Radio Direction Finding
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Raw Radio RSSI Indication</li>
|
||||
<li>Radio RSSI Pulse Frequency w/Validity Indication</li>
|
||||
</ul>
|
||||
<li>Calibrate Arm</li>
|
||||
<li>Clear Arm Fault</li>
|
||||
<li>Reset Arm Motor Drivers</li>
|
||||
<li>
|
||||
Task Movements
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Approach Oxygen Tank</li>
|
||||
<li>Depart Oxygen Tank</li>
|
||||
<li>Approach Light Beacon</li>
|
||||
<li>Depart Light Beacon</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Arm
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Special Movements
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Stow Arm</li>
|
||||
<li>Unstow Arm</li>
|
||||
<li>Upright Arm</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Calibrate Arm</li>
|
||||
<li>Clear Arm Fault</li>
|
||||
<li>Reset Arm Motor Drivers</li>
|
||||
<li>Task Movements
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Approach Oxygen Tank</li>
|
||||
<li>Depart Oxygen Tank</li>
|
||||
<li>Approach Light Beacon</li>
|
||||
<li>Depart Light Beacon</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Mining/Science
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Bucket Weight Measurement</li>
|
||||
<li>Bucket Lift/Tilt Position Readouts</li>
|
||||
<li>
|
||||
Preset Bucket Movements
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Mining Transport</li>
|
||||
<li>Mining Measure</li>
|
||||
<li>Mining Scoop</li>
|
||||
<li>Science Panorama</li>
|
||||
<li>Mining Sample</li>
|
||||
<li>Mining Probe</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Mining/Science
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Bucket Weight Measurement</li>
|
||||
<li>Bucket Lift/Tilt Position Readouts</li>
|
||||
<li>Preset Bucket Movements
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Mining Transport</li>
|
||||
<li>Mining Measure</li>
|
||||
<li>Mining Scoop</li>
|
||||
<li>Science Panorama</li>
|
||||
<li>Mining Sample</li>
|
||||
<li>Mining Probe</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Science Probe Readings
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Temp in C</li>
|
||||
<li>Moisture %</li>
|
||||
<li>Loss Tangent</li>
|
||||
<li>Soil Electrical Conductivity</li>
|
||||
<li>Real Dielectric Permitivity</li>
|
||||
<li>Imaginary Dielectric Permitivity</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Science Camera Controls
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Video Output Selection
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Network Video</li>
|
||||
<li>Camera LCD</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Photo Controls
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Zoom In One Step</li>
|
||||
<li>Zoom Out One Step</li>
|
||||
<li>Full Zoom In</li>
|
||||
<li>Full Zoom Out</li>
|
||||
<li>Shoot Photo</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<li>
|
||||
Science Probe Readings
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Temp in C</li>
|
||||
<li>Moisture %</li>
|
||||
<li>Loss Tangent</li>
|
||||
<li>Soil Electrical Conductivity</li>
|
||||
<li>Real Dielectric Permittivity</li>
|
||||
<li>Imaginary Dielectric Permittivity</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>SSH Console
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>SSH Terminal Display</li>
|
||||
<li>SSH Command Entry</li>
|
||||
<li>Preset Commands
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Network Host Scan</li>
|
||||
<li>List Wifi Networks</li>
|
||||
<li>Equipment Login and Help</li>
|
||||
<li>Equipment Logout</li>
|
||||
<li>Equipment Status</li>
|
||||
<li>Equipment Start</li>
|
||||
<li>Equipment Stop</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Connect/Disconnect Rover Wifi by SSID</li>
|
||||
</ul>
|
||||
<li>
|
||||
Science Camera Controls
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>
|
||||
Video Output Selection
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Network Video</li>
|
||||
<li>Camera LCD</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Photo Controls
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Zoom In One Step</li>
|
||||
<li>Zoom Out One Step</li>
|
||||
<li>Full Zoom In</li>
|
||||
<li>Full Zoom Out</li>
|
||||
<li>Shoot Photo</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Settings
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Map Selection</li>
|
||||
<li>Map Zoom Level</li>
|
||||
<li>Rover Wifi Radio Channel Selection</li>
|
||||
</ul>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
SSH Console
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>SSH Terminal Display</li>
|
||||
<li>SSH Command Entry</li>
|
||||
<li>
|
||||
Preset Commands
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Network Host Scan</li>
|
||||
<li>List Wifi Networks</li>
|
||||
<li>Equipment Login and Help</li>
|
||||
<li>Equipment Logout</li>
|
||||
<li>Equipment Status</li>
|
||||
<li>Equipment Start</li>
|
||||
<li>Equipment Stop</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Mapping Display
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Shows Google Map Terrain</li>
|
||||
<li>Shows Rover Location And Orientation</li>
|
||||
<li>Shows Rover GPS Coordinates</li>
|
||||
<li>Shows Saved Waypoints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Waypoint Entry / Editing
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Name Entry For Landmarks</li>
|
||||
<li>GPS Entry in Decimal</li>
|
||||
<li>GPS Entry in Degree/Minute/Second</li>
|
||||
<li>Waypoint Color Choice</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Navigation Waypoints
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Shows And Allows Editing Of Nav Waypoints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Landmark Waypoints
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Shows And Allows Editing Of Landmark Waypoints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Arm Joint Positions
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Positions Of Six Arm Joints In Revolutions</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Gripper Joint Positions
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Positions Shown As Raw Encoder Positions</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Arm Motor Drive Statuses
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Communication/Movement/Fault Statuses For All Six Arm Joints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Gripper Mode Readouts
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Gripper Mode Control State</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Xbox Control Mode
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Showed Whether Xbox Controller Moving Arm Or Mining</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Heading and Goal Indication w/Compass
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Raw Heading Indication</li>
|
||||
<li>Goal Indication (Unused)</li>
|
||||
<li>Compass Heading Indication</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Low Resolution Mode
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Controlled Low Resolution Fallback Mode During Radio Failure</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Current Speed
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>GPS Speed</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Speed Limit
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>% Of Max Rover Speed As Limit</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Tank Drive Output
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>% Of Total Power To Left/Right Rover Drive Systems</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>IMU Readings
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>Pitch/Roll Readings In +/- 1 Readout</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>Triple Camera Displays
|
||||
<ul class="ps-5 space-y-1 list-disc list-inside">
|
||||
<li>One Primary Video Display</li>
|
||||
<li>Two Secondary Video Displays</li>
|
||||
<li>Named Display For Currently Viewed Camera</li>
|
||||
<li>Ability To Set Each Display To Any Camera</li>
|
||||
<li>Ability to Disable Any Camera</li>
|
||||
<li>Ability to Pan/Tilt Any Camera</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 class="font-bold md:text-2xl my-4 underline">Rover Demos and Software Overviews</h2>
|
||||
{videoList.map((video) => (
|
||||
<h3 class="font-bold md:text-lg my-4">{video.videoTitle}</h3>
|
||||
<YtVideo videoConfig={video}/>
|
||||
))}
|
||||
|
||||
<li>Connect/Disconnect Rover Wifi by SSID</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Settings
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Map Selection</li>
|
||||
<li>Map Zoom Level</li>
|
||||
<li>Rover Wifi Radio Channel Selection</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Mapping Display
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Shows Google Map Terrain</li>
|
||||
<li>Shows Rover Location And Orientation</li>
|
||||
<li>Shows Rover GPS Coordinates</li>
|
||||
<li>Shows Saved Waypoints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Waypoint Entry / Editing
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Name Entry For Landmarks</li>
|
||||
<li>GPS Entry in Decimal</li>
|
||||
<li>GPS Entry in Degree/Minute/Second</li>
|
||||
<li>Waypoint Color Choice</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Navigation Waypoints
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Shows And Allows Editing Of Nav Waypoints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Landmark Waypoints
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Shows And Allows Editing Of Landmark Waypoints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Arm Joint Positions
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Positions Of Six Arm Joints In Revolutions</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Gripper Joint Positions
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Positions Shown As Raw Encoder Positions</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Arm Motor Drive Statuses
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Communication/Movement/Fault Statuses For All Six Arm Joints</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Gripper Mode Readouts
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Gripper Mode Control State</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Xbox Control Mode
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Showed Whether Xbox Controller Moving Arm Or Mining</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Heading and Goal Indication w/Compass
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Raw Heading Indication</li>
|
||||
<li>Goal Indication (Unused)</li>
|
||||
<li>Compass Heading Indication</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Low Resolution Mode
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Controlled Low Resolution Fallback Mode During Radio Failure</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Current Speed
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>GPS Speed</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Speed Limit
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>% Of Max Rover Speed As Limit</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Tank Drive Output
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>% Of Total Power To Left/Right Rover Drive Systems</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
IMU Readings
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>Pitch/Roll Readings In +/- 1 Readout</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li>
|
||||
Triple Camera Displays
|
||||
<ul class="list-inside list-disc space-y-1 ps-5">
|
||||
<li>One Primary Video Display</li>
|
||||
<li>Two Secondary Video Displays</li>
|
||||
<li>Named Display For Currently Viewed Camera</li>
|
||||
<li>Ability To Set Each Display To Any Camera</li>
|
||||
<li>Ability to Disable Any Camera</li>
|
||||
<li>Ability to Pan/Tilt Any Camera</li>
|
||||
</ul>
|
||||
</li>
|
||||
</ul>
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">
|
||||
Rover Demos and Software Overviews
|
||||
</h2>
|
||||
{
|
||||
videoList.map((video) => (
|
||||
<div>
|
||||
<h3 class="my-4 font-bold md:text-lg">{video.videoTitle}</h3>
|
||||
<YtVideo videoConfig={video} />
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</ExperienceLayout>
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="SARL - Dechorionator">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="SARL - Dechorionator" />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="SARL - Denso Embryo Pick and Plate">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="SARL - Denso Embryo Pick and Plate" />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="SARL - Shuttlebox Behavior System">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="SARL - Shuttlebox Behavior System" />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="SARL - Team Lead">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="SARL - Team Lead" />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="SARL - Zebrafish Embryo Pick and Plate">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="SARL - Zebrafish Embryo Pick and Plate" />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="SARL - ZScan Processor">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="SARL - ZScan Processor" />
|
||||
|
||||
@@ -1,78 +1,80 @@
|
||||
---
|
||||
import ExperienceLayout from '@layouts/ExperienceLayout.astro';
|
||||
import Timeline from '@components/Timeline/Timeline.astro';
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
import Timeline from "@components/Timeline/Timeline.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import spring_2019_interns
|
||||
from "@assets/experience/spacex/avionics-test-engineering-internship/spring-2019-interns.jpg";
|
||||
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";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
spring_2019_interns
|
||||
]
|
||||
}
|
||||
animation: "slide",
|
||||
images: [spring_2019_interns],
|
||||
};
|
||||
|
||||
const timeline: timelineEntry[] = [
|
||||
{
|
||||
event: "Started",
|
||||
date: "January 2019",
|
||||
},
|
||||
{
|
||||
event: "Finished",
|
||||
date: "March 2019",
|
||||
}
|
||||
{
|
||||
event: "Started",
|
||||
date: "January 2019",
|
||||
},
|
||||
{
|
||||
event: "Finished",
|
||||
date: "March 2019",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<ExperienceLayout title="SpaceX - Avionics Test Engineering Internship">
|
||||
<Carousel carouselGroup={headerCarouselGroup}/>
|
||||
<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>
|
||||
<h2 class="my-4 font-bold md:text-2xl">Summary</h2>
|
||||
<h3 class="my-4 font-bold md:text-lg">Timeline</h3>
|
||||
<Timeline timeline={timeline} />
|
||||
<h3 class="my-4 font-bold md:text-lg">Key Takeaways</h3>
|
||||
<ul class="list-inside list-disc">
|
||||
<li></li>
|
||||
</ul>
|
||||
<h3 class="my-4 font-bold md:text-lg">Skills Used</h3>
|
||||
<div
|
||||
class="border-caperren-green relative grid grid-flow-row gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Software</div>
|
||||
<hr class="text-caperren-green" />
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
<li>Python</li>
|
||||
<li>Test Driven Development</li>
|
||||
</ul>
|
||||
</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 can’t go into
|
||||
much detail due to NDA’s 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 I’ve 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 wouldn’t 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.
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Electrical</div>
|
||||
<hr class="text-caperren-green" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Other</div>
|
||||
<hr class="text-caperren-green" />
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="my-4 font-bold md:text-2xl">Details</h2>
|
||||
|
||||
Though I did get to work on some really fun projects during my internship at
|
||||
SpaceX, I unfortunately can’t go into much detail due to NDA’s 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 I’ve 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 wouldn’t 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>
|
||||
@@ -1,90 +1,98 @@
|
||||
---
|
||||
import ExperienceLayout from '@layouts/ExperienceLayout.astro';
|
||||
import Timeline from '@components/Timeline/Timeline.astro';
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
import Timeline from "@components/Timeline/Timeline.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import starlink_headquarters_selfie
|
||||
from "@assets/experience/spacex/hardware-test-engineer-i-ii/starlink-headquarters-selfie.jpg";
|
||||
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";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
starlink_headquarters_selfie
|
||||
]
|
||||
}
|
||||
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"
|
||||
}
|
||||
{
|
||||
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>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
<h2 class="my-4 font-bold md:text-2xl">Summary</h2>
|
||||
<h3 class="my-4 font-bold md:text-lg">Timeline</h3>
|
||||
<Timeline timeline={timeline} />
|
||||
<h3 class="my-4 font-bold md:text-lg">Key Takeaways</h3>
|
||||
<ul class="list-inside list-disc">
|
||||
<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="my-4 font-bold md:text-lg">Skills Used</h3>
|
||||
<div
|
||||
class="border-caperren-green relative grid grid-flow-row gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4"
|
||||
>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Software</div>
|
||||
<hr class="text-caperren-green" />
|
||||
<ul class="list-inside list-disc text-sm">
|
||||
<li>Python</li>
|
||||
<li>Test Driven Development</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Electrical</div>
|
||||
<hr class="text-caperren-green" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">Mechanical</div>
|
||||
<hr class="text-caperren-green" />
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm font-extrabold">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>
|
||||
|
||||
<h2 class="my-4 font-bold md:text-2xl">Details By Team</h2>
|
||||
<h3 class="my-4 font-bold md:text-lg">Starlink Hardware Test</h3>
|
||||
<h3 class="my-4 font-bold md:text-lg">Build Reliability Engineering</h3>
|
||||
<h3 class="my-4 font-bold md:text-lg">Components Test Infrastructure</h3>
|
||||
</ExperienceLayout>
|
||||
@@ -1,56 +1,62 @@
|
||||
---
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
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
|
||||
]
|
||||
}
|
||||
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}/>
|
||||
<p class="mt-4">
|
||||
Back when I was in college, a few of my friends and I got this crazy idea to all get RFID implants together.
|
||||
They are essentially the same things you'd use to microchip a pet, but with a slightly different firmware
|
||||
configuration, allowing scans with any 125KHz-compatible reader. The implants came from <a
|
||||
class="text-blue-500 hover:text-blue-300" href="https://dangerousthings.com/product/xem/">dangerousthings.com</a>,
|
||||
and we were lucky enough to have a vet-med student as a friend who made the installation a quick and painless
|
||||
process!
|
||||
I'm glad that I'm not afraid of needles, as the 16 gauge injector the kit came with was nothing to scoff at.
|
||||
Since healing, you would never know the implant was there, with the site leaving no scar or visible indication
|
||||
of its presence.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
With that out of the way, our group began work on hardware which would support the new implants.
|
||||
The goal was to have a generic usb-keyboard emulator for typing passwords with a valid scan, a car
|
||||
off-acc-on ignition replacement, and a fairly specialized modification to the OSU Robotics Club's doorway
|
||||
scanning system so they would support these on top of the official OSU ID cards.
|
||||
As tends to happen, life got busy, and only the usb-keyboard emulator actually came to fruition. The electronics
|
||||
and
|
||||
primary firmware were handled by <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="https://nickmccomb.net">Nick McComb</a>,
|
||||
enclosure by
|
||||
<a class="text-blue-500 hover:text-blue-300" href="https://dylanthrush.com">Dylan Thrush</a>, and I supported
|
||||
some minor firmware development and debugging. If you want to see an example of the keyboard emulator unlocking
|
||||
a PC, check out the video on <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="https://nickmccomb.net/college/printed-circuit-boards/computer-access-module">Nick's website</a>!
|
||||
</p>
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">RFID Implant</h2>
|
||||
<Carousel carouselGroup={rfidImplantCarouselGroup} />
|
||||
<p class="mt-4">
|
||||
Back when I was in college, a few of my friends and I got this crazy idea to
|
||||
all get RFID implants together. They are essentially the same things you'd
|
||||
use to microchip a pet, but with a slightly different firmware
|
||||
configuration, allowing scans with any 125KHz-compatible reader. The
|
||||
implants came from <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="https://dangerousthings.com/product/xem/">dangerousthings.com</a
|
||||
>, and we were lucky enough to have a vet-med student as a friend who made
|
||||
the installation a quick and painless process! I'm glad that I'm not afraid
|
||||
of needles, as the 16 gauge injector the kit came with was nothing to scoff
|
||||
at. Since healing, you would never know the implant was there, with the site
|
||||
leaving no scar or visible indication of its presence.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
With that out of the way, our group began work on hardware which would
|
||||
support the new implants. The goal was to have a generic usb-keyboard
|
||||
emulator for typing passwords with a valid scan, a car off-acc-on ignition
|
||||
replacement, and a fairly specialized modification to the OSU Robotics
|
||||
Club's doorway scanning system so they would support these on top of the
|
||||
official OSU ID cards. As tends to happen, life got busy, and only the
|
||||
usb-keyboard emulator actually came to fruition. The electronics and primary
|
||||
firmware were handled by <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="https://nickmccomb.net">Nick McComb</a
|
||||
>, enclosure by
|
||||
<a class="text-blue-500 hover:text-blue-300" href="https://dylanthrush.com"
|
||||
>Dylan Thrush</a
|
||||
>, and I supported some minor firmware development and debugging. If you
|
||||
want to see an example of the keyboard emulator unlocking a PC, check out
|
||||
the video on <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="https://nickmccomb.net/college/printed-circuit-boards/computer-access-module"
|
||||
>Nick's website</a
|
||||
>!
|
||||
</p>
|
||||
</HobbyLayout>
|
||||
@@ -2,5 +2,4 @@
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
---
|
||||
|
||||
<HobbyLayout title="Homelab - Home Automation">
|
||||
</HobbyLayout>
|
||||
<HobbyLayout title="Homelab - Home Automation" />
|
||||
|
||||
@@ -1,29 +1,22 @@
|
||||
---
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
import rack_from_above from "@assets/hobby/homelab/home-server-rack/rack-from-above.jpg";
|
||||
import rack_from_below from "@assets/hobby/homelab/home-server-rack/rack-from-below.jpg";
|
||||
import rack_middle from "@assets/hobby/homelab/home-server-rack/rack-middle.jpg";
|
||||
import rack_top from "@assets/hobby/homelab/home-server-rack/rack-top.jpg";
|
||||
|
||||
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
rack_from_below,
|
||||
rack_from_above,
|
||||
rack_top,
|
||||
rack_middle
|
||||
]
|
||||
}
|
||||
animation: "slide",
|
||||
images: [rack_from_below, rack_from_above, rack_top, rack_middle],
|
||||
};
|
||||
---
|
||||
|
||||
<HobbyLayout title="Homelab - Home Server Rack">
|
||||
<Carousel carouselGroup={headerCarouselGroup}/>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
|
||||
<!--<h2 class="font-bold md:text-2xl my-4">Prior Homelab</h2>-->
|
||||
<!--<h2 class="font-bold md:text-2xl my-4">Prior Homelab</h2>-->
|
||||
</HobbyLayout>
|
||||
@@ -1,23 +1,19 @@
|
||||
---
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
import cluster_and_switch from "@assets/hobby/homelab/kubernetes-cluster/cluster-and-switch.jpg";
|
||||
|
||||
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
cluster_and_switch
|
||||
]
|
||||
}
|
||||
animation: "slide",
|
||||
images: [cluster_and_switch],
|
||||
};
|
||||
---
|
||||
|
||||
<HobbyLayout title="Homelab - Kubernetes Cluster">
|
||||
<Carousel carouselGroup={headerCarouselGroup}/>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
|
||||
<!--<h2 class="font-bold md:text-2xl my-4">Prior Homelab</h2>-->
|
||||
<!--<h2 class="font-bold md:text-2xl my-4">Prior Homelab</h2>-->
|
||||
</HobbyLayout>
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
import enclosure_front from "@assets/hobby/homelab/offsite-backup-rack/enclosure-front.jpg";
|
||||
import enclosure_front_pc_panel_open from "@assets/hobby/homelab/offsite-backup-rack/enclosure-front-pc-panel-open.jpg";
|
||||
@@ -12,35 +12,32 @@ import enclosure_right from "@assets/hobby/homelab/offsite-backup-rack/enclosure
|
||||
import enclosure_with_ups from "@assets/hobby/homelab/offsite-backup-rack/enclosure-with-ups.jpg";
|
||||
import power_adapter_tray_and_dc_dc from "@assets/hobby/homelab/offsite-backup-rack/power-adapter-tray-and-dc-dc.jpg";
|
||||
import power_supply_closeup from "@assets/hobby/homelab/offsite-backup-rack/power-supply-closeup.jpg";
|
||||
import power_supply_mounting_location
|
||||
from "@assets/hobby/homelab/offsite-backup-rack/power-supply-mounting-location.jpg";
|
||||
import power_supply_mounting_location from "@assets/hobby/homelab/offsite-backup-rack/power-supply-mounting-location.jpg";
|
||||
import sata_tight_fit from "@assets/hobby/homelab/offsite-backup-rack/sata-tight-fit.jpg";
|
||||
import sff_pc_with_sata_and_usb_ssds from "@assets/hobby/homelab/offsite-backup-rack/sff-pc-with-sata-and-usb-ssds.jpg";
|
||||
import up_and_running from "@assets/hobby/homelab/offsite-backup-rack/up-and-running.png";
|
||||
|
||||
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
enclosure_front,
|
||||
enclosure_front_pc_panel_open,
|
||||
enclosure_left,
|
||||
enclosure_rear,
|
||||
enclosure_right,
|
||||
sff_pc_with_sata_and_usb_ssds,
|
||||
sata_tight_fit,
|
||||
power_adapter_tray_and_dc_dc,
|
||||
enclosure_with_ups,
|
||||
power_supply_mounting_location,
|
||||
power_supply_closeup,
|
||||
up_and_running
|
||||
]
|
||||
}
|
||||
animation: "slide",
|
||||
images: [
|
||||
enclosure_front,
|
||||
enclosure_front_pc_panel_open,
|
||||
enclosure_left,
|
||||
enclosure_rear,
|
||||
enclosure_right,
|
||||
sff_pc_with_sata_and_usb_ssds,
|
||||
sata_tight_fit,
|
||||
power_adapter_tray_and_dc_dc,
|
||||
enclosure_with_ups,
|
||||
power_supply_mounting_location,
|
||||
power_supply_closeup,
|
||||
up_and_running,
|
||||
],
|
||||
};
|
||||
---
|
||||
|
||||
<HobbyLayout title="Homelab - Offsite Backup Rack">
|
||||
<Carousel carouselGroup={headerCarouselGroup}/>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
|
||||
<!--<h2 class="font-bold md:text-2xl my-4">Prior Homelab</h2>-->
|
||||
<!--<h2 class="font-bold md:text-2xl my-4">Prior Homelab</h2>-->
|
||||
</HobbyLayout>
|
||||
@@ -1,73 +1,74 @@
|
||||
---
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import LinkButton from "@components/LinkButton.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
import bottom_fasteners_installed
|
||||
from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/bottom-fasteners-installed.jpg";
|
||||
import bottom_fasteners_installed from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/bottom-fasteners-installed.jpg";
|
||||
import closed_seam from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/closed-seam.jpg";
|
||||
import closed_top_buttons_installed
|
||||
from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/closed-top-buttons-installed.jpg";
|
||||
import inside_top_and_bottom
|
||||
from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/inside-top-and-bottom.jpg";
|
||||
import inside_top_and_bottom_buttons_installed
|
||||
from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/inside-top-and-bottom-buttons-installed.jpg";
|
||||
import inside_top_and_bottom_with_buttons
|
||||
from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/inside-top-and-bottom-with-buttons.jpg";
|
||||
import installed_on_bike_handlebars_reference
|
||||
from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/installed-on-bike-handlebars-reference.jpg";
|
||||
import installed_on_bike_riders_position
|
||||
from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/installed-on-bike-riders-position.jpg";
|
||||
import closed_top_buttons_installed from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/closed-top-buttons-installed.jpg";
|
||||
import inside_top_and_bottom from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/inside-top-and-bottom.jpg";
|
||||
import inside_top_and_bottom_buttons_installed from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/inside-top-and-bottom-buttons-installed.jpg";
|
||||
import inside_top_and_bottom_with_buttons from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/inside-top-and-bottom-with-buttons.jpg";
|
||||
import installed_on_bike_handlebars_reference from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/installed-on-bike-handlebars-reference.jpg";
|
||||
import installed_on_bike_riders_position from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/installed-on-bike-riders-position.jpg";
|
||||
import top_and_bottom from "@assets/hobby/motorcycling/custom-accessories/chubby-buttons-2-mount/top-and-bottom.jpg";
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
installed_on_bike_riders_position,
|
||||
installed_on_bike_handlebars_reference,
|
||||
closed_top_buttons_installed,
|
||||
bottom_fasteners_installed,
|
||||
closed_seam,
|
||||
inside_top_and_bottom_buttons_installed,
|
||||
inside_top_and_bottom_with_buttons,
|
||||
inside_top_and_bottom,
|
||||
top_and_bottom
|
||||
]
|
||||
}
|
||||
animation: "slide",
|
||||
images: [
|
||||
installed_on_bike_riders_position,
|
||||
installed_on_bike_handlebars_reference,
|
||||
closed_top_buttons_installed,
|
||||
bottom_fasteners_installed,
|
||||
closed_seam,
|
||||
inside_top_and_bottom_buttons_installed,
|
||||
inside_top_and_bottom_with_buttons,
|
||||
inside_top_and_bottom,
|
||||
top_and_bottom,
|
||||
],
|
||||
};
|
||||
---
|
||||
|
||||
<HobbyLayout title="Motorcycling - Chubby Buttons 2 Mount">
|
||||
<Carousel carouselGroup={headerCarouselGroup}/>
|
||||
<div class="flex items-center justify-center mt-4">
|
||||
<a class="bg-black rounded-2xl p-2 border-2 text-caperren-green border-caperren-green hover:border-caperren-green-light hover:text-caperren-green-light"
|
||||
href="https://cad.onshape.com/documents/816b0b1bef7883d4dc25c66c/v/e11fe68753e080b72015cfb8/e/3802abbd9d7b7c4d2c7ebad3">
|
||||
Onshape
|
||||
Design Files</a>
|
||||
</div>
|
||||
<p class="mt-4">
|
||||
Having ridden motorcycles since I was sixteen, and being an avid music enjoyer, I'd been looking for a way to
|
||||
improve my music listening experience while on-the-go. One large pain-point I'd always had was with controlling
|
||||
track selection and volume levels while my gloves were on, as smartphones don't respond very well to this, if at
|
||||
all. In 2023 I found out about chubby buttons, a low-power and highly water-resistant media controller
|
||||
specifically designed for use with gloves! The only problem was that it was designed to be worn on your arm
|
||||
using a strap, which isn't very practical on a motorcycle.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
Having recently gotten a 3D Printer, and having some baseline modelling skills, I purchased one, took some
|
||||
measurements, and began designing a proper mount.
|
||||
I already owned and used many 1" RAM compatible mounts and gear on the bike, so I decided to make this one
|
||||
natively
|
||||
support the ball size to use an existing clamp I had stored away. This design was the first where I decided to
|
||||
use heat-set inserts in the plastic, along with some medium-strength loctite on the fasteners, due to the
|
||||
high-vibration environment the mount would see. The print was also done using a UV resistant, high-temp rated,
|
||||
and non-water-absorbing ASA filament, as the direct expose to the elements would not allow something like cheap
|
||||
PLA to last very long.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
While my first iteration was sized appropriately and went together with no issues, the ball mount neck ended up
|
||||
snapping due to a low infill percentage. After changing that area to 100% infill, including a handful of the
|
||||
rear mount layers that it attached to, a second iteration has worked perfectly for a few years now! If you're
|
||||
interested in printing this yourself, feel free to download the model using the button under the photos!
|
||||
</p>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
<div class="mt-4 flex items-center justify-center">
|
||||
<LinkButton
|
||||
href="https://cad.onshape.com/documents/816b0b1bef7883d4dc25c66c/v/e11fe68753e080b72015cfb8/e/3802abbd9d7b7c4d2c7ebad3"
|
||||
title="Onshape CAD Design Files"
|
||||
/>
|
||||
</div>
|
||||
<p class="mt-4">
|
||||
Having ridden motorcycles since I was sixteen, and being an avid music
|
||||
enjoyer, I'd been looking for a way to improve my music listening experience
|
||||
while on-the-go. One large pain-point I'd always had was with controlling
|
||||
track selection and volume levels while my gloves were on, as smartphones
|
||||
don't respond very well to this, if at all. In 2023 I found out about chubby
|
||||
buttons, a low-power and highly water-resistant media controller
|
||||
specifically designed for use with gloves! The only problem was that it was
|
||||
designed to be worn on your arm using a strap, which isn't very practical on
|
||||
a motorcycle.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
When starting this project, I'd recently gotten a 3D Printer, so having some
|
||||
baseline modelling skills I took some measurements, and began designing a
|
||||
proper mount. I already owned and used many 1" RAM compatible mounts and
|
||||
gear on my bikes, so I decided to make this one natively support the ball
|
||||
size to use an existing clamp I had stored away. This design was the first
|
||||
where I decided to use heat-set inserts in the plastic, along with some
|
||||
medium-strength Loctite on the fasteners, due to the high-vibration
|
||||
environment the mount would see. The print was also done using a UV
|
||||
resistant, high-temp rated, and non-water-absorbing ASA filament, as the
|
||||
direct expose to the elements would not allow something like cheap PLA to
|
||||
last very long.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
While my first iteration was sized appropriately and went together with no
|
||||
issues, the ball mount neck ended up snapping due to a low infill
|
||||
percentage. After changing that area to 100% infill, including a handful of
|
||||
the rear mount layers that it attached to, a second iteration has worked
|
||||
perfectly for a few years now! If you're interested in printing this
|
||||
yourself, feel free to download the model using the button under the photos!
|
||||
</p>
|
||||
</HobbyLayout>
|
||||
@@ -1,8 +1,8 @@
|
||||
---
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
import kz750 from "@assets/hobby/motorcycling/lineup/1979-kawasaki-kz750-senior-photo.jpg";
|
||||
import ninja600 from "@assets/hobby/motorcycling/lineup/1991-kawasaki-ninja-600r.jpg";
|
||||
@@ -11,33 +11,49 @@ import drz400 from "@assets/hobby/motorcycling/lineup/2005-suzuki-drz-400.jpg";
|
||||
import fjr1300 from "@assets/hobby/motorcycling/lineup/2015-fjr-1300-mountaintop.jpg";
|
||||
import sg400 from "@assets/hobby/motorcycling/lineup/2021-csc-sg400.jpg";
|
||||
|
||||
|
||||
const fjrCarouselGroup: carouselGroup = {animation: "slide", images: [fjr1300]}
|
||||
const cscCarouselGroup: carouselGroup = {animation: "slide", images: [sg400]}
|
||||
const drzCarouselGroup: carouselGroup = {animation: "slide", images: [drz400]}
|
||||
const concoursCarouselGroup: carouselGroup = {animation: "slide", images: [concours]}
|
||||
const ninjaCarouselGroup: carouselGroup = {animation: "slide", images: [ninja600]}
|
||||
const kz750CarouselGroup: carouselGroup = {animation: "slide", images: [kz750]}
|
||||
const fjrCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [fjr1300],
|
||||
};
|
||||
const cscCarouselGroup: carouselGroup = { animation: "slide", images: [sg400] };
|
||||
const drzCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [drz400],
|
||||
};
|
||||
const concoursCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [concours],
|
||||
};
|
||||
const ninjaCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [ninja600],
|
||||
};
|
||||
const kz750CarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [kz750],
|
||||
};
|
||||
---
|
||||
|
||||
<HobbyLayout title="Motorcycling - Lineup">
|
||||
<h2 class="font-bold md:text-2xl my-4 underline">Current Lineup</h2>
|
||||
<h3 class="font-bold md:text-lg my-4 underline">2015 Yamaha FJR 1300</h3>
|
||||
<Carousel carouselGroup={fjrCarouselGroup}/>
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">Current Lineup</h2>
|
||||
<h3 class="my-4 font-bold underline md:text-lg">2015 Yamaha FJR 1300</h3>
|
||||
<Carousel carouselGroup={fjrCarouselGroup} />
|
||||
|
||||
<h3 class="font-bold md:text-lg my-4 underline">2021 CSC SG400</h3>
|
||||
<Carousel carouselGroup={cscCarouselGroup}/>
|
||||
<h3 class="my-4 font-bold underline md:text-lg">2021 CSC SG400</h3>
|
||||
<Carousel carouselGroup={cscCarouselGroup} />
|
||||
|
||||
<h2 class="font-bold md:text-2xl my-4 underline">Prior Lineup</h2>
|
||||
<h3 class="font-bold md:text-lg my-4 underline">2005 Suzuki DRZ 400</h3>
|
||||
<Carousel carouselGroup={drzCarouselGroup}/>
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">Prior Lineup</h2>
|
||||
<h3 class="my-4 font-bold underline md:text-lg">2005 Suzuki DRZ 400</h3>
|
||||
<Carousel carouselGroup={drzCarouselGroup} />
|
||||
|
||||
<h3 class="font-bold md:text-lg my-4 underline">1991 Kawasaki Concours ZG1000</h3>
|
||||
<Carousel carouselGroup={concoursCarouselGroup}/>
|
||||
<h3 class="my-4 font-bold underline md:text-lg">
|
||||
1991 Kawasaki Concours ZG1000
|
||||
</h3>
|
||||
<Carousel carouselGroup={concoursCarouselGroup} />
|
||||
|
||||
<h3 class="font-bold md:text-lg my-4 underline">1979 Kawasaki KZ750</h3>
|
||||
<Carousel carouselGroup={kz750CarouselGroup}/>
|
||||
<h3 class="my-4 font-bold underline md:text-lg">1979 Kawasaki KZ750</h3>
|
||||
<Carousel carouselGroup={kz750CarouselGroup} />
|
||||
|
||||
<h3 class="font-bold md:text-lg my-4 underline">1991 Kawasaki Ninja 600R</h3>
|
||||
<Carousel carouselGroup={ninjaCarouselGroup}/>
|
||||
<h3 class="my-4 font-bold underline md:text-lg">1991 Kawasaki Ninja 600R</h3>
|
||||
<Carousel carouselGroup={ninjaCarouselGroup} />
|
||||
</HobbyLayout>
|
||||
@@ -2,5 +2,4 @@
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
---
|
||||
|
||||
<HobbyLayout>
|
||||
</HobbyLayout>
|
||||
<HobbyLayout />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
---
|
||||
|
||||
<HobbyLayout>
|
||||
</HobbyLayout>
|
||||
<HobbyLayout />
|
||||
|
||||
@@ -2,5 +2,4 @@
|
||||
import HobbyLayout from "@layouts/HobbyLayout.astro";
|
||||
---
|
||||
|
||||
<HobbyLayout title="NixOS">
|
||||
</HobbyLayout>
|
||||
<HobbyLayout title="NixOS" />
|
||||
|
||||
@@ -1,79 +1,96 @@
|
||||
---
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import Carousel from "@components/CustomCarousel/CustomCarousel.astro";
|
||||
import BaseLayout from "../layouts/BaseLayout.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
import alaska_bike_mountain_ocean from "@assets/about/alaska-bike-mountain-ocean.jpg"
|
||||
import circ_champions from "@assets/about/circ-champions.jpg"
|
||||
import alaska_bike_mountain_ocean from "@assets/about/alaska-bike-mountain-ocean.jpg";
|
||||
import circ_champions from "@assets/about/circ-champions.jpg";
|
||||
import headshot from "@assets/about/headshot.jpg";
|
||||
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
headshot,
|
||||
alaska_bike_mountain_ocean,
|
||||
circ_champions
|
||||
]
|
||||
}
|
||||
animation: "slide",
|
||||
images: [headshot, alaska_bike_mountain_ocean, circ_champions],
|
||||
};
|
||||
---
|
||||
|
||||
<BaseLayout title="About" showTitle={false}>
|
||||
<Carousel carouselGroup={headerCarouselGroup}/>
|
||||
<h2 class="font-bold md:text-2xl my-4 underline">Who Am I</h2>
|
||||
<p>
|
||||
My name is Corwin Perren, and I'm a multi-disciplinary engineer with a <a
|
||||
class="text-blue-500 hover:text-blue-300" href="/education">degree in computer science</a> from Oregon State
|
||||
University.
|
||||
For as long as I can remember, I've been fascinated by how things work, never being shy about taking them apart
|
||||
to learn the gritty details. At a young age, I began tinkering, adding lights and fans and doorbells to the
|
||||
pretend cardboard box houses my brother and I would play in.
|
||||
Later, I learned to solder, work on vehicles and engines, install and run Linux, manage enterprise computing
|
||||
infrastructure, build and repair computers, write scripts, and by the end of high school set out with a clear
|
||||
goal for my college years.
|
||||
I wanted to learn and teach myself enough to be able to think up almost any project, encompassing all facets of
|
||||
engineering, and be capable of driving it to completion with my own skill set.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
I think young me would be very pleased by how well I managed to achieve that goal!
|
||||
Through college, I learned electronics and PCB design, embedded and pc programming, basic mechanical design and
|
||||
fabrication, on top of learning how to work well with others in a team.
|
||||
I quickly realized that robotics was an ideal focus due to its inherent multi-disciplinary nature, and joined
|
||||
the OSU Robotics Club, which introduced me to people who are still my best friends today.
|
||||
Through student engineering jobs, I had the unique opportunity to work on some incredible projects such as the
|
||||
<a class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler">robotic oceanographic
|
||||
surface sampler</a> and an <a class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/osu-sinnhuber-aquatic-research-laboratory/zebrafish-embryo-pick-and-plate">embryo
|
||||
pick-and-plate machine</a>.
|
||||
One my my proudest moments was when our club's mars rover took first place at the Candian International Rover
|
||||
Challenge in 2018, for which I was the <a class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/osu-robotics-club/mars-rover-software-team-lead">software
|
||||
lead</a>!
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
After a short three-month <a class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/spacex/avionics-test-engineering-internship">internship</a> at
|
||||
SpaceX in Hawthorne at the end of college, I applied for a <a class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/spacex/hardware-test-engineer-i-ii">test
|
||||
engineering</a> position with the company's Starlink team and was hired in mid-2019.
|
||||
For six years, I developed test system hardware, software, harnesses, mechanical fixtures, devops
|
||||
infrastructure, websites, and tooling to ensure that Starlink, Falcon, Dragon, and Starship component tests were
|
||||
producing well-validated and reliable hardware.
|
||||
Through it all, I got to apply and hone every skill I had developed, while learning countless more.
|
||||
Now though, it's on to the next adventure, whatever that may be!
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
To learn more about my experiences, hobbies, interests, and skills, feel free to explore the site!
|
||||
While the short summary above provides some insight into who I am, it leaves out plenty!
|
||||
For example, I've been an avid <a class="text-blue-500 hover:text-blue-300" href="/hobby/motorcycling/lineup">motorcycle
|
||||
rider</a> since I was sixteen, and have an <a class="text-blue-500 hover:text-blue-300" href="/hobby/body-mods">rfid
|
||||
implant</a> in my hand!
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
If you're interested in contacting me, feel free to message on <a class="text-blue-500 hover:text-blue-300"
|
||||
href="https://github.com/caperren">LinkedIn</a>,
|
||||
or via the primary contact methods
|
||||
on my <a class="text-blue-500 hover:text-blue-300" href="/resume/2025-11-10-infrastructure-engineer">resume</a>.
|
||||
</p>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
<h2 class="my-4 font-bold underline md:text-2xl">Who Am I</h2>
|
||||
<p>
|
||||
My name is Corwin Perren, and I'm a multi-disciplinary engineer with a <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/education">degree in computer science</a
|
||||
> from Oregon State University. For as long as I can remember, I've been fascinated
|
||||
by how things work, never being shy about taking them apart to learn the gritty
|
||||
details. At a young age, I began tinkering, adding lights and fans and doorbells
|
||||
to the pretend cardboard box houses my brother and I would play in. Later, I learned
|
||||
to solder, work on vehicles and engines, install and run Linux, manage enterprise
|
||||
computing infrastructure, build and repair computers, write scripts, and by the
|
||||
end of high school set out with a clear goal for my college years. I wanted to
|
||||
learn and teach myself enough to be able to think up almost any project, encompassing
|
||||
all facets of engineering, and be capable of driving it to completion with my
|
||||
own skill set.
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
I think young me would be very pleased by how well I managed to achieve that
|
||||
goal! Through college, I learned electronics and PCB design, embedded and pc
|
||||
programming, basic mechanical design and fabrication, on top of learning how
|
||||
to work well with others in a team. I quickly realized that robotics was an
|
||||
ideal focus due to its inherent multi-disciplinary nature, and joined the
|
||||
OSU Robotics Club, which introduced me to people who are still my best
|
||||
friends today. Through student engineering jobs, I had the unique
|
||||
opportunity to work on some incredible projects such as the
|
||||
<a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler"
|
||||
>robotic oceanographic surface sampler</a
|
||||
> and an <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/osu-sinnhuber-aquatic-research-laboratory/zebrafish-embryo-pick-and-plate"
|
||||
>embryo pick-and-plate machine</a
|
||||
>. One my my proudest moments was when our club's mars rover took first
|
||||
place at the Candian International Rover Challenge in 2018, for which I was
|
||||
the <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/osu-robotics-club/mars-rover-software-team-lead"
|
||||
>software lead</a
|
||||
>!
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
After a short three-month <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/spacex/avionics-test-engineering-internship"
|
||||
>internship</a
|
||||
> at SpaceX in Hawthorne at the end of college, I applied for a <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/experience/spacex/hardware-test-engineer-i-ii">test engineering</a
|
||||
> position with the company's Starlink team and was hired in mid-2019. For six
|
||||
years, I developed test system hardware, software, harnesses, mechanical fixtures,
|
||||
devops infrastructure, websites, and tooling to ensure that Starlink, Falcon,
|
||||
Dragon, and Starship component tests were producing well-validated and reliable
|
||||
hardware. Through it all, I got to apply and hone every skill I had developed,
|
||||
while learning countless more. Now though, it's on to the next adventure, whatever
|
||||
that may be!
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
To learn more about my experiences, hobbies, interests, and skills, feel
|
||||
free to explore the site! While the short summary above provides some
|
||||
insight into who I am, it leaves out plenty! For example, I've been an avid <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/hobby/motorcycling/lineup">motorcycle rider</a
|
||||
> since I was sixteen, and have an <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/hobby/body-mods">rfid implant</a
|
||||
> in my hand!
|
||||
</p>
|
||||
<p class="mt-4">
|
||||
If you're interested in contacting me, feel free to message on <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="https://github.com/caperren">LinkedIn</a
|
||||
>, or via the primary contact methods on my <a
|
||||
class="text-blue-500 hover:text-blue-300"
|
||||
href="/resume/2025-11-10-infrastructure-engineer">resume</a
|
||||
>.
|
||||
</p>
|
||||
</BaseLayout>
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import ResumeLayout from "@layouts/ResumeLayout.astro";
|
||||
import resume from "@assets/resume/corwin_perren_2019-07-01_hardware_test_engineer.pdf"
|
||||
import resume from "@assets/resume/corwin_perren_2019-07-01_hardware_test_engineer.pdf";
|
||||
---
|
||||
|
||||
<ResumeLayout title="2019-07-01 - Hardware Test Engineer" resume={resume}/>
|
||||
<ResumeLayout title="2019-07-01 - Hardware Test Engineer" resume={resume} />
|
||||
|
||||
@@ -2,4 +2,4 @@
|
||||
import ResumeLayout from "@layouts/ResumeLayout.astro";
|
||||
---
|
||||
|
||||
<ResumeLayout/>
|
||||
<ResumeLayout />
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
import ResumeLayout from "@layouts/ResumeLayout.astro";
|
||||
import resume from "@assets/resume/corwin_perren_2025-10-27-infrastructure_engineer.pdf"
|
||||
import resume from "@assets/resume/corwin_perren_2025-10-27-infrastructure_engineer.pdf";
|
||||
---
|
||||
|
||||
<ResumeLayout title="2025-10-27 - Infrastructure Engineer" resume={resume}/>
|
||||
<ResumeLayout title="2025-10-27 - Infrastructure Engineer" resume={resume} />
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type {APIRoute} from 'astro';
|
||||
import type { APIRoute } from "astro";
|
||||
|
||||
const getRobotsTxt = (sitemapURL: URL) => `\
|
||||
User-agent: *
|
||||
@@ -7,7 +7,7 @@ Allow: /
|
||||
Sitemap: ${sitemapURL.href}
|
||||
`;
|
||||
|
||||
export const GET: APIRoute = ({site}) => {
|
||||
const sitemapURL = new URL('sitemap-index.xml', site);
|
||||
return new Response(getRobotsTxt(sitemapURL));
|
||||
export const GET: APIRoute = ({ site }) => {
|
||||
const sitemapURL = new URL("sitemap-index.xml", site);
|
||||
return new Response(getRobotsTxt(sitemapURL));
|
||||
};
|
||||
@@ -5,9 +5,9 @@
|
||||
@source "../../node_modules/flowbite";
|
||||
|
||||
@theme {
|
||||
--default-font-family: font-mono;
|
||||
--default-font-family: font-mono;
|
||||
|
||||
--color-caperren-green: #10ac25;
|
||||
--color-caperren-green-light: #00ff2a;
|
||||
--color-caperren-green-dark: #06370e;
|
||||
--color-caperren-green: #10ac25;
|
||||
--color-caperren-green-light: #00ff2a;
|
||||
--color-caperren-green-dark: #06370e;
|
||||
}
|
||||
|
||||
@@ -1,16 +1,15 @@
|
||||
import {test, expect} from '@playwright/test';
|
||||
|
||||
import {getPaths} from "@data/site-layout.ts";
|
||||
import { test, expect } from "@playwright/test";
|
||||
|
||||
import { getPaths } from "@data/site-layout.ts";
|
||||
|
||||
for (const pagePath of getPaths()) {
|
||||
test(`${pagePath}: Navigable`, async ({page}) => {
|
||||
const response = await page.request.get(pagePath);
|
||||
await expect(response).toBeOK();
|
||||
});
|
||||
test(`${pagePath}: Navigable`, async ({ page }) => {
|
||||
const response = await page.request.get(pagePath);
|
||||
await expect(response).toBeOK();
|
||||
});
|
||||
|
||||
test(`${pagePath}: Has Title`, async ({page}) => {
|
||||
await page.goto(pagePath);
|
||||
expect(await page.title()).not.toBe("Corwin Perren")
|
||||
});
|
||||
test(`${pagePath}: Has Title`, async ({ page }) => {
|
||||
await page.goto(pagePath);
|
||||
expect(await page.title()).not.toBe("Corwin Perren");
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,39 +1,45 @@
|
||||
import {expect, test} from "vitest";
|
||||
import { expect, test } from "vitest";
|
||||
|
||||
import {siteLayout, getPaths} from "@data/site-layout.ts";
|
||||
import { siteLayout, getPaths } from "@data/site-layout.ts";
|
||||
|
||||
export const setDifference = <T>(a: Set<T>, b: Set<T>) =>
|
||||
new Set([...a].filter(x => !b.has(x)));
|
||||
new Set([...a].filter((x) => !b.has(x)));
|
||||
|
||||
// Paths that should be known to Astro statically
|
||||
const astroStaticPaths = new Set(
|
||||
Object.keys(import.meta.glob("/src/pages/**/*.astro"))
|
||||
.map((path) =>
|
||||
path
|
||||
.replace("/src/pages", "")
|
||||
.replace(/index\.astro$/, "")
|
||||
.replace(/\.astro$|\.md$/, "")
|
||||
.replace(/\/$/, "")
|
||||
|| "/"
|
||||
));
|
||||
Object.keys(import.meta.glob("/src/pages/**/*.astro")).map(
|
||||
(path) =>
|
||||
path
|
||||
.replace("/src/pages", "")
|
||||
.replace(/index\.astro$/, "")
|
||||
.replace(/\.astro$|\.md$/, "")
|
||||
.replace(/\/$/, "") || "/",
|
||||
),
|
||||
);
|
||||
|
||||
// Paths that exist in the site layout
|
||||
const siteLayoutPaths = new Set([...getPaths(siteLayout), ...getPaths(siteLayout, [], true)]);
|
||||
const siteLayoutPaths = new Set([
|
||||
...getPaths(siteLayout),
|
||||
...getPaths(siteLayout, [], true),
|
||||
]);
|
||||
|
||||
test('Astro Paths Not Empty', () => {
|
||||
expect(astroStaticPaths).not.toHaveLength(0);
|
||||
test("Astro Paths Not Empty", () => {
|
||||
expect(astroStaticPaths).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Site Layout Paths Not Empty', () => {
|
||||
expect(siteLayoutPaths).not.toHaveLength(0);
|
||||
test("Site Layout Paths Not Empty", () => {
|
||||
expect(siteLayoutPaths).not.toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Pages Missing from Site Layout', () => {
|
||||
const astroNotLayoutPaths = setDifference(astroStaticPaths, siteLayoutPaths);
|
||||
expect(astroNotLayoutPaths).toHaveLength(0);
|
||||
test("Pages Missing from Site Layout", () => {
|
||||
const astroNotLayoutPaths = setDifference(astroStaticPaths, siteLayoutPaths);
|
||||
expect(astroNotLayoutPaths).toHaveLength(0);
|
||||
});
|
||||
|
||||
test('Pages Missing from Astro Paths', () => {
|
||||
const siteLayoutNotAstroPaths = setDifference(siteLayoutPaths, astroStaticPaths);
|
||||
expect(siteLayoutNotAstroPaths).toHaveLength(0);
|
||||
test("Pages Missing from Astro Paths", () => {
|
||||
const siteLayoutNotAstroPaths = setDifference(
|
||||
siteLayoutPaths,
|
||||
astroStaticPaths,
|
||||
);
|
||||
expect(siteLayoutNotAstroPaths).toHaveLength(0);
|
||||
});
|
||||
|
||||
@@ -1,32 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"paths": {
|
||||
"@assets/*": [
|
||||
"./src/assets/*"
|
||||
],
|
||||
"@components/*": [
|
||||
"./src/components/*"
|
||||
],
|
||||
"@data/*": [
|
||||
"./src/data/*"
|
||||
],
|
||||
"@interfaces/*": [
|
||||
"./src/interfaces/*"
|
||||
],
|
||||
"@layouts/*": [
|
||||
"./src/layouts/*"
|
||||
],
|
||||
"@styles/*": [
|
||||
"./src/styles/*"
|
||||
]
|
||||
"@assets/*": ["./src/assets/*"],
|
||||
"@components/*": ["./src/components/*"],
|
||||
"@data/*": ["./src/data/*"],
|
||||
"@interfaces/*": ["./src/interfaces/*"],
|
||||
"@layouts/*": ["./src/layouts/*"],
|
||||
"@styles/*": ["./src/styles/*"]
|
||||
}
|
||||
},
|
||||
"extends": "astro/tsconfigs/strict",
|
||||
"include": [
|
||||
".astro/types.d.ts",
|
||||
"**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"dist"
|
||||
]
|
||||
"include": [".astro/types.d.ts", "**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
import path from 'path';
|
||||
import {getViteConfig} from 'astro/config';
|
||||
import path from "path";
|
||||
import { getViteConfig } from "astro/config";
|
||||
|
||||
export default getViteConfig(
|
||||
{
|
||||
// @ts-ignore
|
||||
test: {
|
||||
exclude: [
|
||||
"test-e2e/**",
|
||||
"node_modules/**",
|
||||
],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src')
|
||||
},
|
||||
},
|
||||
{
|
||||
// @ts-ignore
|
||||
test: {
|
||||
exclude: ["test-e2e/**", "node_modules/**"],
|
||||
resolve: {
|
||||
alias: {
|
||||
"@": path.resolve(__dirname, "./src"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
site: 'https://caperren.com/'
|
||||
},
|
||||
},
|
||||
{
|
||||
site: "https://caperren.com/",
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user