Merge pull request 'More website content updates' (#10) from website-content-updates into main
Reviewed-on: #10
@@ -1,7 +1,17 @@
|
||||
.DS_Store
|
||||
|
||||
.idea
|
||||
.astro
|
||||
.gitea/
|
||||
.astro/
|
||||
.idea/
|
||||
|
||||
*/dist/
|
||||
*/build/
|
||||
*/node_modules/
|
||||
*/node_modules/
|
||||
*/playwright-report/
|
||||
*/test-results/
|
||||
|
||||
.gitignore
|
||||
Dockerfile
|
||||
Makefile
|
||||
new-words.txt
|
||||
README.md
|
||||
7
.editorconfig
Normal file
@@ -0,0 +1,7 @@
|
||||
[*]
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
max_line_length = 80
|
||||
@@ -1,27 +1,27 @@
|
||||
name: Playwright Tests
|
||||
on:
|
||||
push:
|
||||
branches: [ main, master ]
|
||||
branches: [main, master]
|
||||
pull_request:
|
||||
branches: [ main, master ]
|
||||
branches: [main, master]
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 60
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npx playwright test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: lts/*
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
- name: Install Playwright Browsers
|
||||
run: npx playwright install --with-deps
|
||||
- name: Run Playwright tests
|
||||
run: npx playwright test
|
||||
- uses: actions/upload-artifact@v4
|
||||
if: ${{ !cancelled() }}
|
||||
with:
|
||||
name: playwright-report
|
||||
path: playwright-report/
|
||||
retention-days: 30
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -98,4 +104,4 @@ jobs:
|
||||
-H 'accept: */*' \
|
||||
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '"caperren-com"'
|
||||
-d '"caperren-com"'
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
name: Build and Test - Staging
|
||||
on:
|
||||
pull_request:
|
||||
types: [ opened, synchronize, reopened ]
|
||||
types: [opened, synchronize, reopened]
|
||||
|
||||
jobs:
|
||||
test:
|
||||
@@ -12,16 +12,24 @@ jobs:
|
||||
project_version: ${{ steps.project_metadata.outputs.PROJECT_VERSION }}
|
||||
steps:
|
||||
- name: Checkout Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
|
||||
- name: Setup Node Environment
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: ">=22.20"
|
||||
|
||||
- name: Setup Project Dependencies
|
||||
run: |
|
||||
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
|
||||
|
||||
@@ -42,7 +50,7 @@ jobs:
|
||||
needs: test
|
||||
steps:
|
||||
- name: Checkout caperren-com Repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v5
|
||||
with:
|
||||
path: caperren-com
|
||||
|
||||
@@ -57,7 +65,7 @@ jobs:
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
- name: Build and Push
|
||||
uses: docker/build-push-action@v5
|
||||
uses: docker/build-push-action@v6
|
||||
with:
|
||||
context: caperren-com
|
||||
push: true
|
||||
@@ -98,4 +106,4 @@ jobs:
|
||||
-H 'accept: */*' \
|
||||
-H 'Authorization: Bearer ${{ secrets.TRUENAS_CAPERRENCOM_API_KEY }}' \
|
||||
-H 'Content-Type: application/json' \
|
||||
-d '"caperren-com-stg"'
|
||||
-d '"caperren-com-stg"'
|
||||
|
||||
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
@@ -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
|
||||
29
.prettierrc
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"astroOrganizeImportsMode": "All",
|
||||
"importOrder": [
|
||||
"^@core/(.*)$",
|
||||
"^@server/(.*)$",
|
||||
"^@ui/(.*)$",
|
||||
"^@layouts/(.*)$",
|
||||
"^@components/(.*)$",
|
||||
"^@interfaces/(.*)$",
|
||||
"^@assets/(.*)$",
|
||||
"^[./]"
|
||||
],
|
||||
"importOrderSeparation": true,
|
||||
"importOrderSortSpecifiers": true,
|
||||
"plugins": [
|
||||
"prettier-plugin-astro",
|
||||
"prettier-plugin-tailwindcss",
|
||||
"@trivago/prettier-plugin-sort-imports",
|
||||
"prettier-plugin-astro-organize-imports"
|
||||
],
|
||||
"overrides": [
|
||||
{
|
||||
"files": "*.astro",
|
||||
"options": {
|
||||
"parser": "astro"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
33
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
|
||||
@@ -26,8 +51,4 @@ COPY --from=build /app/dist /usr/share/nginx/html
|
||||
|
||||
RUN chown -R nginx:nginx /usr/share/nginx/html
|
||||
|
||||
#COPY entrypoint.sh /entrypoint.sh
|
||||
|
||||
EXPOSE 80
|
||||
|
||||
#ENTRYPOINT ["/entrypoint.sh"]
|
||||
EXPOSE 80
|
||||
63
Makefile
@@ -6,7 +6,16 @@
|
||||
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 \
|
||||
convert_video \
|
||||
convert_video_times
|
||||
|
||||
default: dev
|
||||
|
||||
@@ -28,3 +37,55 @@ 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
|
||||
|
||||
convert_video:
|
||||
ffmpeg \
|
||||
-init_hw_device vaapi=va:/dev/dri/renderD128 \
|
||||
-filter_hw_device va \
|
||||
-i $(input) \
|
||||
-vf 'format=nv12,hwupload,scale_vaapi=-2:720' \
|
||||
-c:v h264_vaapi \
|
||||
-rc_mode CQP \
|
||||
-qp 28 \
|
||||
-an \
|
||||
$(extra_args) \
|
||||
$(output)
|
||||
|
||||
convert_video_times:
|
||||
ffmpeg \
|
||||
-init_hw_device vaapi=va:/dev/dri/renderD128 \
|
||||
-filter_hw_device va \
|
||||
-i $(input) \
|
||||
-vf 'format=nv12,hwupload,scale_vaapi=-2:720,trim=start=$(start):end=$(end)' \
|
||||
-c:v h264_vaapi \
|
||||
-rc_mode CQP \
|
||||
-qp 28 \
|
||||
-an \
|
||||
$(output)
|
||||
|
||||
49
README.md
@@ -1,48 +1,3 @@
|
||||
# Astro Starter Kit: Basics
|
||||
# Corwin Perren's Personal Portfolio Website
|
||||
|
||||
```sh
|
||||
npm create astro@latest -- --template basics
|
||||
```
|
||||
|
||||
[](https://stackblitz.com/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codesandbox.io/p/sandbox/github/withastro/astro/tree/latest/examples/basics)
|
||||
[](https://codespaces.new/withastro/astro?devcontainer_path=.devcontainer/basics/devcontainer.json)
|
||||
|
||||
> 🧑🚀 **Seasoned astronaut?** Delete this file. Have fun!
|
||||
|
||||

|
||||
|
||||
## 🚀 Project Structure
|
||||
|
||||
Inside of your Astro project, you'll see the following folders and files:
|
||||
|
||||
```text
|
||||
/
|
||||
├── public/
|
||||
│ └── favicon.svg
|
||||
├── src/
|
||||
│ ├── layouts/
|
||||
│ │ └── Layout.astro
|
||||
│ └── pages/
|
||||
│ └── index.astro
|
||||
└── package.json
|
||||
```
|
||||
|
||||
To learn more about the folder structure of an Astro project, refer to [our guide on project structure](https://docs.astro.build/en/basics/project-structure/).
|
||||
|
||||
## 🧞 Commands
|
||||
|
||||
All commands are run from the root of the project, from a terminal:
|
||||
|
||||
| Command | Action |
|
||||
| :------------------------ | :----------------------------------------------- |
|
||||
| `npm install` | Installs dependencies |
|
||||
| `npm run dev` | Starts local dev server at `localhost:4321` |
|
||||
| `npm run build` | Build your production site to `./dist/` |
|
||||
| `npm run preview` | Preview your build locally, before deploying |
|
||||
| `npm run astro ...` | Run CLI commands like `astro add`, `astro check` |
|
||||
| `npm run astro -- --help` | Get help using the Astro CLI |
|
||||
|
||||
## 👀 Want to learn more?
|
||||
|
||||
Feel free to check [our documentation](https://docs.astro.build) or jump into our [Discord server](https://astro.build/chat).
|
||||
Check the Makefile and/or package.json for the commands needed to build and run this project.
|
||||
|
||||
@@ -1,25 +1,31 @@
|
||||
// @ts-check
|
||||
import {defineConfig} from 'astro/config';
|
||||
import sitemap from "@astrojs/sitemap";
|
||||
import tailwindcss from "@tailwindcss/vite";
|
||||
import { defineConfig } from "astro/config";
|
||||
|
||||
import {siteLayout, getPaths} from "./src/data/site-layout.ts";
|
||||
// We don't have access to short imports this early in the build chain
|
||||
// noinspection ES6PreferShortImport
|
||||
import { getPaths, siteLayout } 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: [tailwindcss()],
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
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"
|
||||
]
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
#!/usr/bin/env sh
|
||||
set -e
|
||||
|
||||
# Generate a unique token per container launch
|
||||
TOKEN="$(cat /proc/sys/kernel/random/uuid)" # or: TOKEN="$(date +%s%N)"
|
||||
|
||||
# Write it into a file NGINX will include
|
||||
cat >/etc/nginx/conf.d/_release_token.conf <<EOF
|
||||
# auto-generated at container start
|
||||
set \$release_token "$TOKEN";
|
||||
EOF
|
||||
|
||||
# Then exec nginx
|
||||
exec nginx -g 'daemon off;'
|
||||
@@ -14,12 +14,6 @@ http {
|
||||
root /usr/share/nginx/html;
|
||||
index index.html index.htm;
|
||||
include /etc/nginx/mime.types;
|
||||
#include /etc/nginx/conf.d/_release_token.conf;
|
||||
|
||||
#etag off;
|
||||
|
||||
#add_header ETag "\"W/$release_token\"" always;
|
||||
#add_header Cache-Control "max-age=0, must-revalidate" always;
|
||||
|
||||
gzip on;
|
||||
gzip_proxied any;
|
||||
|
||||
2078
package-lock.json
generated
@@ -1,7 +1,6 @@
|
||||
{
|
||||
"name": "caperren-com",
|
||||
"type": "module",
|
||||
"version": "0.0.1",
|
||||
"scripts": {
|
||||
"dev": "astro dev",
|
||||
"dev-hosted": "astro dev --host",
|
||||
@@ -14,7 +13,7 @@
|
||||
"dependencies": {
|
||||
"@astrojs/sitemap": "^3.6.0",
|
||||
"@tailwindcss/vite": "^4.1.11",
|
||||
"astro": "^5.15.4",
|
||||
"astro": "^5.16.3",
|
||||
"flowbite": "^3.1.2",
|
||||
"leader-line-new": "^1.1.9",
|
||||
"luxon": "^3.7.2",
|
||||
@@ -23,8 +22,14 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@playwright/test": "^1.56.1",
|
||||
"@trivago/prettier-plugin-sort-imports": "^6.0.0",
|
||||
"@types/luxon": "^3.7.1",
|
||||
"@types/node": "^24.10.0",
|
||||
"cspell": "^9.3.2",
|
||||
"prettier": "3.7.3",
|
||||
"prettier-plugin-astro": "0.14.1",
|
||||
"prettier-plugin-astro-organize-imports": "^0.4.11",
|
||||
"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,
|
||||
},
|
||||
});
|
||||
|
||||
51
project-words.txt
Normal file
@@ -0,0 +1,51 @@
|
||||
ADCP
|
||||
ASSEM
|
||||
astrojs
|
||||
Candian
|
||||
caperren
|
||||
CEOAS
|
||||
COMSC
|
||||
Concours
|
||||
CONSERV
|
||||
Corwin
|
||||
dangerousthings
|
||||
Dechorionator
|
||||
fhhs
|
||||
flowbite
|
||||
HDFS
|
||||
headshot
|
||||
Homelab
|
||||
hwupload
|
||||
iceops
|
||||
ITAR
|
||||
Jetson
|
||||
leconte
|
||||
Loctite
|
||||
luxon
|
||||
MGMT
|
||||
nixos
|
||||
offroad
|
||||
Onshape
|
||||
OSSM
|
||||
OSURC
|
||||
Perren
|
||||
Perren's
|
||||
pubpath
|
||||
RFID
|
||||
RSSI
|
||||
SARL
|
||||
Shuttlebox
|
||||
sinnhuber
|
||||
sitemapindex
|
||||
ssds
|
||||
Starlink
|
||||
steller
|
||||
Steller
|
||||
timelapse
|
||||
trivago
|
||||
Unstow
|
||||
uuidv
|
||||
vaapi
|
||||
vitest
|
||||
Zebrafish
|
||||
zscan
|
||||
BIN
public/16x16-favicon.png
Normal file
|
After Width: | Height: | Size: 308 B |
BIN
public/180x180-favicon.png
Normal file
|
After Width: | Height: | Size: 3.9 KiB |
BIN
public/192x192-favicon.png
Normal file
|
After Width: | Height: | Size: 4.5 KiB |
BIN
public/32x32-favicon.png
Normal file
|
After Width: | Height: | Size: 500 B |
BIN
public/48x48-favicon.png
Normal file
|
After Width: | Height: | Size: 785 B |
BIN
public/512x512-favicon.png
Normal file
|
After Width: | Height: | Size: 18 KiB |
|
Before Width: | Height: | Size: 662 B |
BIN
src/assets/about/headshot.jpg
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
|
Before Width: | Height: | Size: 956 KiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 5.1 MiB |
|
After Width: | Height: | Size: 2.5 MiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
After Width: | Height: | Size: 5.1 MiB |
|
After Width: | Height: | Size: 6.8 MiB |
|
After Width: | Height: | Size: 5.0 MiB |
|
After Width: | Height: | Size: 2.7 MiB |
|
After Width: | Height: | Size: 3.3 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 851 KiB |
|
After Width: | Height: | Size: 2.6 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 902 KiB |
|
After Width: | Height: | Size: 2.3 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 1.9 MiB |
@@ -1,151 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||
|
||||
<svg
|
||||
class="logo-title"
|
||||
width="21.129419mm"
|
||||
height="9.3295746mm"
|
||||
viewBox="0 0 74.868021 33.057548"
|
||||
id="svg4376"
|
||||
version="1.1"
|
||||
inkscape:version="1.4.2 (ebf0e940d0, 2025-05-08)"
|
||||
sodipodi:docname="logo-title.svg"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<defs
|
||||
id="defs4378" />
|
||||
<sodipodi:namedview
|
||||
id="base"
|
||||
pagecolor="#000000"
|
||||
bordercolor="#666666"
|
||||
borderopacity="1.0"
|
||||
inkscape:pageopacity="1"
|
||||
inkscape:pageshadow="2"
|
||||
inkscape:zoom="11.313709"
|
||||
inkscape:cx="329.77692"
|
||||
inkscape:cy="530.2417"
|
||||
inkscape:document-units="px"
|
||||
inkscape:current-layer="layer2"
|
||||
showgrid="false"
|
||||
inkscape:window-width="2546"
|
||||
inkscape:window-height="1386"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:showpageshadow="true"
|
||||
inkscape:pagecheckerboard="1"
|
||||
inkscape:deskcolor="#505050"
|
||||
inkscape:export-bgcolor="#ffffff00">
|
||||
<inkscape:page
|
||||
x="0"
|
||||
y="0"
|
||||
width="74.868019"
|
||||
height="33.057549"
|
||||
id="page2"
|
||||
margin="0"
|
||||
bleed="0" />
|
||||
</sodipodi:namedview>
|
||||
<metadata
|
||||
id="metadata4381">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<g
|
||||
inkscape:label="Layer 1"
|
||||
inkscape:groupmode="layer"
|
||||
id="layer1"
|
||||
transform="translate(-262.90298,-476.37904)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="279.60336"
|
||||
y="517.86682"
|
||||
id="text4159-4"
|
||||
transform="scale(1.0283363,0.97244452)"
|
||||
inkscape:export-xdpi="120.07"
|
||||
inkscape:export-ydpi="120.07"
|
||||
inkscape:export-filename="text4159-4.svg"><tspan
|
||||
sodipodi:role="line"
|
||||
x="279.60336"
|
||||
y="517.86682"
|
||||
style="font-size:15.425px;line-height:1.25"
|
||||
id="tspan4167-2">Perren</tspan></text>
|
||||
</g>
|
||||
<g
|
||||
inkscape:groupmode="layer"
|
||||
id="layer2"
|
||||
inkscape:label="Layer 2"
|
||||
transform="translate(-262.90298,-476.37904)">
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="260.65689"
|
||||
y="506.06656"
|
||||
id="text3344-4-5"
|
||||
inkscape:export-xdpi="120.07"
|
||||
inkscape:export-ydpi="120.07"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3346-7-6"
|
||||
x="260.65689"
|
||||
y="506.06656"
|
||||
style="font-size:40px;line-height:1.25">C</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="270.34253"
|
||||
y="498.55893"
|
||||
id="text3348-1-8"
|
||||
transform="scale(1.0028223,0.99718564)"
|
||||
inkscape:export-xdpi="120.07"
|
||||
inkscape:export-ydpi="120.07"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3350-3-1"
|
||||
x="270.34253"
|
||||
y="498.55893"
|
||||
style="font-size:16.283px;line-height:1.25">A</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-variant:normal;font-weight:normal;font-stretch:normal;line-height:0%;font-family:sans-serif;-inkscape-font-specification:'sans-serif, Normal';text-align:start;letter-spacing:0px;word-spacing:0px;writing-mode:lr-tb;text-anchor:start;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="96.614212"
|
||||
y="537.43079"
|
||||
id="text3344-5-7-6"
|
||||
transform="matrix(0.88187751,-0.35574603,0.36069903,0.98843971,0,0)"
|
||||
inkscape:export-xdpi="120.07"
|
||||
inkscape:export-ydpi="120.07"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan3370-7-8"
|
||||
x="96.614212"
|
||||
y="537.43079"
|
||||
style="font-size:14.587px;line-height:1.25">P</tspan></text>
|
||||
<text
|
||||
xml:space="preserve"
|
||||
style="font-style:normal;font-weight:normal;line-height:0%;font-family:sans-serif;letter-spacing:0px;word-spacing:0px;fill:#10ac25;fill-opacity:1;stroke:none;stroke-width:1px;stroke-linecap:butt;stroke-linejoin:miter;stroke-opacity:1"
|
||||
x="287.28516"
|
||||
y="490.68661"
|
||||
id="text4159"
|
||||
inkscape:export-xdpi="120.07"
|
||||
inkscape:export-ydpi="120.07"><tspan
|
||||
sodipodi:role="line"
|
||||
id="tspan4161"
|
||||
x="287.28516"
|
||||
y="490.68661"
|
||||
style="font-size:15px;line-height:1.25">Corwin</tspan><tspan
|
||||
sodipodi:role="line"
|
||||
x="287.28516"
|
||||
y="509.43661"
|
||||
style="font-size:15px;line-height:1.25"
|
||||
id="tspan4167"> </tspan></text>
|
||||
</g>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 5.6 KiB |
@@ -1,96 +0,0 @@
|
||||
---
|
||||
import {Image} from 'astro:assets';
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
|
||||
const groupToShow: carouselGroup = Astro.props.carouselGroup;
|
||||
|
||||
const limitByHeightClasses = "sm:max-w-xl md:max-w-3xl lg:max-w-5xl xl:max-w-7xl";
|
||||
const limitByWidthClasses = "max-h-fit";
|
||||
---
|
||||
|
||||
<custom-carousel class="flex flex-col relative w-full"
|
||||
data-custom-carousel={groupToShow.animation}
|
||||
data-custom-carousel-interval={groupToShow.interval}>
|
||||
|
||||
<!-- Carousel wrapper -->
|
||||
<div class="relative overflow-hidden w-full h-56 md:h-120">
|
||||
{
|
||||
groupToShow.images.map((image, index) => (
|
||||
<div class="hidden duration-700 ease-in-out" data-custom-carousel-item={index}>
|
||||
<Image src={image}
|
||||
class="absolute inset-0 m-auto h-full w-auto max-h-full max-w-full object-contain"
|
||||
alt="..."
|
||||
layout='constrained'
|
||||
loading="eager"/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
{(groupToShow.images.length > 1) && (
|
||||
<!-- Slider indicators -->
|
||||
<div class="absolute z-30 flex -translate-x-1/2 bottom-2 left-1/2 space-x-3 rounded-full">
|
||||
{
|
||||
groupToShow.images.map((_, index) => (
|
||||
<button type="button"
|
||||
class="w-3 h-3 rounded-full bg-black hover:bg-caperren-green-light"
|
||||
aria-current={index ? "false" : "true"}
|
||||
aria-label={index.toString()}
|
||||
data-custom-carousel-slide-to={index}></button>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
<!-- Slider controls -->
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-0 start-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
|
||||
data-custom-carousel-prev
|
||||
>
|
||||
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full ring-2 ring-caperren-green/25 bg-black/25 group-hover:bg-black/75">
|
||||
<svg
|
||||
class="h-4 w-4 text-caperren-green group-hover:text-caperren-green-light"
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 6 10"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="M5 1 1 5l4 4"
|
||||
/>
|
||||
</svg>
|
||||
<span class="hidden">Previous</span>
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
type="button"
|
||||
class="absolute top-0 end-0 z-30 flex items-center justify-center h-full px-4 cursor-pointer group focus:outline-none"
|
||||
data-custom-carousel-next
|
||||
>
|
||||
<span class="inline-flex items-center justify-center w-10 h-10 rounded-full ring-2 ring-caperren-green/25 bg-black/25 group-hover:bg-black/75">
|
||||
<svg
|
||||
class="h-4 w-4 text-caperren-green group-hover:text-caperren-green-light "
|
||||
aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 6 10"
|
||||
>
|
||||
<path
|
||||
stroke="currentColor"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 9 4-4-4-4"
|
||||
/>
|
||||
</svg>
|
||||
<span class="hidden">Next</span>
|
||||
</span>
|
||||
</button>
|
||||
)}
|
||||
</custom-carousel>
|
||||
|
||||
<script src="./custom-carousel.ts"/>
|
||||
@@ -1,80 +0,0 @@
|
||||
import {Carousel, type CarouselItem, type CarouselOptions, type IndicatorItem} from 'flowbite';
|
||||
|
||||
class CustomCarousel extends HTMLElement {
|
||||
_slide: boolean;
|
||||
_items: CarouselItem[];
|
||||
_options: CarouselOptions;
|
||||
_carousel: Carousel;
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._slide = this.getAttribute('data-custom-carousel') === 'slide';
|
||||
this._items = this._getItems();
|
||||
this._options = this._getOptions();
|
||||
this._carousel = new Carousel(this, this._items, this._options);
|
||||
|
||||
if (this._slide && this._items.length > 0) this._carousel.cycle();
|
||||
|
||||
window.addEventListener("load", this._attachHandlers);
|
||||
|
||||
}
|
||||
|
||||
_getItems = (): CarouselItem[] => {
|
||||
let customItems = this.querySelectorAll('[data-custom-carousel-item]') || [];
|
||||
|
||||
return Array.from(customItems).map(
|
||||
(item, index): CarouselItem => {
|
||||
return {el: item as HTMLElement, position: index}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
_getOptions = (): CarouselOptions => {
|
||||
let customIndicators = this.querySelectorAll('[data-custom-carousel-slide-to]') || [];
|
||||
|
||||
return {
|
||||
defaultPosition: 0,
|
||||
interval: this.dataset.customCarouselInterval ? Number(this.dataset.customCarouselInterval) : 8000,
|
||||
|
||||
indicators: {
|
||||
activeClasses: 'border-2 border-caperren-green bg-black',
|
||||
inactiveClasses: 'bg-caperren-green/40 hover:bg-caperren-green-light',
|
||||
items: Array.from(customIndicators).map(
|
||||
(item, index): IndicatorItem => {
|
||||
return {
|
||||
el: item as HTMLElement,
|
||||
position: Number(item.getAttribute('data-custom-carousel-slide-to'))
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_attachHandlers = (): void => {
|
||||
// Controls
|
||||
const carouselNextEl = this.querySelector(
|
||||
'[data-custom-carousel-next]'
|
||||
);
|
||||
const carouselPrevEl = this.querySelector(
|
||||
'[data-custom-carousel-prev]'
|
||||
);
|
||||
|
||||
if (carouselNextEl) {
|
||||
carouselNextEl.addEventListener('click', () => {
|
||||
this._carousel.next();
|
||||
});
|
||||
}
|
||||
|
||||
if (carouselPrevEl) {
|
||||
carouselPrevEl.addEventListener('click', () => {
|
||||
this._carousel.prev();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
customElements.define('custom-carousel', CustomCarousel)
|
||||
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
@@ -0,0 +1,5 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<h3 class="mt-4 mb-2 font-bold md:text-lg">{Astro.props.text}</h3>
|
||||
@@ -1,7 +1,10 @@
|
||||
---
|
||||
---
|
||||
|
||||
<footer class="fixed bottom-0 left-0 z-20 w-full px-6 py-2 bg-black border-t border-t-caperren-green-dark text-caperren-green-dark text-sm flex items-center justify-between">
|
||||
<span>{import.meta.env.PUBLIC_BUILD_ENVIRONMENT || "development"}</span>
|
||||
<span>{import.meta.env.PUBLIC_REPO_VERSION_HASH || "invalid"}</span>
|
||||
---
|
||||
|
||||
<footer
|
||||
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>
|
||||
|
||||
14
src/components/InlineLink.astro
Normal file
@@ -0,0 +1,14 @@
|
||||
---
|
||||
interface Props {
|
||||
href: string;
|
||||
target?: string;
|
||||
}
|
||||
|
||||
const { href, target = "_blank" } = Astro.props;
|
||||
---
|
||||
|
||||
<>
|
||||
<a class="text-blue-500 hover:text-blue-300" href={href} target={target}>
|
||||
<slot />
|
||||
</a>
|
||||
</>
|
||||
18
src/components/LinkButton.astro
Normal file
@@ -0,0 +1,18 @@
|
||||
---
|
||||
interface Props {
|
||||
title: string;
|
||||
|
||||
href: string;
|
||||
target?: string;
|
||||
}
|
||||
|
||||
const { title, href, target = "_blank" } = Astro.props;
|
||||
---
|
||||
|
||||
<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={href}
|
||||
target={target}
|
||||
>
|
||||
{title}
|
||||
</a>
|
||||
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>
|
||||
109
src/components/Media/CustomCarousel/custom-carousel.ts
Normal file
@@ -0,0 +1,109 @@
|
||||
import {
|
||||
Carousel,
|
||||
type CarouselInterface,
|
||||
type CarouselItem,
|
||||
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
@@ -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>
|
||||
34
src/components/Media/Video.astro
Normal file
@@ -0,0 +1,34 @@
|
||||
---
|
||||
interface Props {
|
||||
videoPath: string;
|
||||
videoType?: string;
|
||||
|
||||
controls?: boolean;
|
||||
autoPlay?: boolean;
|
||||
loop?: boolean;
|
||||
playsInline?: boolean;
|
||||
}
|
||||
|
||||
const {
|
||||
videoPath,
|
||||
videoType = "video/mp4",
|
||||
|
||||
controls = true,
|
||||
autoPlay = false,
|
||||
loop = false,
|
||||
playsInline = false,
|
||||
} = Astro.props;
|
||||
---
|
||||
|
||||
<div class="mx-auto my-auto">
|
||||
<video
|
||||
class="h-auto w-full"
|
||||
controls={controls}
|
||||
autoplay={autoPlay}
|
||||
loop={loop}
|
||||
playsinline={playsInline}
|
||||
>
|
||||
<source src={videoPath} type={videoType} />
|
||||
Your browser does not support the video tag.
|
||||
</video>
|
||||
</div>
|
||||
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,33 +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}
|
||||
height="56"
|
||||
width="139"
|
||||
class="h-14 w-auto"
|
||||
alt="logo title"
|
||||
loading="eager"
|
||||
/>
|
||||
</a>
|
||||
<button data-collapse-toggle="navbar-multi-level" type="button"
|
||||
class="inline-flex items-center p-2 w-10 h-10 justify-center text-sm md:hidden focus:ring-2 focus:ring-caperren-green"
|
||||
aria-controls="navbar-multi-level" aria-expanded="false">
|
||||
<span class="sr-only">Open main menu</span>
|
||||
<svg class="w-5 h-5" aria-hidden="true" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 17 14">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M1 1h15M1 7h15M1 13h15"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="z-40 hidden w-full md:block md:w-auto" id="navbar-multi-level">
|
||||
<NestedNavbarEntry items={siteLayout}/>
|
||||
</div>
|
||||
<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 ? "" : "md:flex-row md:space-x-8 md:mt-0")}>
|
||||
{
|
||||
items.map((entry, index) => (
|
||||
(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 md:hover:bg-transparent md:border-0 md:hover:text-caperren-green-light md:p-0 ">
|
||||
{entry.navText}
|
||||
<svg class="w-2.5 h-2.5 ms-2.5" aria-hidden="true"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none" viewBox="0 0 10 6">
|
||||
<path stroke="currentColor" stroke-linecap="round" stroke-linejoin="round"
|
||||
stroke-width="2"
|
||||
d="m1 1 4 4 4-4"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<div id={"dropdownNavbar" + getNavLinkSuffix(entry)}
|
||||
class="z-10 hidden bg-black border border-caperren-green shadow-sm w-screen md:w-max">
|
||||
<Astro.self items={entry.children} paths={[...paths, entry.path]}
|
||||
depth={depth + 1}/>
|
||||
</div>
|
||||
|
||||
) : (
|
||||
|
||||
<a href={getHrefPath(entry)}
|
||||
class="block py-2 px-3 bg-transparent hover:text-caperren-green-light ring-caperren-green-dark md:p-0"
|
||||
aria-current="page">{entry.navText}</a>
|
||||
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
))}
|
||||
</ul>
|
||||
<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>
|
||||
|
||||
7
src/components/Paragraph.astro
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="">
|
||||
<slot />
|
||||
</div>
|
||||
7
src/components/Paragraphs.astro
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
|
||||
---
|
||||
|
||||
<div class="space-y-2">
|
||||
<slot />
|
||||
</div>
|
||||
43
src/components/PopoverWordDefinition.astro
Normal file
@@ -0,0 +1,43 @@
|
||||
---
|
||||
import { v4 as uuidv4 } from "uuid";
|
||||
|
||||
const popoverUUID = uuidv4();
|
||||
|
||||
const keys: { [key: string]: string } = {
|
||||
ADCP: "Acoustic doppler current profiler",
|
||||
COTS: "Consumer off-the-shelf",
|
||||
CTD: "Conductivity, temperature, and depth sensor",
|
||||
};
|
||||
|
||||
const key: string | undefined = Astro.props.key;
|
||||
let word: string | undefined = Astro.props.word;
|
||||
let definition: string | undefined = Astro.props.definition;
|
||||
|
||||
if (key && keys.hasOwnProperty(key)) {
|
||||
word = key;
|
||||
definition = keys[key];
|
||||
}
|
||||
---
|
||||
|
||||
<>
|
||||
<a
|
||||
href="#"
|
||||
class="text-fg-brand decoration-caperren-green font-medium underline decoration-dashed hover:no-underline"
|
||||
data-popover-target={popoverUUID}>{word}</a
|
||||
>
|
||||
<div
|
||||
data-popover
|
||||
id={popoverUUID}
|
||||
role="tooltip"
|
||||
class="text-body border-caperren-green invisible absolute z-90 inline-block w-fit max-w-96 rounded-lg border border-dashed bg-black p-3 text-sm opacity-0 shadow-xs transition-opacity duration-300"
|
||||
>
|
||||
<div>
|
||||
<h3
|
||||
class="text-heading border-b-caperren-green-dark mb-1 border-b font-semibold"
|
||||
>
|
||||
{word}
|
||||
</h3>
|
||||
<p>{definition}</p>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
@@ -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="w-full text-sm text-left">
|
||||
<thead class="text-xs border-b-3 border-caperren-green uppercase bg-black">
|
||||
<tr>
|
||||
{data.header.map(headingText => (
|
||||
<th scope="col" class={paddingClasses}>
|
||||
{headingText}
|
||||
</th>
|
||||
<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>
|
||||
</div>
|
||||
</tr>
|
||||
))
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
@@ -1,34 +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="relative z-10 grid gap-6 grid-flow-row sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 3xl:grid-cols-6"
|
||||
data-timeline>
|
||||
{timeline.map((entry, index) => (
|
||||
<div class="pt-1 border bg-black border-caperren-green rounded-lg min-w-s max-w-s px-2 pb-2"
|
||||
data-timeline-node-index={index}
|
||||
>
|
||||
<h3 class="text-lg font-bold">
|
||||
{entry.event}
|
||||
</h3>
|
||||
<h4 class="font-semibold leading-none">
|
||||
{entry.eventDetail}
|
||||
</h4>
|
||||
<time class="mb-2 mt-1 text-sm italic leading-none">
|
||||
{entry.date}
|
||||
</time>
|
||||
{entry.description && (
|
||||
<p class="text-sm font-normal">
|
||||
{entry.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
<div 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,44 +1,80 @@
|
||||
import LeaderLine from "leader-line-new";
|
||||
|
||||
class CustomTimeline extends HTMLElement {
|
||||
_eventElements: Element[];
|
||||
_eventElements: Element[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._eventElements = this._getNodeElements();
|
||||
constructor() {
|
||||
super();
|
||||
|
||||
this._eventElements = this._getNodeElements();
|
||||
window.addEventListener("load", this._paintLeaderLines);
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
_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'))
|
||||
);
|
||||
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();
|
||||
|
||||
_paintLeaderLines = () => {
|
||||
let pairs = this._eventElements.map((entry, index) => {
|
||||
if (index < this._eventElements.length - 1) {
|
||||
return [entry, this._eventElements[index + 1]];
|
||||
}
|
||||
}).filter(pair => pair !== undefined)
|
||||
wrapper.style.transform =
|
||||
"translate(-" +
|
||||
Number(rectWrapper.left) +
|
||||
"px, -" +
|
||||
Number(rectWrapper.top) +
|
||||
"px)";
|
||||
|
||||
pairs.forEach(pair => {
|
||||
new LeaderLine({
|
||||
start: pair[0],
|
||||
end: pair[1],
|
||||
color: '#10ac25',
|
||||
size: 3,
|
||||
startSocket: "right",
|
||||
endSocket: "left",
|
||||
startPlug: "square",
|
||||
endPlug: "arrow3"
|
||||
});
|
||||
});
|
||||
}
|
||||
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,226 +1,273 @@
|
||||
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: "About", path: "" },
|
||||
{ navText: "Education", path: "education" },
|
||||
{
|
||||
navText: "Experiences",
|
||||
path: "experience",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Experiences",
|
||||
path: "experience",
|
||||
navText: "SpaceX",
|
||||
path: "spacex",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "SpaceX",
|
||||
path: "spacex",
|
||||
children: [
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Hardware Test Engineer I/II",
|
||||
path: "hardware-test-engineer-i-ii"
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
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",
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Hardware Test Engineer I/II",
|
||||
path: "hardware-test-engineer-i-ii",
|
||||
},
|
||||
{
|
||||
enabled: false,
|
||||
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: "Shed Solar", path: "shed-solar", enabled: false},
|
||||
{navText: "OSSM Overkill Edition", path: "ossm-overkill-edition", enabled: false},
|
||||
]
|
||||
},
|
||||
{enabled: false, navText: "NixOS", path: "nixos"},
|
||||
{navText: "Body Mods", path: "body-mods"},
|
||||
]
|
||||
},
|
||||
{
|
||||
navText: "Resumes",
|
||||
path: "resume",
|
||||
{
|
||||
enabled: false,
|
||||
navText: "Robotics Oceanographic Surface Sampler",
|
||||
path: "robotic-oceanographic-surface-sampler",
|
||||
},
|
||||
{
|
||||
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)];
|
||||
};
|
||||
|
||||
8
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,13 +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,36 +1,80 @@
|
||||
---
|
||||
import '@styles/global.css'
|
||||
import "@styles/global.css";
|
||||
|
||||
import Navbar from '@components/Navbar.astro';
|
||||
import Footer from '@components/Footer.astro';
|
||||
import Footer from "@components/Footer.astro";
|
||||
import Navbar from "@components/Navbar.astro";
|
||||
|
||||
import {pathToMetadata} from "@data/site-layout.ts";
|
||||
import { pathToMetadata } from "@data/site-layout.ts";
|
||||
|
||||
interface Props {
|
||||
title?: string;
|
||||
subTitles?: string[];
|
||||
|
||||
const pageTitle = Astro.props.title ? `${Astro.props.title} - Corwin Perren` : "Corwin Perren";
|
||||
const showTitle = Astro.props.showTitle ?? true;
|
||||
showTitle?: boolean;
|
||||
}
|
||||
|
||||
const { title, subTitles, showTitle = true } = Astro.props;
|
||||
|
||||
const pageTitle = Astro.props.title
|
||||
? `${Astro.props.title} - Corwin Perren`
|
||||
: "Corwin Perren";
|
||||
const pageEnabled = pathToMetadata(Astro.url.pathname).enabled ?? true;
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<link rel="icon" href="/favicon-solid.png" type="image/png"/>
|
||||
<link rel="sitemap" href="/sitemap-index.xml"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
<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="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="bg-black text-white">
|
||||
<Navbar/>
|
||||
<main class="mx-6 mt-6 mb-14">
|
||||
{(Astro.props.title && showTitle && pageEnabled) && (
|
||||
<h1 class="font-extrabold md:text-3xl md:mb-6">{Astro.props.title}</h1>
|
||||
)}
|
||||
{pageEnabled && (
|
||||
<slot/>
|
||||
)}
|
||||
</main>
|
||||
<Footer/>
|
||||
<script src="../scripts/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
</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">
|
||||
{
|
||||
title && showTitle && pageEnabled && (
|
||||
<h1
|
||||
class={
|
||||
"text-xl font-extrabold md:text-3xl " +
|
||||
(subTitles ? "" : "md:mb-6")
|
||||
}
|
||||
>
|
||||
{title}
|
||||
</h1>
|
||||
)
|
||||
}
|
||||
{
|
||||
showTitle &&
|
||||
pageEnabled &&
|
||||
subTitles?.map((subTitle, index) => (
|
||||
<p
|
||||
class={
|
||||
"text-sm font-bold md:text-xl " +
|
||||
(index == subTitles.length - 1 ? "mb-2 md:mb-6" : "")
|
||||
}
|
||||
>
|
||||
{subTitle}
|
||||
</p>
|
||||
))
|
||||
}
|
||||
{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/>
|
||||
</BaseLayout>
|
||||
|
||||
<BaseLayout {...Astro.props}>
|
||||
<slot />
|
||||
</BaseLayout>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
---
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
import BaseLayout from "./BaseLayout.astro";
|
||||
---
|
||||
<BaseLayout title={Astro.props.title}>
|
||||
<slot/>
|
||||
</BaseLayout>
|
||||
|
||||
<BaseLayout {...Astro.props}>
|
||||
<slot />
|
||||
</BaseLayout>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
---
|
||||
import BaseLayout from './BaseLayout.astro';
|
||||
import PdfViewer from "@components/Media/PdfViewer.astro";
|
||||
import BaseLayout from "@layouts/BaseLayout.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>
|
||||
</BaseLayout>
|
||||
<BaseLayout {...Astro.props}>
|
||||
<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 Timeline from "@components/Timeline/Timeline.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
import Table from "@components/Table.astro";
|
||||
import Timeline from "@components/Timeline/Timeline.astro";
|
||||
import BaseLayout from "@layouts/BaseLayout.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}/>
|
||||
</BaseLayout>
|
||||
<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>
|
||||
|
||||
@@ -1,6 +1,138 @@
|
||||
---
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
|
||||
import H2 from "@components/CustomHtmlWrappers/H2.astro";
|
||||
import H3 from "@components/CustomHtmlWrappers/H3.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
import Paragraph from "@components/Paragraph.astro";
|
||||
import Timeline from "@components/Timeline/Timeline.astro";
|
||||
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
|
||||
import { deploymentTimeline } from "./osu-ceoas-ocean-mixing-group.ts";
|
||||
|
||||
import building from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/building.jpg";
|
||||
import glacier_selfie from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/glacier-selfie.jpg";
|
||||
import ground_station from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/ground-station.jpg";
|
||||
import iced_in from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/iced-in.jpg";
|
||||
import pushing_icebergs from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/pushing-icebergs.jpg";
|
||||
import ross_at_terminus from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/ross-at-terminus.png";
|
||||
import ross_on_the_docks from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/ross-on-the-docks.jpg";
|
||||
import ross_steller_ice_operations from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/ross-steller-ice-operations.png";
|
||||
import steller_at_terminus from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/steller-at-terminus.png";
|
||||
import steller_in_ice_from_above from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/steller-in-ice-from-above.jpg";
|
||||
import whole_glacier from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/whole-glacier.jpg";
|
||||
import working_at_the_docks from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/working-at-the-docks.jpg";
|
||||
import working_trailer from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/working-trailer.jpg";
|
||||
|
||||
import massive_calving from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/videos/massive-calving-compressed.mp4";
|
||||
import petersburg_working_timelapse from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/videos/petersburg-working-timelapse-compressed.mp4";
|
||||
import ross_iceops from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/videos/ross-iceops-compressed.mp4";
|
||||
import ross_terminus_calving from "@assets/experience/osu-ceoas-ocean-mixing-group/leconte-glacier-deployments/videos/ross-terminus-calving-compressed.mp4";
|
||||
|
||||
const videos = [
|
||||
ross_terminus_calving,
|
||||
ross_iceops,
|
||||
massive_calving,
|
||||
petersburg_working_timelapse,
|
||||
];
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
ross_at_terminus,
|
||||
ross_steller_ice_operations,
|
||||
steller_at_terminus,
|
||||
steller_in_ice_from_above,
|
||||
whole_glacier,
|
||||
ground_station,
|
||||
glacier_selfie,
|
||||
iced_in,
|
||||
pushing_icebergs,
|
||||
ross_on_the_docks,
|
||||
working_at_the_docks,
|
||||
building,
|
||||
working_trailer,
|
||||
],
|
||||
};
|
||||
|
||||
import InlineLink from "@components/InlineLink.astro";
|
||||
import Video from "@components/Media/Video.astro";
|
||||
import Paragraphs from "@components/Paragraphs.astro";
|
||||
import PopoverWordDefinition from "@components/PopoverWordDefinition.astro";
|
||||
import { subTitles } from "./osu-ceoas-ocean-mixing-group.ts";
|
||||
---
|
||||
|
||||
<ExperienceLayout title="CEOAS - LeConte Glacier Deployments">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout title="LeConte Glacier Deployments" subTitles={subTitles}>
|
||||
<Carousel carouselGroup={headerCarouselGroup} />
|
||||
<H2 text="Summary" />
|
||||
<H3 text="Timeline" />
|
||||
<Timeline timeline={deploymentTimeline} />
|
||||
<H3 text="Location" />
|
||||
<iframe
|
||||
class="w-full"
|
||||
width="600"
|
||||
height="450"
|
||||
src="https://maps.google.com/maps?q=leconte%20glacier&t=k&z=11&ie=UTF8&iwloc=B&output=embed"
|
||||
></iframe>
|
||||
<H2 text="Details" />
|
||||
<Paragraphs>
|
||||
<Paragraph>
|
||||
As part of my time working on the
|
||||
<InlineLink
|
||||
href="/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler"
|
||||
>
|
||||
Robotic Oceanographic Surface Sampler</InlineLink
|
||||
>, I had the fantastic opportunity to be deployed at the LeConte Glacier
|
||||
in Alaska! This started in early 2017 with setup and ocean trials in
|
||||
nearby Petersburg. The team had sent multiple shipping containers with our
|
||||
robotic platforms and most equipment to assemble, test, and debug them a
|
||||
few months prior, allowing us to get to work the moment we arrived. We
|
||||
spent multiple weeks at the docks with our makeshift workstations built
|
||||
from plywood and pelican cases, validating the hardware we'd sent, and
|
||||
making adjustments with improved hardware we'd hand-carried on our
|
||||
flights. This also provided a great opportunity to work out any final
|
||||
firmware and/or software bugs while the vehicles were still relatively
|
||||
easy to retrieve. After a short trip back home to recover, and prep any
|
||||
last minute items we'd forgotten, our research team flew back and headed
|
||||
for the glacier!
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
The towering mountain of ice sits roughly 30 miles from Petersburg, so
|
||||
we'd commissioned an off-season fishing vessel, Steller, and it's crew, to
|
||||
take us as close to it as was reasonably safe. The team worked 24 hours a
|
||||
day, on two shifts, deploying and retrieving the ROSS platforms,
|
||||
performing repairs (as needed), recovering/processing collected data,
|
||||
manually deploying the ship's <PopoverWordDefinition key="CTD" />, and
|
||||
occasionally spending considerable time pushing icebergs the size of
|
||||
houses away from an <PopoverWordDefinition key="ADCP" /> mounted to Steller
|
||||
using fiberglass poles. Many hardware failures had to be solved during these
|
||||
long days, and it was a very rewarding and creative experience to work around
|
||||
the limitations of this isolated (and salty) environment.
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
On top of being a unique engineering and team building experience, LeConte
|
||||
lives among the most beautiful places I've yet to experience in this life.
|
||||
There's something special about being somewhere so incredibly remote and
|
||||
untouched by humans. The pristine evergreen forests, eerie blue-green hues
|
||||
of the glacier and icebergs, ancient towering mountains, and genuinely
|
||||
curious looks from local land and marine life unfamiliar with human
|
||||
presence made it humbly clear that for once we as humans were the odd ones
|
||||
out. These trips were ones that I will treasure and think back on fondly
|
||||
on for the rest of my life.
|
||||
</Paragraph>
|
||||
</Paragraphs>
|
||||
<H2 text="Videos" />
|
||||
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||
{
|
||||
videos.map((video) => (
|
||||
<Video
|
||||
videoPath={video}
|
||||
autoPlay={true}
|
||||
loop={true}
|
||||
playsInline={true}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</ExperienceLayout>
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||
|
||||
export const deploymentTimeline: timelineEntry[] = [
|
||||
{
|
||||
event: "Setup & Ocean Trials",
|
||||
eventDetail: "Petersburg, AK",
|
||||
date: "April 2017",
|
||||
},
|
||||
{
|
||||
event: "Glacier Deployment #1",
|
||||
eventDetail: "LeConte Glacier, AK",
|
||||
date: "May 2017",
|
||||
},
|
||||
{
|
||||
event: "Glacier Deployment #2",
|
||||
eventDetail: "LeConte Glacier, AK",
|
||||
date: "September 2017",
|
||||
},
|
||||
{
|
||||
event: "Scientific Paper Published",
|
||||
eventDetail: "The Oceanographic Society",
|
||||
date: "September 2017",
|
||||
},
|
||||
];
|
||||
|
||||
export const subTitles = [
|
||||
"Oregon State University",
|
||||
"College of Earth, Ocean, and Atmospheric Sciences",
|
||||
];
|
||||
@@ -1,6 +1,112 @@
|
||||
---
|
||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||
|
||||
import H2 from "@components/CustomHtmlWrappers/H2.astro";
|
||||
import H3 from "@components/CustomHtmlWrappers/H3.astro";
|
||||
import LinkButton from "@components/LinkButton.astro";
|
||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
import PdfViewer from "@components/Media/PdfViewer.astro";
|
||||
import PopoverWordDefinition from "@components/PopoverWordDefinition.astro";
|
||||
import Timeline from "@components/Timeline/Timeline.astro";
|
||||
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||
|
||||
import electronics_box from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/electronics-box.jpg";
|
||||
import jet_drive from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/jet-drive.jpg";
|
||||
import ross_on_vessel_at_night from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ross-on-vessel-at-night.jpg";
|
||||
import ross_on_vessel from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ross-on-vessel.jpg";
|
||||
import publication from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ross-publication.pdf";
|
||||
import ross_team from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ross-team.jpg";
|
||||
import ui from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ui.jpg";
|
||||
|
||||
import {
|
||||
deploymentTimeline,
|
||||
subTitles,
|
||||
} from "./osu-ceoas-ocean-mixing-group.ts";
|
||||
|
||||
const headerCarouselGroup: carouselGroup = {
|
||||
animation: "slide",
|
||||
images: [
|
||||
ross_team,
|
||||
ross_on_vessel,
|
||||
ross_on_vessel_at_night,
|
||||
electronics_box,
|
||||
jet_drive,
|
||||
ui,
|
||||
],
|
||||
};
|
||||
|
||||
const timeline: timelineEntry[] = [
|
||||
{
|
||||
event: "Started",
|
||||
eventDetail: "Joined ROSS",
|
||||
date: "April 2016",
|
||||
},
|
||||
...deploymentTimeline,
|
||||
{
|
||||
event: "Finished",
|
||||
eventDetail: "Left ROSS",
|
||||
date: "May 2018",
|
||||
},
|
||||
];
|
||||
---
|
||||
|
||||
<ExperienceLayout title="CEOAS - Robotic Oceanographic Surface Sampler">
|
||||
</ExperienceLayout>
|
||||
<ExperienceLayout
|
||||
title="Robotic Oceanographic Surface Sampler"
|
||||
subTitles={subTitles}
|
||||
>
|
||||
<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 text="Summary" />
|
||||
<H3 text="Timeline" />
|
||||
<Timeline timeline={timeline} />
|
||||
<H3 text="Key Takeaways" />
|
||||
<ul class="list-inside list-disc">
|
||||
<li>
|
||||
<div class="inline-block">
|
||||
Assembled, fabricated, and debugged both custom and
|
||||
<PopoverWordDefinition key="COTS" />
|
||||
hardware and electronics.
|
||||
</div>
|
||||
</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="Details" />
|
||||
power and voltage logging
|
||||
<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 Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||
import YtVideo from "@components/Media/YtVideo.astro";
|
||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||
import type { videoConfig } from "@interfaces/video.ts";
|
||||
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";
|
||||
|
||||
|
||||
|
||||
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}/>
|
||||
))}
|
||||
|
||||
</ExperienceLayout>
|
||||
<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" />
|
||||
|
||||