Compare commits
3 Commits
3be602c6cf
...
090bc11ed0
| Author | SHA1 | Date | |
|---|---|---|---|
| 090bc11ed0 | |||
| dac9e80efd | |||
| 8fd744118f |
9
Makefile
@@ -15,7 +15,8 @@
|
|||||||
cleanup-check \
|
cleanup-check \
|
||||||
cleanup-code \
|
cleanup-code \
|
||||||
convert_video \
|
convert_video \
|
||||||
convert_video_times
|
convert_video_times \
|
||||||
|
generate_asset_imports
|
||||||
|
|
||||||
default: dev
|
default: dev
|
||||||
|
|
||||||
@@ -89,3 +90,9 @@ convert_video_times:
|
|||||||
-qp 28 \
|
-qp 28 \
|
||||||
-an \
|
-an \
|
||||||
$(output)
|
$(output)
|
||||||
|
|
||||||
|
generate_asset_imports:
|
||||||
|
@for assets_path in `find "src/assets/${assets_relative_path}" -maxdepth 1 -type f -printf "%f\n"`; do \
|
||||||
|
without_extension=$${assets_path/%.*}; \
|
||||||
|
echo "import $${without_extension//-/_} from \"@assets/${assets_relative_path}/$$assets_path\";"; \
|
||||||
|
done;
|
||||||
|
|||||||
@@ -13,13 +13,17 @@ Concours
|
|||||||
CONSERV
|
CONSERV
|
||||||
Corwin
|
Corwin
|
||||||
dangerousthings
|
dangerousthings
|
||||||
|
dechorionation
|
||||||
Dechorionator
|
Dechorionator
|
||||||
|
dechorionators
|
||||||
dockerization
|
dockerization
|
||||||
dockerizing
|
dockerizing
|
||||||
drumheller
|
drumheller
|
||||||
ebox
|
ebox
|
||||||
|
ELMI
|
||||||
fhhs
|
fhhs
|
||||||
flowbite
|
flowbite
|
||||||
|
flowrate
|
||||||
Gitea
|
Gitea
|
||||||
HDFS
|
HDFS
|
||||||
headshot
|
headshot
|
||||||
@@ -34,6 +38,8 @@ leconte
|
|||||||
Loctite
|
Loctite
|
||||||
luxon
|
luxon
|
||||||
MGMT
|
MGMT
|
||||||
|
Micropumps
|
||||||
|
Millis
|
||||||
Mokai
|
Mokai
|
||||||
Multimeters
|
Multimeters
|
||||||
nixos
|
nixos
|
||||||
@@ -42,26 +48,32 @@ Onshape
|
|||||||
OSSM
|
OSSM
|
||||||
OSURC
|
OSURC
|
||||||
Passthroughs
|
Passthroughs
|
||||||
|
pcbs
|
||||||
Perren
|
Perren
|
||||||
Perren's
|
Perren's
|
||||||
Pixhawk
|
Pixhawk
|
||||||
Protocase
|
Protocase
|
||||||
pubpath
|
pubpath
|
||||||
RFID
|
RFID
|
||||||
|
Rito
|
||||||
RSSI
|
RSSI
|
||||||
SARL
|
SARL
|
||||||
|
showerheads
|
||||||
Shuttlebox
|
Shuttlebox
|
||||||
sinnhuber
|
sinnhuber
|
||||||
sitemapindex
|
sitemapindex
|
||||||
Smartsheet
|
Smartsheet
|
||||||
|
solderable
|
||||||
ssds
|
ssds
|
||||||
Starlink
|
Starlink
|
||||||
steller
|
steller
|
||||||
Steller
|
Steller
|
||||||
|
Tanguay
|
||||||
Teamcenter
|
Teamcenter
|
||||||
timelapse
|
timelapse
|
||||||
triaging
|
triaging
|
||||||
trivago
|
trivago
|
||||||
|
Truong
|
||||||
Unstow
|
Unstow
|
||||||
uuidv
|
uuidv
|
||||||
vaapi
|
vaapi
|
||||||
|
|||||||
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 4.0 MiB |
|
After Width: | Height: | Size: 3.1 MiB |
|
After Width: | Height: | Size: 3.6 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 3.7 MiB |
|
After Width: | Height: | Size: 7.7 MiB |
|
After Width: | Height: | Size: 7.3 MiB |
|
After Width: | Height: | Size: 8.3 MiB |
|
After Width: | Height: | Size: 7.6 MiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 3.5 MiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 4.1 MiB |
|
After Width: | Height: | Size: 166 KiB |
|
After Width: | Height: | Size: 239 KiB |
|
After Width: | Height: | Size: 3.8 MiB |
|
After Width: | Height: | Size: 3.9 MiB |
|
After Width: | Height: | Size: 4.6 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 4.3 MiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 4.4 MiB |
|
After Width: | Height: | Size: 6.4 MiB |
|
After Width: | Height: | Size: 7.9 MiB |
|
After Width: | Height: | Size: 8.9 MiB |
|
After Width: | Height: | Size: 8.7 MiB |
|
After Width: | Height: | Size: 7.8 MiB |
|
After Width: | Height: | Size: 5.9 MiB |
|
After Width: | Height: | Size: 9.2 MiB |
|
After Width: | Height: | Size: 4.2 MiB |
|
After Width: | Height: | Size: 8.5 MiB |
|
Before Width: | Height: | Size: 2.4 MiB After Width: | Height: | Size: 2.4 MiB |
@@ -1,15 +1,21 @@
|
|||||||
---
|
---
|
||||||
import { Image } from "astro:assets";
|
import { Image } from "astro:assets";
|
||||||
|
|
||||||
|
import type { ComponentPropsBase } from "@interfaces/components.ts";
|
||||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||||
|
|
||||||
const groupToShow: carouselGroup = Astro.props.carouselGroup;
|
interface Props extends ComponentPropsBase {
|
||||||
|
carouselGroup: carouselGroup;
|
||||||
|
showBorder?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { class: className, carouselGroup, showBorder = true } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<custom-carousel
|
<custom-carousel
|
||||||
class="relative flex w-full flex-col"
|
class="relative flex w-full flex-col"
|
||||||
data-custom-carousel={groupToShow.animation}
|
data-custom-carousel={carouselGroup.animation ?? "slide"}
|
||||||
data-custom-carousel-interval={groupToShow.interval}
|
data-custom-carousel-interval={carouselGroup.interval}
|
||||||
>
|
>
|
||||||
<!-- Modal for fullscreen viewing -->
|
<!-- Modal for fullscreen viewing -->
|
||||||
<div
|
<div
|
||||||
@@ -57,54 +63,63 @@ const groupToShow: carouselGroup = Astro.props.carouselGroup;
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Carousel wrapper -->
|
<!-- Carousel wrapper -->
|
||||||
<div class="relative h-56 w-full overflow-hidden md:h-120">
|
<div
|
||||||
|
class:list={[
|
||||||
|
"relative h-56 w-full overflow-hidden rounded-lg md:h-120",
|
||||||
|
showBorder ? "border" : false,
|
||||||
|
className ? className : "border-caperren-green-dark",
|
||||||
|
]}
|
||||||
|
>
|
||||||
{
|
{
|
||||||
groupToShow.images.map((image, index) => (
|
carouselGroup.images &&
|
||||||
<div
|
carouselGroup.images.map((image, index) => (
|
||||||
class="hidden duration-1500 ease-in-out"
|
<div
|
||||||
data-custom-carousel-item={index}
|
class="hidden bg-black duration-1500 ease-in-out"
|
||||||
>
|
data-custom-carousel-item={index}
|
||||||
<Image
|
>
|
||||||
src={image}
|
<Image
|
||||||
class="absolute inset-0 m-auto h-full max-h-full w-auto max-w-full object-contain"
|
src={image}
|
||||||
alt="..."
|
class="absolute inset-0 m-auto h-full max-h-full w-auto max-w-full object-contain"
|
||||||
layout="constrained"
|
alt="..."
|
||||||
loading="eager"
|
layout="constrained"
|
||||||
/>
|
loading="eager"
|
||||||
</div>
|
/>
|
||||||
))
|
</div>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Slider indicators -->
|
<!-- Slider indicators -->
|
||||||
{
|
{
|
||||||
groupToShow.images.length > 1 && (
|
carouselGroup.images && carouselGroup.images.length > 1 && (
|
||||||
<div>
|
<div>
|
||||||
<div class="absolute bottom-2 left-1/2 z-30 flex -translate-x-1/2 space-x-3 rounded-full">
|
<div class="absolute bottom-2 left-1/2 z-30 flex -translate-x-1/2 space-x-3 rounded-full">
|
||||||
{groupToShow.images.map((_, index) => (
|
{carouselGroup.images &&
|
||||||
<button
|
carouselGroup.images.map((_, index) => (
|
||||||
type="button"
|
<button
|
||||||
class="hover:bg-caperren-green-light h-3 w-3 rounded-full bg-black"
|
type="button"
|
||||||
aria-current={index ? "false" : "true"}
|
class="hover:bg-caperren-green-light h-3 w-3 rounded-full"
|
||||||
aria-label={index.toString()}
|
aria-current={index ? "false" : "true"}
|
||||||
data-custom-carousel-slide-to={index}
|
aria-label={index.toString()}
|
||||||
/>
|
data-custom-carousel-slide-to={index}
|
||||||
))}
|
/>
|
||||||
|
))}
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
type="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"
|
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
|
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">
|
<span class="ring-caperren-green/35 group-hover:ring-caperren-green inline-flex h-10 w-10 items-center justify-center rounded-full bg-black/35 ring-2 group-hover:bg-black/75">
|
||||||
<svg
|
<svg
|
||||||
class="text-caperren-green group-hover:text-caperren-green-light h-4 w-4"
|
class="text-caperren-green stroke-caperren-green group-hover:text-caperren-green-light h-4 w-4"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 6 10"
|
viewBox="0 0 8 10"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
|
fill-opacity="0%"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
@@ -119,15 +134,16 @@ const groupToShow: carouselGroup = Astro.props.carouselGroup;
|
|||||||
class="group absolute end-0 top-0 z-30 flex h-full cursor-pointer items-center justify-center px-4 focus:outline-none"
|
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
|
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">
|
<span class="ring-caperren-green/25 group-hover:ring-caperren-green inline-flex h-10 w-10 items-center justify-center rounded-full bg-black/25 ring-2 group-hover:bg-black/75">
|
||||||
<svg
|
<svg
|
||||||
class="text-caperren-green group-hover:text-caperren-green-light h-4 w-4"
|
class="text-caperren-green stroke-caperren-green group-hover:text-caperren-green-light h-4 w-4"
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
viewBox="0 0 6 10"
|
viewBox="0 0 4 10"
|
||||||
>
|
>
|
||||||
<path
|
<path
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
|
fill-opacity="0%"
|
||||||
stroke-linecap="round"
|
stroke-linecap="round"
|
||||||
stroke-linejoin="round"
|
stroke-linejoin="round"
|
||||||
stroke-width="2"
|
stroke-width="2"
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ const {
|
|||||||
|
|
||||||
<div class="mx-auto my-auto">
|
<div class="mx-auto my-auto">
|
||||||
<video
|
<video
|
||||||
class="h-auto w-full"
|
class="border-caperren-green-dark h-auto w-full rounded-lg border"
|
||||||
controls={controls}
|
controls={controls}
|
||||||
autoplay={autoPlay}
|
autoplay={autoPlay}
|
||||||
loop={loop}
|
loop={loop}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
---
|
---
|
||||||
import type { videoConfig } from "@interfaces/yt-video.ts";
|
import type { videoConfig } from "@interfaces/video.ts";
|
||||||
|
|
||||||
interface Props extends videoConfig {}
|
interface Props extends videoConfig {}
|
||||||
|
|
||||||
@@ -18,7 +18,7 @@ const aspect = `${width} / ${height}`;
|
|||||||
<iframe
|
<iframe
|
||||||
src={videoPath}
|
src={videoPath}
|
||||||
title={videoTitle}
|
title={videoTitle}
|
||||||
class="h-full w-full"
|
class="border-caperren-green-dark h-full w-full rounded-lg border"
|
||||||
allow={"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; " +
|
allow={"accelerometer; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share; " +
|
||||||
(autoPlay ? "autoplay;" : "")}
|
(autoPlay ? "autoplay;" : "")}
|
||||||
referrerpolicy="strict-origin-when-cross-origin"
|
referrerpolicy="strict-origin-when-cross-origin"
|
||||||
|
|||||||
@@ -36,7 +36,8 @@ const { pathname } = Astro.url;
|
|||||||
data-dropdown-offset-skidding="12"
|
data-dropdown-offset-skidding="12"
|
||||||
class:list={[
|
class:list={[
|
||||||
"hover:text-caperren-green-light lg:hover:text-caperren-green-light flex w-full items-center justify-between lg:p-0 lg:hover:bg-transparent",
|
"hover:text-caperren-green-light lg:hover:text-caperren-green-light flex w-full items-center justify-between lg:p-0 lg:hover:bg-transparent",
|
||||||
pathname.startsWith(getHrefPath(paths, entry))
|
pathname.startsWith(getHrefPath(paths, entry)) &&
|
||||||
|
!(entry.placeholderEntry ?? false)
|
||||||
? "border-caperren-green border-b-2"
|
? "border-caperren-green border-b-2"
|
||||||
: false,
|
: false,
|
||||||
]}
|
]}
|
||||||
@@ -71,20 +72,32 @@ const { pathname } = Astro.url;
|
|||||||
) : (
|
) : (
|
||||||
<div>
|
<div>
|
||||||
<a
|
<a
|
||||||
href={getHrefPath(paths, entry)}
|
href={
|
||||||
|
!(entry.placeholderEntry ?? false)
|
||||||
|
? getHrefPath(paths, entry)
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
target={
|
target={
|
||||||
getHrefPath(paths, entry).startsWith("/") ? "" : "_blank"
|
getHrefPath(paths, entry).startsWith("/") ? "" : "_blank"
|
||||||
}
|
}
|
||||||
class:list={[
|
class:list={[
|
||||||
"hover:text-caperren-green-light ring-caperren-green-dark block bg-transparent lg:p-0",
|
"ring-caperren-green-dark block bg-transparent lg:p-0",
|
||||||
pathname === getHrefPath(paths, entry)
|
pathname === getHrefPath(paths, entry)
|
||||||
? "border-caperren-green border-b-2"
|
? "border-caperren-green border-b-2"
|
||||||
: false,
|
: false,
|
||||||
|
entry.isSubItem ? "ms-3" : false,
|
||||||
|
entry.placeholderEntry
|
||||||
|
? false
|
||||||
|
: "hover:text-caperren-green-light",
|
||||||
]}
|
]}
|
||||||
aria-current={
|
aria-current={
|
||||||
pathname === getHrefPath(paths, entry) ? "page" : undefined
|
pathname === getHrefPath(paths, entry) &&
|
||||||
|
!(entry.placeholderEntry ?? false)
|
||||||
|
? "page"
|
||||||
|
: undefined
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
|
{entry.isSubItem && "∟ "}
|
||||||
{entry.navText}
|
{entry.navText}
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ const hasHeader = Astro.slots.has("header");
|
|||||||
const hasDefault = Astro.slots.has("default");
|
const hasDefault = Astro.slots.has("default");
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="grid grid-cols-1 gap-3">
|
<div class="grid grid-cols-1 gap-0.5">
|
||||||
{
|
{
|
||||||
Astro.slots.has("header") && (
|
Astro.slots.has("header") && (
|
||||||
<div>
|
<div>
|
||||||
|
|||||||
51
src/components/PrintedCircuitBoard.astro
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
---
|
||||||
|
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||||
|
import Ul from "@components/Ul.astro";
|
||||||
|
|
||||||
|
import type {
|
||||||
|
printedCircuitBoard,
|
||||||
|
printedCircuitBoardRevision,
|
||||||
|
} from "@interfaces/printed-circuit-board.ts";
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
pcb: printedCircuitBoard;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { pcb } = Astro.props;
|
||||||
|
const semanticPcbRevisionSort = (
|
||||||
|
a: printedCircuitBoardRevision,
|
||||||
|
b: printedCircuitBoardRevision,
|
||||||
|
): number =>
|
||||||
|
-((a.major - b.major) * 100 + (a.minor - b.minor) * 10 + (a.patch - b.patch));
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class="grid grid-cols-1 gap-4 md:grid-cols-2">
|
||||||
|
{
|
||||||
|
pcb.revisions?.sort(semanticPcbRevisionSort).map((revision) => (
|
||||||
|
<div class="border-caperren-green block space-y-2 rounded-lg border bg-black py-2">
|
||||||
|
<div class="border-caperren-green flex flex-wrap items-center justify-between rounded-none border-b px-4 pb-2">
|
||||||
|
<div>
|
||||||
|
<span class="font-black">Revision:</span>
|
||||||
|
<span>
|
||||||
|
{revision.major}.{revision.minor}.{revision.patch}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div class="text-sm italic">{revision.date.toISODate()}</div>
|
||||||
|
</div>
|
||||||
|
<div class="px-4">
|
||||||
|
<Carousel
|
||||||
|
class=""
|
||||||
|
carouselGroup={{ images: revision.images }}
|
||||||
|
showBorder={false}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
{revision.notes && revision.notes.length > 0 && (
|
||||||
|
<Ul
|
||||||
|
class="border-caperren-green border-t p-4 text-sm"
|
||||||
|
lineItems={revision.notes}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
|
}
|
||||||
|
</div>
|
||||||
@@ -1,7 +1,11 @@
|
|||||||
---
|
---
|
||||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||||
|
|
||||||
const timeline: timelineEntry[] = Astro.props.timeline || [];
|
interface Props {
|
||||||
|
timeline: timelineEntry[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { timeline = [] } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<custom-timeline>
|
<custom-timeline>
|
||||||
@@ -11,21 +15,25 @@ const timeline: timelineEntry[] = Astro.props.timeline || [];
|
|||||||
data-timeline
|
data-timeline
|
||||||
>
|
>
|
||||||
{
|
{
|
||||||
timeline.map((entry, index) => (
|
timeline
|
||||||
<div
|
.sort((a: timelineEntry, b: timelineEntry) =>
|
||||||
class="border-caperren-green min-w-s max-w-s rounded-lg border bg-black px-2 pt-1 pb-2"
|
a.date.diff(b.date).toMillis(),
|
||||||
data-timeline-node-index={index}
|
)
|
||||||
>
|
.map((entry, index) => (
|
||||||
<h3 class="text-lg font-bold">{entry.event}</h3>
|
<div
|
||||||
<h4 class="leading-none font-semibold">{entry.eventDetail}</h4>
|
class="border-caperren-green min-w-s max-w-s rounded-lg border bg-black px-2 pt-1 pb-2"
|
||||||
<time class="mt-1 mb-2 text-sm leading-none italic">
|
data-timeline-node-index={index}
|
||||||
{entry.date}
|
>
|
||||||
</time>
|
<h3 class="text-lg font-bold">{entry.event}</h3>
|
||||||
{entry.description && (
|
<h4 class="leading-none font-semibold">{entry.eventDetail}</h4>
|
||||||
<p class="text-sm font-normal">{entry.description}</p>
|
<time class="mt-1 mb-2 text-sm leading-none italic">
|
||||||
)}
|
{entry.date.toFormat("LLLL kkkk")}
|
||||||
</div>
|
</time>
|
||||||
))
|
{entry.description && (
|
||||||
|
<p class="text-sm font-normal">{entry.description}</p>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
))
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div
|
<div
|
||||||
|
|||||||
0
src/content.config.ts
Normal file
@@ -1,7 +1,6 @@
|
|||||||
import type { navLink } from "@interfaces/site-layout.ts";
|
import type { navLink } from "@interfaces/site-layout.ts";
|
||||||
|
|
||||||
export const siteLayout: navLink[] = [
|
export const siteLayout: navLink[] = [
|
||||||
// Standard navbar entries
|
|
||||||
{ navText: "About", path: "" },
|
{ navText: "About", path: "" },
|
||||||
{ navText: "Education", path: "education" },
|
{ navText: "Education", path: "education" },
|
||||||
{
|
{
|
||||||
@@ -26,49 +25,57 @@ export const siteLayout: navLink[] = [
|
|||||||
navText: "OSU CEOAS Ocean Mixing Group",
|
navText: "OSU CEOAS Ocean Mixing Group",
|
||||||
path: "osu-ceoas-ocean-mixing-group",
|
path: "osu-ceoas-ocean-mixing-group",
|
||||||
children: [
|
children: [
|
||||||
|
{
|
||||||
|
navText: "Student Software/Electrical Engineer",
|
||||||
|
placeholderEntry: true,
|
||||||
|
},
|
||||||
{
|
{
|
||||||
navText: "Robotic Oceanographic Surface Sampler",
|
navText: "Robotic Oceanographic Surface Sampler",
|
||||||
|
isSubItem: true,
|
||||||
path: "robotic-oceanographic-surface-sampler",
|
path: "robotic-oceanographic-surface-sampler",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
navText: "LeConte Glacier Deployments",
|
navText: "LeConte Glacier Deployments",
|
||||||
|
isSubItem: true,
|
||||||
path: "leconte-glacier-deployments",
|
path: "leconte-glacier-deployments",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: false,
|
navText: "OSU Sinnhuber Aquatic Research Lab",
|
||||||
navText: "OSU SARL",
|
|
||||||
path: "osu-sinnhuber-aquatic-research-laboratory",
|
path: "osu-sinnhuber-aquatic-research-laboratory",
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
enabled: false,
|
navText: "Student Automation Engineer",
|
||||||
navText: "Team Lead",
|
placeholderEntry: true,
|
||||||
path: "team-lead",
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
navText: "Zebrafish Embryo Pick and Plate",
|
navText: "Zebrafish Embryo Pick and Plate",
|
||||||
|
isSubItem: true,
|
||||||
path: "zebrafish-embryo-pick-and-plate",
|
path: "zebrafish-embryo-pick-and-plate",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
navText: "Shuttlebox Behavior System",
|
navText: "Shuttlebox Behavior System",
|
||||||
|
isSubItem: true,
|
||||||
path: "shuttlebox-behavior-system",
|
path: "shuttlebox-behavior-system",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: false,
|
|
||||||
navText: "Dechorionator",
|
navText: "Dechorionator",
|
||||||
|
isSubItem: true,
|
||||||
path: "dechorionator",
|
path: "dechorionator",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
navText: "Denso Embryo Pick and Plate",
|
navText: "Denso Embryo Pick and Plate",
|
||||||
|
isSubItem: true,
|
||||||
path: "denso-embryo-pick-and-plate",
|
path: "denso-embryo-pick-and-plate",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
enabled: false,
|
enabled: false,
|
||||||
navText: "ZScan Processor",
|
navText: "ZScan Processor",
|
||||||
|
isSubItem: true,
|
||||||
path: "zscan-processor",
|
path: "zscan-processor",
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
@@ -262,7 +269,10 @@ export const getPaths = (
|
|||||||
];
|
];
|
||||||
} else {
|
} else {
|
||||||
let enabled = currentEntry.enabled ?? true;
|
let enabled = currentEntry.enabled ?? true;
|
||||||
if (disabledOnly ? !enabled : enabled) {
|
if (
|
||||||
|
(disabledOnly ? !enabled : enabled) &&
|
||||||
|
!currentEntry.placeholderEntry
|
||||||
|
) {
|
||||||
foundPaths.push("/" + [...paths, currentEntry.path || ""].join("/"));
|
foundPaths.push("/" + [...paths, currentEntry.path || ""].join("/"));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
export interface carouselGroup {
|
export interface carouselGroup {
|
||||||
animation: "static" | "slide";
|
animation?: "static" | "slide";
|
||||||
interval?: number;
|
interval?: number;
|
||||||
images: ImageMetadata[];
|
images?: ImageMetadata[];
|
||||||
}
|
}
|
||||||
|
|||||||
32
src/interfaces/printed-circuit-board.ts
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
import type { ImageMetadata } from "astro";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||||
|
import type { lineItem } from "@interfaces/ul-li.ts";
|
||||||
|
|
||||||
|
export interface printedCircuitBoardRevision {
|
||||||
|
major: number;
|
||||||
|
minor: number;
|
||||||
|
patch: number;
|
||||||
|
|
||||||
|
date: DateTime;
|
||||||
|
|
||||||
|
images?: ImageMetadata[];
|
||||||
|
notes?: lineItem[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface printedCircuitBoard {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
|
||||||
|
revisions: printedCircuitBoardRevision[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const timelineFromPrintedCircuitBoard = (
|
||||||
|
pcb: printedCircuitBoard,
|
||||||
|
): timelineEntry[] =>
|
||||||
|
pcb.revisions?.map((revision) => ({
|
||||||
|
event: `PCB Released: ${pcb.name} `,
|
||||||
|
eventDetail: `Revision: ${revision.major}.${revision.minor}.${revision.patch}`,
|
||||||
|
date: revision.date,
|
||||||
|
}));
|
||||||
@@ -1,8 +1,13 @@
|
|||||||
export interface navLink {
|
export interface navLink {
|
||||||
enabled?: boolean;
|
enabled?: boolean;
|
||||||
hidden?: boolean;
|
hidden?: boolean;
|
||||||
|
placeholderEntry?: boolean;
|
||||||
|
|
||||||
navText: string;
|
navText: string;
|
||||||
|
isSubItem?: boolean; // For visual distinction only
|
||||||
|
|
||||||
path?: string;
|
path?: string;
|
||||||
pubpath?: string;
|
pubpath?: string;
|
||||||
|
|
||||||
children?: navLink[];
|
children?: navLink[];
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
export interface timelineEntry {
|
export interface timelineEntry {
|
||||||
event: string;
|
event: string;
|
||||||
eventDetail?: string;
|
eventDetail?: string;
|
||||||
date: string;
|
date: DateTime;
|
||||||
description?: string;
|
description?: string;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ const pageEnabled = pathToMetadata(Astro.url.pathname).enabled ?? true;
|
|||||||
class="grow overflow-x-hidden overflow-y-scroll"
|
class="grow overflow-x-hidden overflow-y-scroll"
|
||||||
>
|
>
|
||||||
<Navbar />
|
<Navbar />
|
||||||
<main class="mx-6 my-4">
|
<main class="mx-6 my-4 space-y-4">
|
||||||
{
|
{
|
||||||
showTitle && pageEnabled && (
|
showTitle && pageEnabled && (
|
||||||
<PageGroup>
|
<PageGroup>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
---
|
---
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
import BaseLayout from "@layouts/BaseLayout.astro";
|
import BaseLayout from "@layouts/BaseLayout.astro";
|
||||||
|
|
||||||
import H2 from "@components/H2.astro";
|
import H2 from "@components/H2.astro";
|
||||||
@@ -25,12 +27,12 @@ const timeline: timelineEntry[] = [
|
|||||||
{
|
{
|
||||||
event: "High School Diploma",
|
event: "High School Diploma",
|
||||||
eventDetail: "Friday Harbor High School",
|
eventDetail: "Friday Harbor High School",
|
||||||
date: "June 2011",
|
date: DateTime.fromISO("2011-06-15"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "B.S. Computer Science",
|
event: "B.S. Computer Science",
|
||||||
eventDetail: "Oregon State University",
|
eventDetail: "Oregon State University",
|
||||||
date: "June 2019",
|
date: DateTime.fromISO("2019-06-15"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,25 +1,27 @@
|
|||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||||
|
|
||||||
export const deploymentTimeline: timelineEntry[] = [
|
export const deploymentTimeline: timelineEntry[] = [
|
||||||
{
|
{
|
||||||
event: "Setup & Ocean Trials",
|
event: "Setup & Ocean Trials",
|
||||||
eventDetail: "Petersburg, AK",
|
eventDetail: "Petersburg, AK",
|
||||||
date: "April 2017",
|
date: DateTime.fromISO("2017-04-01"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "Glacier Deployment #1",
|
event: "Glacier Deployment #1",
|
||||||
eventDetail: "LeConte Glacier, AK",
|
eventDetail: "LeConte Glacier, AK",
|
||||||
date: "May 2017",
|
date: DateTime.fromISO("2017-05-01"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "Glacier Deployment #2",
|
event: "Glacier Deployment #2",
|
||||||
eventDetail: "LeConte Glacier, AK",
|
eventDetail: "LeConte Glacier, AK",
|
||||||
date: "September 2017",
|
date: DateTime.fromISO("2017-09-01"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "Scientific Paper Published",
|
event: "Scientific Paper Published",
|
||||||
eventDetail: "The Oceanographic Society",
|
eventDetail: "The Oceanographic Society",
|
||||||
date: "September 2017",
|
date: DateTime.fromISO("2017-09-02"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import publication from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic
|
|||||||
import ross_team from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ross-team.jpg";
|
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 ui from "@assets/experience/osu-ceoas-ocean-mixing-group/robotic-oceanographic-surface-sampler/ui.jpg";
|
||||||
|
|
||||||
|
import { DateTime } from "luxon";
|
||||||
import {
|
import {
|
||||||
deploymentTimeline,
|
deploymentTimeline,
|
||||||
subTitles,
|
subTitles,
|
||||||
@@ -53,13 +54,13 @@ const timeline: timelineEntry[] = [
|
|||||||
{
|
{
|
||||||
event: "Started",
|
event: "Started",
|
||||||
eventDetail: "Joined ROSS",
|
eventDetail: "Joined ROSS",
|
||||||
date: "April 2016",
|
date: DateTime.fromISO("2016-04-01"),
|
||||||
},
|
},
|
||||||
...deploymentTimeline,
|
...deploymentTimeline,
|
||||||
{
|
{
|
||||||
event: "Finished",
|
event: "Finished",
|
||||||
eventDetail: "Left ROSS",
|
eventDetail: "Left ROSS",
|
||||||
date: "May 2018",
|
date: DateTime.fromISO("2018-05-01"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
---
|
---
|
||||||
|
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||||
|
|
||||||
import H2 from "@components/H2.astro";
|
import H2 from "@components/H2.astro";
|
||||||
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
import Carousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||||
import YtVideo from "@components/Media/YtVideo.astro";
|
import YtVideo from "@components/Media/YtVideo.astro";
|
||||||
import PageGroup from "@components/PageGroup.astro";
|
import PageGroup from "@components/PageGroup.astro";
|
||||||
|
|
||||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||||
import type { videoConfig } from "@interfaces/yt-video.ts";
|
import type { videoConfig } from "@interfaces/video.ts";
|
||||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
|
||||||
|
import { roverSubTitles } from "./osu-robotics-club.ts";
|
||||||
|
|
||||||
import circ_champions from "@assets/experience/osu-robotics-club/mars-rover-software-lead/circ-champions.jpg";
|
import circ_champions from "@assets/experience/osu-robotics-club/mars-rover-software-lead/circ-champions.jpg";
|
||||||
import corwin_at_competition from "@assets/experience/osu-robotics-club/mars-rover-software-lead/corwin-at-competition.jpg";
|
import corwin_at_competition from "@assets/experience/osu-robotics-club/mars-rover-software-lead/corwin-at-competition.jpg";
|
||||||
@@ -47,7 +51,7 @@ const videos: videoConfig[] = [
|
|||||||
];
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<ExperienceLayout title="OSURC - Software Team Lead">
|
<ExperienceLayout title="Software Team Lead" subTitles={roverSubTitles}>
|
||||||
<Carousel carouselGroup={headerCarouselGroup} />
|
<Carousel carouselGroup={headerCarouselGroup} />
|
||||||
|
|
||||||
<PageGroup>
|
<PageGroup>
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
export const subTitles = ["Oregon State University", "OSU Robotics Club"];
|
||||||
|
|
||||||
|
export const roverSubTitles = [...subTitles, "Mars Rover Team"];
|
||||||
@@ -1,5 +1,235 @@
|
|||||||
---
|
---
|
||||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
||||||
|
|
||||||
|
import H2 from "@components/H2.astro";
|
||||||
|
import H3 from "@components/H3.astro";
|
||||||
|
import Li from "@components/Li.astro";
|
||||||
|
import CustomCarousel from "@components/Media/CustomCarousel/CustomCarousel.astro";
|
||||||
|
import PageGroup from "@components/PageGroup.astro";
|
||||||
|
import Paragraph from "@components/Paragraph.astro";
|
||||||
|
import Paragraphs from "@components/Paragraphs.astro";
|
||||||
|
import PrintedCircuitBoard from "@components/PrintedCircuitBoard.astro";
|
||||||
|
import SkillMatrix from "@components/SkillMatrix/SkillMatrix.astro";
|
||||||
|
import Timeline from "@components/Timeline/Timeline.astro";
|
||||||
|
import Ul from "@components/Ul.astro";
|
||||||
|
|
||||||
|
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||||
|
import { timelineFromPrintedCircuitBoard } from "@interfaces/printed-circuit-board.ts";
|
||||||
|
import type { categorySkills } from "@interfaces/skill-matrix.ts";
|
||||||
|
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||||
|
|
||||||
|
import { dechorionatorPcb } from "./dechorionator.ts";
|
||||||
|
import {
|
||||||
|
subTitles,
|
||||||
|
workingTimeline,
|
||||||
|
} from "./osu-sinnhuber-aquatic-research-laboratory.ts";
|
||||||
|
|
||||||
|
import assembly_internal_overview from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/assembly-internal-overview.jpg";
|
||||||
|
import assembly_pcb_connected from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/assembly-pcb-connected.jpg";
|
||||||
|
import assembly_pcb_front_on from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/assembly-pcb-front-on.jpg";
|
||||||
|
import assembly_pcb_front from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/assembly-pcb-front.jpg";
|
||||||
|
import assembly_pcb_rear from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/assembly-pcb-rear.jpg";
|
||||||
|
import assembly_surround_front from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/assembly-surround-front.jpg";
|
||||||
|
import assembly_surround_rear from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/assembly-surround-rear.jpg";
|
||||||
|
import off_front_lid_open from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/off-front-lid-open.jpg";
|
||||||
|
import off_front from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/off-front.jpg";
|
||||||
|
import on_front_closeup from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/on-front-closeup.jpg";
|
||||||
|
import on_front from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/on-front.jpg";
|
||||||
|
import rear_water_manifold from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/rear-water-manifold.jpg";
|
||||||
|
import rear from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/rear.jpg";
|
||||||
|
import top_drain from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/top-drain.jpg";
|
||||||
|
import top_holder_closeup from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/top-holder-closeup.jpg";
|
||||||
|
import top_lid_open from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/top-lid-open.jpg";
|
||||||
|
import top_showerhead from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/top-showerhead.jpg";
|
||||||
|
import travel_setup from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/travel-setup.jpg";
|
||||||
|
import InlineLink from "@components/InlineLink.astro";
|
||||||
|
import PopoverWordDefinition from "@components/PopoverWordDefinition.astro";
|
||||||
|
|
||||||
|
const headerCarouselGroup: carouselGroup = {
|
||||||
|
animation: "slide",
|
||||||
|
images: [
|
||||||
|
off_front,
|
||||||
|
off_front_lid_open,
|
||||||
|
on_front,
|
||||||
|
on_front_closeup,
|
||||||
|
top_lid_open,
|
||||||
|
top_showerhead,
|
||||||
|
top_drain,
|
||||||
|
top_holder_closeup,
|
||||||
|
rear,
|
||||||
|
rear_water_manifold,
|
||||||
|
travel_setup,
|
||||||
|
|
||||||
|
assembly_surround_front,
|
||||||
|
assembly_surround_rear,
|
||||||
|
assembly_pcb_rear,
|
||||||
|
assembly_pcb_front,
|
||||||
|
assembly_pcb_front_on,
|
||||||
|
assembly_pcb_connected,
|
||||||
|
assembly_internal_overview,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const timeline: timelineEntry[] = [
|
||||||
|
...workingTimeline,
|
||||||
|
...timelineFromPrintedCircuitBoard(dechorionatorPcb),
|
||||||
|
];
|
||||||
|
|
||||||
|
const categorizedSkills: categorySkills[] = [
|
||||||
|
{
|
||||||
|
category: "Electrical",
|
||||||
|
skills: [
|
||||||
|
{
|
||||||
|
item: "Schematic & PCB Design",
|
||||||
|
subItems: [
|
||||||
|
{ item: "Mentor Graphics PADS" },
|
||||||
|
{ item: "Altium Designer" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item: "PCB Assembly & Rework",
|
||||||
|
subItems: [
|
||||||
|
{ item: "Handheld Soldering" },
|
||||||
|
{ item: "Handheld Hot-Air Reflow" },
|
||||||
|
{ item: "Oven Reflow" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
item: "Electrical Diagnostics",
|
||||||
|
subItems: [{ item: "Multimeters" }, { item: "Oscilloscopes" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
category: "Software & Environments",
|
||||||
|
skills: [
|
||||||
|
{ item: "Git" },
|
||||||
|
{
|
||||||
|
item: "Programming",
|
||||||
|
subItems: [{ item: "Low-Level Embedded C/C++ (Atmel Studio)" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
---
|
---
|
||||||
|
|
||||||
<ExperienceLayout title="SARL - Dechorionator" />
|
<ExperienceLayout title="Dechorionator" subTitles={subTitles}>
|
||||||
|
<CustomCarousel carouselGroup={headerCarouselGroup} />
|
||||||
|
<PageGroup>
|
||||||
|
<Fragment slot="header"><H2>Summary</H2></Fragment>
|
||||||
|
<PageGroup>
|
||||||
|
<Fragment slot="header"><H3>Timeline</H3></Fragment>
|
||||||
|
<Timeline timeline={timeline} />
|
||||||
|
</PageGroup>
|
||||||
|
<PageGroup>
|
||||||
|
<Fragment slot="header"><H3>Key Takeaways</H3></Fragment>
|
||||||
|
<Ul>
|
||||||
|
<Li
|
||||||
|
>Created an all-in-one tool for removing the chorions of zebrafish
|
||||||
|
embryos in a controlled and repeatable manner</Li
|
||||||
|
>
|
||||||
|
<Li
|
||||||
|
>Developed custom PCBs to handle motion, pump control, and user
|
||||||
|
interaction</Li
|
||||||
|
>
|
||||||
|
<Li
|
||||||
|
>Deployed multiple units to the lab, and one to an east-coast partner
|
||||||
|
laboratory</Li
|
||||||
|
>
|
||||||
|
<Li
|
||||||
|
>Cost reduced to roughly 1/5 that of the lab's previous dechorionation
|
||||||
|
hardware</Li
|
||||||
|
>
|
||||||
|
</Ul>
|
||||||
|
</PageGroup>
|
||||||
|
<SkillMatrix categorizedSkills={categorizedSkills} />
|
||||||
|
</PageGroup>
|
||||||
|
<PageGroup>
|
||||||
|
<Fragment slot="header"><H2>Details</H2></Fragment>
|
||||||
|
<Paragraphs>
|
||||||
|
<Paragraph>
|
||||||
|
Before delving into what was built, some quick context is probably
|
||||||
|
needed. A dechorionator is a device that removes <InlineLink
|
||||||
|
href="https://en.wikipedia.org/wiki/Chorion">chorions</InlineLink
|
||||||
|
> from embryos. Chorions are the outer membranes of an embryo which provide
|
||||||
|
protection, and a permeable membrane which can allow gasses and nutrients
|
||||||
|
to reach the developing animal inside. As SARL is a toxicology lab, and its
|
||||||
|
experiments need to be deterministic, this protective layer can drastically
|
||||||
|
skew tests results, and even worse, can can variances embryo to embryo, or
|
||||||
|
egg batch to egg batch. To remove this, a special protein is added to a petri-dish
|
||||||
|
full of embryos, and then the dish is gently swirled with jerking start and
|
||||||
|
stop motions. The goal is to provide light agitation between the embryo, the
|
||||||
|
dish, and their neighbors, helping the protein eat away the chorion and sluff
|
||||||
|
off into the dish. This can, and has been done by hand, but when I joined
|
||||||
|
SARL they already had two machines which which could automatically perform
|
||||||
|
this task. However, they were incredibly expensive and massively overcomplicated,
|
||||||
|
requiring a whole table's worth of custom shaker units, networked peristaltic
|
||||||
|
pumps, and servos. The engineering team was tasked with simplifying this setup
|
||||||
|
while reducing both their size and cost.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
We started with a <PopoverWordDefinition key="COTS" /> shaker unit from the
|
||||||
|
company ELMI, which had a stepper-motor-based drive system, making it a perfect
|
||||||
|
candidate for easy retrofit. After gutting the existing electronics, and taking
|
||||||
|
some measurements, I started on a custom PCB design. Basic requirements were
|
||||||
|
that the board needed to be able to control the stepper motor, control the
|
||||||
|
speed of a liquid pump, provide controls to users, allow for config editing
|
||||||
|
from those controls, and provide a screen for cycle progress and editing those
|
||||||
|
config values. Since this was one of the first PCBs I'd ever designed and
|
||||||
|
hand-assembled, I started with a basic proof-of-concept which was for bench
|
||||||
|
use only (revision: 1.1.0). While I worked on the electronics, my co-worker
|
||||||
|
and good friend <InlineLink href="https://dylanthrush.com"
|
||||||
|
>Dylan Thrush</InlineLink
|
||||||
|
> was busy designing a top-plate for the shaker to hold the dishes, shower
|
||||||
|
them with water, and drain the pumped-in liquid.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
First tests showed that the overall concept was going to work, just
|
||||||
|
needing signal conditioning for the rotary encoder to avoid ghosted or
|
||||||
|
missing inputs. A larger problem we found was that the brushed-dc-motor
|
||||||
|
driven peristaltic pump was not going to be able to supply the flowrate
|
||||||
|
needed to properly shower the four dishes. We'd already chosen one of
|
||||||
|
the highest-flowrate pumps which could fit inside the shaker housing,
|
||||||
|
and ended up having to pivot to a much more expensive one from TCS
|
||||||
|
Micropumps. Luckily, not only did it solve our flowrate problem, but
|
||||||
|
also held up much better to the saltwater solution being pumped through
|
||||||
|
it than our initial choice.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
With the proof-of-concept design functional, I began a redesign of the
|
||||||
|
control PCB to replace the existing control panel and drive circuitry
|
||||||
|
from the ELMI shaker (revision: 3.0.0). The existing control circuitry
|
||||||
|
had a unique assembly design that I'd not encountered before, using a
|
||||||
|
PCB with solderable standoffs as the front panel, and soldered copper
|
||||||
|
strips as retention tabs. I was so fascinated by the design that I
|
||||||
|
decided to emulate it. Check out the images at the end of the reel above
|
||||||
|
to see how this unique assembly was put together! Around the time that
|
||||||
|
the PCBs were ready, we'd hired a new engineer, <InlineLink
|
||||||
|
href="https://www.linkedin.com/in/aaron-rito-2b754777/"
|
||||||
|
>Aaron Rito</InlineLink
|
||||||
|
>, who I tasked with writing the firmware while providing input and
|
||||||
|
guidance.
|
||||||
|
</Paragraph>
|
||||||
|
<Paragraph>
|
||||||
|
Over the next few months, many revisions were made to the firmware, as
|
||||||
|
well as mechanical designs for the showerheads and water manifold. Dylan
|
||||||
|
also had a final design for the top-plate milled out that looked
|
||||||
|
beautiful. Once those changes were complete, we provided the prototype
|
||||||
|
unit to the researchers, along with documentation on how to use the
|
||||||
|
tuning values. They then spent a few weeks running the new dechorionator
|
||||||
|
alongside the old ones, while tweaking these parameters until the
|
||||||
|
performance matched. We then built up four more units, and pre-flashed
|
||||||
|
them with this configuration. Three of these went into the lab, where a
|
||||||
|
total of four of our new dechorionators sat on the same table where just
|
||||||
|
two prior-generation ones used to live. It even had additional space for
|
||||||
|
pre and post prep work on the petri-dishes! The last one I installed in
|
||||||
|
a partner lab on the east coast after flying there with the head of the
|
||||||
|
lab, Robyn Tanguay and deputy director, Lisa Truong.
|
||||||
|
</Paragraph>
|
||||||
|
</Paragraphs>
|
||||||
|
</PageGroup>
|
||||||
|
<PageGroup>
|
||||||
|
<Fragment slot="header"><H2>Printed Circuit Boards</H2></Fragment>
|
||||||
|
<PrintedCircuitBoard pcb={dechorionatorPcb} />
|
||||||
|
</PageGroup>
|
||||||
|
</ExperienceLayout>
|
||||||
|
|||||||
@@ -0,0 +1,56 @@
|
|||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
import type { printedCircuitBoard } from "@interfaces/printed-circuit-board.ts";
|
||||||
|
|
||||||
|
import pcb_1_1_0_assembled_bottom_angle from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/1-1-0/assembled-bottom-angle.jpg";
|
||||||
|
import pcb_1_1_0_assembled_bottom from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/1-1-0/assembled-bottom.jpg";
|
||||||
|
import pcb_1_1_0_assembled_side from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/1-1-0/assembled-side.jpg";
|
||||||
|
import pcb_1_1_0_bare_bottom from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/1-1-0/bare-bottom.png";
|
||||||
|
import pcb_1_1_0_bare_top from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/1-1-0/bare-top.png";
|
||||||
|
import assembled_main_bottom from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/assembled-main-bottom.jpg";
|
||||||
|
import assembled_side from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/assembled-side.jpg";
|
||||||
|
import bare_main_bottom from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/bare-main-bottom.jpg";
|
||||||
|
import bare_main_top from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/bare-main-top.jpg";
|
||||||
|
import bare_surround_bottom from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/bare-surround-bottom.jpg";
|
||||||
|
import bare_surround_top from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/bare-surround-top.jpg";
|
||||||
|
import half_assembled_main_top_surround_bottom_lcd from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/half-assembled-main-top-surround-bottom-lcd.jpg";
|
||||||
|
import half_assembled_main_top_surround_bottom from "@assets/experience/osu-sinnhuber-aquatic-research-laboratory/dechorionator/pcbs/3-0-0/half-assembled-main-top-surround-bottom.jpg";
|
||||||
|
|
||||||
|
export const dechorionatorPcb: printedCircuitBoard = {
|
||||||
|
name: "Dechorionator",
|
||||||
|
description:
|
||||||
|
"Control board which provides motion and water flow control, along with user control and monitoring.",
|
||||||
|
|
||||||
|
revisions: [
|
||||||
|
{
|
||||||
|
major: 3,
|
||||||
|
minor: 0,
|
||||||
|
patch: 0,
|
||||||
|
date: DateTime.fromISO("2015-08-30"),
|
||||||
|
images: [
|
||||||
|
half_assembled_main_top_surround_bottom,
|
||||||
|
half_assembled_main_top_surround_bottom_lcd,
|
||||||
|
assembled_main_bottom,
|
||||||
|
assembled_side,
|
||||||
|
bare_main_top,
|
||||||
|
bare_main_bottom,
|
||||||
|
bare_surround_top,
|
||||||
|
bare_surround_bottom,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
major: 1,
|
||||||
|
minor: 1,
|
||||||
|
patch: 0,
|
||||||
|
date: DateTime.fromISO("2014-05-10"),
|
||||||
|
images: [
|
||||||
|
pcb_1_1_0_assembled_bottom_angle,
|
||||||
|
pcb_1_1_0_assembled_bottom,
|
||||||
|
pcb_1_1_0_assembled_side,
|
||||||
|
pcb_1_1_0_bare_top,
|
||||||
|
pcb_1_1_0_bare_bottom,
|
||||||
|
],
|
||||||
|
// notes: [{ item: "Effective first functional revision" }],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
|
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||||
|
|
||||||
|
export const subTitles = [
|
||||||
|
"Oregon State University",
|
||||||
|
"College of Agricultural Sciences",
|
||||||
|
"Department of Environmental and Molecular Toxicology",
|
||||||
|
"Sinnhuber Aquatic Research Laboratory",
|
||||||
|
];
|
||||||
|
|
||||||
|
export const workingTimeline: timelineEntry[] = [
|
||||||
|
{
|
||||||
|
event: "Started",
|
||||||
|
eventDetail: "Joined SARL Engineering",
|
||||||
|
date: DateTime.fromISO("2013-09-01"),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
event: "Finished",
|
||||||
|
eventDetail: "Left SARL Engineering",
|
||||||
|
date: DateTime.fromISO("2019-08-01"),
|
||||||
|
},
|
||||||
|
];
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
---
|
|
||||||
import ExperienceLayout from "@layouts/ExperienceLayout.astro";
|
|
||||||
---
|
|
||||||
|
|
||||||
<ExperienceLayout title="SARL - Team Lead" />
|
|
||||||
@@ -19,6 +19,7 @@ import swag from "@assets/experience/spacex/avionics-test-engineering-internship
|
|||||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||||
import type { categorySkills } from "@interfaces/skill-matrix.ts";
|
import type { categorySkills } from "@interfaces/skill-matrix.ts";
|
||||||
import type { timelineEntry } from "@interfaces/timeline.ts";
|
import type { timelineEntry } from "@interfaces/timeline.ts";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
const headerCarouselGroup: carouselGroup = {
|
const headerCarouselGroup: carouselGroup = {
|
||||||
animation: "slide",
|
animation: "slide",
|
||||||
@@ -28,11 +29,11 @@ const headerCarouselGroup: carouselGroup = {
|
|||||||
const timeline: timelineEntry[] = [
|
const timeline: timelineEntry[] = [
|
||||||
{
|
{
|
||||||
event: "Started",
|
event: "Started",
|
||||||
date: "January 2019",
|
date: DateTime.fromISO("2019-01-01"),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "Finished",
|
event: "Finished",
|
||||||
date: "March 2019",
|
date: DateTime.fromISO("2019-03-01"),
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ import type { timelineEntry } from "@interfaces/timeline.ts";
|
|||||||
import five_year_patch from "@assets/experience/spacex/hardware-test-engineer-i-ii/five-year-patch.jpg";
|
import five_year_patch from "@assets/experience/spacex/hardware-test-engineer-i-ii/five-year-patch.jpg";
|
||||||
import starlink_headquarters_selfie from "@assets/experience/spacex/hardware-test-engineer-i-ii/starlink-headquarters-selfie.jpg";
|
import starlink_headquarters_selfie from "@assets/experience/spacex/hardware-test-engineer-i-ii/starlink-headquarters-selfie.jpg";
|
||||||
import starlink_patch from "@assets/experience/spacex/hardware-test-engineer-i-ii/starlink-patch.jpg";
|
import starlink_patch from "@assets/experience/spacex/hardware-test-engineer-i-ii/starlink-patch.jpg";
|
||||||
|
import { DateTime } from "luxon";
|
||||||
|
|
||||||
const headerCarouselGroup: carouselGroup = {
|
const headerCarouselGroup: carouselGroup = {
|
||||||
animation: "slide",
|
animation: "slide",
|
||||||
@@ -30,28 +31,28 @@ const timeline: timelineEntry[] = [
|
|||||||
{
|
{
|
||||||
event: "Started",
|
event: "Started",
|
||||||
eventDetail: "Satellite Hardware Test Team",
|
eventDetail: "Satellite Hardware Test Team",
|
||||||
date: "September 2019",
|
date: DateTime.fromISO("2019-09-01"),
|
||||||
description:
|
description:
|
||||||
"Owned test systems for four generations of Starlink flight computers and two generations of power boards",
|
"Owned test systems for four generations of Starlink flight computers and two generations of power boards",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "Transitioned To Remote",
|
event: "Transitioned To Remote",
|
||||||
eventDetail: "Moved To Oregon",
|
eventDetail: "Moved To Oregon",
|
||||||
date: "August 2022",
|
date: DateTime.fromISO("2022-08-01"),
|
||||||
description:
|
description:
|
||||||
"Personal decision, but I was allowed to work on tools for the build reliability engineering team",
|
"Personal decision, but I was allowed to work on tools for the build reliability engineering team",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "Changed Teams",
|
event: "Changed Teams",
|
||||||
eventDetail: "Components Test Infra Team",
|
eventDetail: "Components Test Infra Team",
|
||||||
date: "March 2024",
|
date: DateTime.fromISO("2024-03-01"),
|
||||||
description:
|
description:
|
||||||
"Vertical move that allowed for broader application of my skills",
|
"Vertical move that allowed for broader application of my skills",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
event: "Finished",
|
event: "Finished",
|
||||||
eventDetail: "Thanks for all the fish!",
|
eventDetail: "Thanks for all the fish!",
|
||||||
date: "April 2025",
|
date: DateTime.fromISO("2025-04-01"),
|
||||||
description:
|
description:
|
||||||
"Celebrated five and a half years of helping put thousands of satellites, and dozens of rockets, into orbit",
|
"Celebrated five and a half years of helping put thousands of satellites, and dozens of rockets, into orbit",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -103,16 +103,16 @@ const categorizedSkills: categorySkills[] = [
|
|||||||
From the DevOps perspective, I created a Makefile within the repo for
|
From the DevOps perspective, I created a Makefile within the repo for
|
||||||
local development targets to build, run, and test both in a pure
|
local development targets to build, run, and test both in a pure
|
||||||
context, and within a Docker container. After pushing updates on a
|
context, and within a Docker container. After pushing updates on a
|
||||||
branch to my local Gitea instance, and opening a pull request, the
|
branch to my local Gitea instance, and opening a pull request, an
|
||||||
website performs spelling checks, runs unit tests, and integration
|
actions runner performs spelling checks, runs unit tests, and
|
||||||
tests. Once these pass, the website is built into a Docker container and
|
integration tests. Once these pass, the website is built into a Docker
|
||||||
uploaded to the registry in my Gitea instance. The image is then
|
container and uploaded to the registry in my Gitea instance. The image
|
||||||
deployed to staging on my <PopoverWordDefinition key="VPS" />, allowing
|
is then deployed to staging on my <PopoverWordDefinition key="VPS" />,
|
||||||
for manual validation of the changes. In order to merge, the build,
|
allowing for manual validation of the changes. In order to merge, the
|
||||||
test, and deploy actions must pass, and ideally I should be empirically
|
build, test, and deploy actions must pass, and I empirically validate
|
||||||
validating the staging deployment. After merging and closing the pull
|
the deployment. Post-merge, a similar action builds, tests, and deploys
|
||||||
request, another action builds, tests, and deploys the main branch in
|
the main branch in the same way as before, but to production, and is
|
||||||
the same way as before, but to production, and is what you see here!
|
what you see here!
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
</Paragraphs>
|
</Paragraphs>
|
||||||
<SkillMatrix categorizedSkills={categorizedSkills} />
|
<SkillMatrix categorizedSkills={categorizedSkills} />
|
||||||
|
|||||||
@@ -10,9 +10,9 @@ import Paragraphs from "@components/Paragraphs.astro";
|
|||||||
|
|
||||||
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
import type { carouselGroup } from "@interfaces/image-carousel.ts";
|
||||||
|
|
||||||
import alaska_bike_mountain_ocean from "@assets/about/alaska-bike-mountain-ocean.jpg";
|
|
||||||
import headshot from "@assets/about/headshot.jpg";
|
import headshot from "@assets/about/headshot.jpg";
|
||||||
import circ_champions from "@assets/experience/osu-robotics-club/mars-rover-software-lead/circ-champions.jpg";
|
import circ_champions from "@assets/experience/osu-robotics-club/mars-rover-software-lead/circ-champions.jpg";
|
||||||
|
import alaska_bike_mountain_ocean from "@assets/hobby/motorcycling/trips/2025-08-alaska/alaska-bike-mountain-ocean.jpg";
|
||||||
|
|
||||||
const headerCarouselGroup: carouselGroup = {
|
const headerCarouselGroup: carouselGroup = {
|
||||||
animation: "slide",
|
animation: "slide",
|
||||||
@@ -88,7 +88,7 @@ const headerCarouselGroup: carouselGroup = {
|
|||||||
>rfid implant</InlineLink
|
>rfid implant</InlineLink
|
||||||
> in my hand!
|
> in my hand!
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Paragraph class="mt-8 flex flex-col items-center" initialTab={false}>
|
<Paragraph class="mt-4 flex flex-col items-center" initialTab={false}>
|
||||||
<div>
|
<div>
|
||||||
If you're interested in contacting me, feel free to message on <InlineLink
|
If you're interested in contacting me, feel free to message on <InlineLink
|
||||||
href="https://github.com/caperren">LinkedIn</InlineLink
|
href="https://github.com/caperren">LinkedIn</InlineLink
|
||||||
|
|||||||
@@ -4,4 +4,8 @@ import ResumeLayout from "@layouts/ResumeLayout.astro";
|
|||||||
import resume from "@assets/resume/corwin_perren_2019-07-01_hardware_test_engineer.pdf";
|
import resume from "@assets/resume/corwin_perren_2019-07-01_hardware_test_engineer.pdf";
|
||||||
---
|
---
|
||||||
|
|
||||||
<ResumeLayout title="2019-07-01 - Hardware Test Engineer" resume={resume} />
|
<ResumeLayout
|
||||||
|
title="Hardware Test Engineer"
|
||||||
|
subTitles={["2019-07-01"]}
|
||||||
|
resume={resume}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -4,4 +4,8 @@ import ResumeLayout from "@layouts/ResumeLayout.astro";
|
|||||||
import resume from "@assets/resume/corwin_perren_2025-10-27_infrastructure_engineer.pdf";
|
import resume from "@assets/resume/corwin_perren_2025-10-27_infrastructure_engineer.pdf";
|
||||||
---
|
---
|
||||||
|
|
||||||
<ResumeLayout title="2025-10-27 - Infrastructure Engineer" resume={resume} />
|
<ResumeLayout
|
||||||
|
title="Infrastructure Engineer"
|
||||||
|
subTitles={["2025-10-27"]}
|
||||||
|
resume={resume}
|
||||||
|
/>
|
||||||
|
|||||||
@@ -40,5 +40,8 @@ test("Pages Missing from Astro Paths", () => {
|
|||||||
siteLayoutPaths,
|
siteLayoutPaths,
|
||||||
astroStaticPaths,
|
astroStaticPaths,
|
||||||
);
|
);
|
||||||
expect(siteLayoutNotAstroPaths).toHaveLength(0);
|
expect(
|
||||||
|
siteLayoutNotAstroPaths,
|
||||||
|
`FOUND: ${[...siteLayoutNotAstroPaths]}`,
|
||||||
|
).toHaveLength(0);
|
||||||
});
|
});
|
||||||
|
|||||||