Made a baseline working carousel, timeline, and started flushing out content for primary spacex experience
This commit is contained in:
@@ -1,8 +0,0 @@
|
||||
---
|
||||
const { images } = Astro.props;
|
||||
---
|
||||
<div class="carousel">
|
||||
{images.map(img => (
|
||||
<img src={img} alt="carousel item" style="width:100%; max-width:600px; margin: 1rem auto; display:block;" />
|
||||
))}
|
||||
</div>
|
||||
173
src/components/CustomCarousel.astro
Normal file
173
src/components/CustomCarousel.astro
Normal file
@@ -0,0 +1,173 @@
|
||||
---
|
||||
import {Image} from 'astro:assets';
|
||||
|
||||
import type {carouselGroup} from "@interfaces/image-carousel.ts";
|
||||
|
||||
const groupToShow: carouselGroup = Astro.props.carouselGroup;
|
||||
---
|
||||
|
||||
<custom-carousel class="flex flex-col relative w-full"
|
||||
data-custom-carousel={groupToShow.animation}
|
||||
data-custom-carousel-interval={groupToShow.interval}>
|
||||
|
||||
<!-- Carousel wrapper -->
|
||||
<div class="relative overflow-hidden h-56 md:h-156">
|
||||
{
|
||||
groupToShow.images.map((image, index) => (
|
||||
<div class="hidden duration-700 ease-in-out" data-custom-carousel-item>
|
||||
<Image src={image}
|
||||
class="relative sm:max-w-xl md:max-w-3xl lg:max-w-5xl xl:max-w-7xl -translate-x-1/2 -translate-y-1/2 top-1/2 left-1/2"
|
||||
alt="..."
|
||||
loading="eager"/>
|
||||
</div>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
|
||||
{(groupToShow.images.length > 1) && (
|
||||
<!-- Slider indicators -->
|
||||
<div class="absolute z-30 flex -translate-x-1/2 bottom-8 md: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>
|
||||
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._carousel.cycle();
|
||||
|
||||
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: 1,
|
||||
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)
|
||||
</script>
|
||||
@@ -20,7 +20,7 @@ import {siteLayout} from "@data/site-layout.ts";
|
||||
d="M1 1h15M1 7h15M1 13h15"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="hidden w-full md:block md:w-auto" id="navbar-multi-level">
|
||||
<div class="z-40 hidden w-full md:block md:w-auto" id="navbar-multi-level">
|
||||
<NestedNavbarEntry items={siteLayout}/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -21,7 +21,7 @@ const getHrefPath = (entry: navLink): string => {
|
||||
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.title}
|
||||
{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"
|
||||
@@ -39,7 +39,7 @@ const getHrefPath = (entry: navLink): string => {
|
||||
|
||||
<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.title}</a>
|
||||
aria-current="page">{entry.navText}</a>
|
||||
|
||||
)}
|
||||
</li>
|
||||
|
||||
78
src/components/Timeline.astro
Normal file
78
src/components/Timeline.astro
Normal file
@@ -0,0 +1,78 @@
|
||||
---
|
||||
import type {timelineEntry} from "@interfaces/timeline.ts";
|
||||
const timeline: timelineEntry[] = Astro.props.timeline || [];
|
||||
---
|
||||
|
||||
<custom-timeline>
|
||||
|
||||
<div class="relative z-10 grid gap-6 grid-flow-row sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 3xl:grid-cols-6"
|
||||
data-timeline>
|
||||
{timeline.map((entry, index) => (
|
||||
<div class="pt-1 border bg-black border-caperren-green rounded-lg min-w-s max-w-s px-2 pb-2"
|
||||
data-timeline-node-index={index}
|
||||
>
|
||||
<h3 class="text-lg font-bold">
|
||||
{entry.event}
|
||||
</h3>
|
||||
<h4 class="font-semibold leading-none">
|
||||
{entry.eventDetail}
|
||||
</h4>
|
||||
<time class="mb-2 mt-1 text-sm italic leading-none">
|
||||
{entry.date}
|
||||
</time>
|
||||
{entry.description && (
|
||||
<p class="text-sm font-normal">
|
||||
{entry.description}
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</custom-timeline>
|
||||
|
||||
<script>
|
||||
import LeaderLine from "leader-line-new";
|
||||
|
||||
class CustomTimeline extends HTMLElement {
|
||||
_eventElements: Element[];
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this._eventElements = this._getNodeElements();
|
||||
this._paintLeaderLines();
|
||||
|
||||
}
|
||||
|
||||
_getNodeElements = (): Element[] => {
|
||||
return Array.from(
|
||||
this.querySelectorAll('[data-timeline-node-index]')
|
||||
).sort(
|
||||
(elementA, elementB) =>
|
||||
Number(elementA.getAttribute('data-timeline-node-index')) - Number(elementB.getAttribute('data-timeline-node-index'))
|
||||
);
|
||||
}
|
||||
|
||||
_paintLeaderLines = () => {
|
||||
let pairs = this._eventElements.map((entry, index) => {
|
||||
if (index < this._eventElements.length - 1) {
|
||||
return [entry, this._eventElements[index + 1]];
|
||||
}
|
||||
}).filter(pair => pair !== undefined)
|
||||
|
||||
pairs.forEach(pair => {
|
||||
new LeaderLine({
|
||||
start: pair[0],
|
||||
end: pair[1],
|
||||
color: '#10ac25',
|
||||
size: 3,
|
||||
startSocket: "right",
|
||||
endSocket: "left",
|
||||
startPlug: "square",
|
||||
endPlug: "arrow3"
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
customElements.define('custom-timeline', CustomTimeline)
|
||||
</script>
|
||||
Reference in New Issue
Block a user