Component for PCBs, many visual tweaks, finished dechorionator content, added many many photos, started work on mars rover software lead, timeline to luxon and automatic date-based ordering
Some checks failed
Build and Test - Staging / test (pull_request) Failing after 4m56s
Build and Test - Staging / build_and_push (pull_request) Has been skipped
Build and Test - Staging / deploy_staging (pull_request) Has been skipped

This commit is contained in:
2025-12-12 22:48:03 -08:00
parent adcbce68c8
commit 8fd744118f
67 changed files with 586 additions and 107 deletions

View File

@@ -1,15 +1,21 @@
---
import { Image } from "astro:assets";
import type { ComponentPropsBase } from "@interfaces/components.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
class="relative flex w-full flex-col"
data-custom-carousel={groupToShow.animation}
data-custom-carousel-interval={groupToShow.interval}
data-custom-carousel={carouselGroup.animation ?? "slide"}
data-custom-carousel-interval={carouselGroup.interval}
>
<!-- Modal for fullscreen viewing -->
<div
@@ -57,54 +63,63 @@ const groupToShow: carouselGroup = Astro.props.carouselGroup;
</div>
<!-- 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) => (
<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>
))
carouselGroup.images &&
carouselGroup.images.map((image, index) => (
<div
class="hidden bg-black 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 && (
carouselGroup.images && carouselGroup.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}
/>
))}
{carouselGroup.images &&
carouselGroup.images.map((_, index) => (
<button
type="button"
class="hover:bg-caperren-green-light h-3 w-3 rounded-full"
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">
<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
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"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 6 10"
viewBox="0 0 8 10"
>
<path
stroke="currentColor"
fill-opacity="0%"
stroke-linecap="round"
stroke-linejoin="round"
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"
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
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"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 6 10"
viewBox="0 0 4 10"
>
<path
stroke="currentColor"
fill-opacity="0%"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"

View File

@@ -22,7 +22,7 @@ const {
<div class="mx-auto my-auto">
<video
class="h-auto w-full"
class="border-caperren-green-dark h-auto w-full rounded-lg border"
controls={controls}
autoplay={autoPlay}
loop={loop}

View File

@@ -1,5 +1,5 @@
---
import type { videoConfig } from "@interfaces/yt-video.ts";
import type { videoConfig } from "@interfaces/video.ts";
interface Props extends videoConfig {}
@@ -18,7 +18,7 @@ const aspect = `${width} / ${height}`;
<iframe
src={videoPath}
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; " +
(autoPlay ? "autoplay;" : "")}
referrerpolicy="strict-origin-when-cross-origin"

View File

@@ -36,7 +36,8 @@ const { pathname } = Astro.url;
data-dropdown-offset-skidding="12"
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",
pathname.startsWith(getHrefPath(paths, entry))
pathname.startsWith(getHrefPath(paths, entry)) &&
!(entry.placeholderEntry ?? false)
? "border-caperren-green border-b-2"
: false,
]}
@@ -71,20 +72,32 @@ const { pathname } = Astro.url;
) : (
<div>
<a
href={getHrefPath(paths, entry)}
href={
!(entry.placeholderEntry ?? false)
? getHrefPath(paths, entry)
: undefined
}
target={
getHrefPath(paths, entry).startsWith("/") ? "" : "_blank"
}
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)
? "border-caperren-green border-b-2"
: false,
entry.isSubItem ? "ms-3" : false,
entry.placeholderEntry
? false
: "hover:text-caperren-green-light",
]}
aria-current={
pathname === getHrefPath(paths, entry) ? "page" : undefined
pathname === getHrefPath(paths, entry) &&
!(entry.placeholderEntry ?? false)
? "page"
: undefined
}
>
{entry.isSubItem && "∟ "}
{entry.navText}
</a>
</div>

View File

@@ -3,7 +3,7 @@ const hasHeader = Astro.slots.has("header");
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") && (
<div>

View 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>

View File

@@ -1,7 +1,11 @@
---
import type { timelineEntry } from "@interfaces/timeline.ts";
const timeline: timelineEntry[] = Astro.props.timeline || [];
interface Props {
timeline: timelineEntry[];
}
const { timeline = [] } = Astro.props;
---
<custom-timeline>
@@ -11,21 +15,25 @@ const timeline: timelineEntry[] = Astro.props.timeline || [];
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>
))
timeline
.sort((a: timelineEntry, b: timelineEntry) =>
a.date.diff(b.date).toMillis(),
)
.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.toFormat("LLLL kkkk")}
</time>
{entry.description && (
<p class="text-sm font-normal">{entry.description}</p>
)}
</div>
))
}
</div>
<div