Skip to content

Commit 2a332f3

Browse files
authored
Merge pull request #1 from NateOs/website-fixes-nathan
Website fixes nathan
2 parents 702836b + 7545609 commit 2a332f3

5 files changed

Lines changed: 9734 additions & 56 deletions

File tree

app/components/HeroSection.vue

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,43 @@
1+
<script setup>
2+
import { onBeforeUnmount, onMounted, ref } from "vue";
3+
4+
const videoFrame = ref(null);
5+
let observer;
6+
7+
const postToPlayer = (func) => {
8+
const frame = videoFrame.value;
9+
if (!frame || !frame.contentWindow) return;
10+
frame.contentWindow.postMessage(
11+
JSON.stringify({ event: "command", func, args: [] }),
12+
"*"
13+
);
14+
};
15+
16+
onMounted(() => {
17+
const frame = videoFrame.value;
18+
if (!frame) return;
19+
20+
observer = new IntersectionObserver(
21+
([entry]) => {
22+
if (entry.isIntersecting) {
23+
postToPlayer("mute");
24+
postToPlayer("playVideo");
25+
} else {
26+
postToPlayer("pauseVideo");
27+
}
28+
},
29+
{ threshold: 0.4 }
30+
);
31+
32+
observer.observe(frame);
33+
});
34+
35+
onBeforeUnmount(() => {
36+
if (observer && videoFrame.value) observer.unobserve(videoFrame.value);
37+
observer = null;
38+
});
39+
</script>
40+
141
<template>
242
<!-- Hero Section -->
343
<section class="py-32 px-6 ">
@@ -119,8 +159,9 @@
119159
<div class="rounded-2xl overflow-hidden shadow-2xl">
120160
<div class="w-full aspect-video">
121161
<iframe
162+
ref="videoFrame"
122163
class="w-full h-full"
123-
src="https://www.youtube-nocookie.com/embed/vNWWGWXjybg?rel=0&modestbranding=1&playsinline=1"
164+
src="https://www.youtube-nocookie.com/embed/vNWWGWXjybg?rel=0&modestbranding=1&playsinline=1&enablejsapi=1"
124165
title="DevCongress – Community Highlight"
125166
loading="lazy"
126167
referrerpolicy="strict-origin-when-cross-origin"
Lines changed: 140 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,34 +1,58 @@
11
<template>
22
<section class="py-24 px-6 text-white">
33
<div class="max-w-7xl mx-auto text-center mb-16">
4-
<h2 class="text-5xl lg:text-7xl font-bold text-center mb-16 tracking-tighter">
5-
See What’s Possible When You <br class="hidden sm:inline-flex" />
4+
<h2
5+
class="text-5xl lg:text-7xl font-bold text-center mb-16 tracking-tighter">
6+
See What’s Possible When You
7+
<br class="hidden sm:inline-flex" />
68
<span class="text-primary-500">Have the Right Support</span>
79
</h2>
810
</div>
911

1012
<!-- HORIZONTAL SCROLLER -->
11-
<div class="relative mx-auto">
12-
<div class="overflow-x-auto snap-x snap-mandatory scrollbar-none -mx-6 px-6">
13-
<div class="grid grid-flow-col auto-cols-[minmax(16rem,20rem)] gap-8 py-2">
13+
<div class="relative mx-auto max-w-7xl">
14+
<!-- Prev / Next buttons -->
15+
<button
16+
@click="scrollBy(-1)"
17+
aria-label="Previous testimonials"
18+
class="absolute left-4 top-1/2 -translate-y-1/2 z-20 bg-black/50 backdrop-blur-md text-white rounded-full p-4 text-2xl transition-colors duration-150 testimonial-scroll-button focus:outline-none">
19+
20+
</button>
21+
<button
22+
@click="scrollBy(1)"
23+
aria-label="Next testimonials"
24+
class="absolute right-4 top-1/2 -translate-y-1/2 z-20 bg-black/50 backdrop-blur-md text-white rounded-full p-4 text-2xl transition-colors duration-150 testimonial-scroll-button focus:outline-none">
25+
26+
</button>
27+
28+
<div
29+
ref="scroller"
30+
role="region"
31+
aria-label="Testimonials carousel"
32+
tabindex="0"
33+
class="overflow-x-auto snap-x snap-mandatory scrollbar-none -mx-6 px-6 py-2">
34+
<div class="flex gap-8 items-start">
1435
<!-- CARD -->
15-
<div v-for="(t, i) in testimonials" :key="i" class="snap-start group">
36+
<div
37+
v-for="(t, i) in testimonials"
38+
:key="i"
39+
class="snap-start min-w-[14.4rem] w-[18rem] sm:w-[21.6rem] shrink-0 group">
1640
<div
17-
class="relative w-full aspect-4/3 sm:aspect-9/16 rounded-3xl overflow-hidden shadow-lg group hover:scale-[1.02] transition duration-300"
18-
>
41+
class="relative w-full rounded-3xl overflow-hidden shadow-lg group-hover:scale-[1.02] transition-transform duration-300"
42+
style="aspect-ratio: 4/3">
1943
<img
2044
:src="t.image"
2145
:alt="t.name"
22-
class="w-full h-full object-cover grayscale-100 group-hover:grayscale-0 group-hover:sepia-0 transition ease-out duration-300 delay-75"
23-
/>
46+
:data-seed="t.name || i"
47+
@error="onImgError"
48+
class="w-full h-full object-cover grayscale-100 group-hover:grayscale-0 transition ease-out duration-300" />
2449
</div>
2550

2651
<p class="text-base text-gray-100 mt-4 italic leading-relaxed">
2752
“{{ t.quote }}”
2853
<br />
2954
<span
30-
class="not-italic font-semibold text-gray-400 group-hover:text-secondary-400"
31-
>
55+
class="not-italic font-semibold text-gray-400 group-hover:text-secondary-400">
3256
– {{ t.name }}, {{ t.role }}
3357
</span>
3458
</p>
@@ -40,7 +64,105 @@
4064
</template>
4165

4266
<script setup>
43-
import { testimonials } from '../data/testimonials';
67+
import { onMounted, onBeforeUnmount, ref } from "vue";
68+
import { testimonials as rawTestimonials } from "../data/testimonials";
69+
70+
const scroller = ref(null);
71+
const testimonials = ref([]);
72+
73+
function shuffleArray(array) {
74+
const shuffled = [...array];
75+
for (let i = shuffled.length - 1; i > 0; i--) {
76+
const j = Math.floor(Math.random() * (i + 1));
77+
[shuffled[i], shuffled[j]] = [shuffled[j], shuffled[i]];
78+
}
79+
return shuffled;
80+
}
81+
82+
function scrollBy(direction = 1) {
83+
const el = scroller.value;
84+
if (!el) return;
85+
const amount = Math.round(el.clientWidth * 0.8);
86+
el.scrollBy({ left: direction * amount, behavior: "smooth" });
87+
}
88+
89+
let pointerDown = false;
90+
let startX = 0;
91+
let scrollLeft = 0;
92+
93+
function onPointerDown(e) {
94+
const el = scroller.value;
95+
if (!el) return;
96+
pointerDown = true;
97+
el.setPointerCapture(e.pointerId);
98+
startX = e.clientX;
99+
scrollLeft = el.scrollLeft;
100+
}
101+
102+
function onPointerMove(e) {
103+
if (!pointerDown) return;
104+
const el = scroller.value;
105+
if (!el) return;
106+
const dx = e.clientX - startX;
107+
el.scrollLeft = scrollLeft - dx;
108+
}
109+
110+
function onPointerUp(e) {
111+
const el = scroller.value;
112+
if (!el) return;
113+
pointerDown = false;
114+
try {
115+
el.releasePointerCapture?.(e.pointerId);
116+
} catch (err) {
117+
// ignore
118+
}
119+
}
120+
121+
function onKeyDown(e) {
122+
if (e.key === "ArrowLeft") {
123+
e.preventDefault();
124+
scrollBy(-1);
125+
} else if (e.key === "ArrowRight") {
126+
e.preventDefault();
127+
scrollBy(1);
128+
}
129+
}
130+
131+
function onImgError(e) {
132+
try {
133+
e.target.onerror = null;
134+
const seed = e.target?.dataset?.seed || String(Date.now());
135+
const style = "adventurer"; // fun avatar style
136+
const params = new URLSearchParams({
137+
seed: seed,
138+
backgroundType: "solid",
139+
}).toString();
140+
e.target.src = `https://api.dicebear.com/6.x/${style}/svg?${params}`;
141+
} catch (err) {
142+
// fallback no-op
143+
}
144+
}
145+
146+
onMounted(() => {
147+
testimonials.value = shuffleArray(rawTestimonials);
148+
const el = scroller.value;
149+
if (!el) return;
150+
el.addEventListener("pointerdown", onPointerDown);
151+
el.addEventListener("pointermove", onPointerMove);
152+
el.addEventListener("pointerup", onPointerUp);
153+
el.addEventListener("pointercancel", onPointerUp);
154+
el.addEventListener("keydown", onKeyDown);
155+
});
156+
157+
onBeforeUnmount(() => {
158+
const el = scroller.value;
159+
if (!el) return;
160+
el.removeEventListener("pointerdown", onPointerDown);
161+
el.removeEventListener("pointermove", onPointerMove);
162+
el.removeEventListener("pointerup", onPointerUp);
163+
el.removeEventListener("pointercancel", onPointerUp);
164+
el.removeEventListener("keydown", onKeyDown);
165+
});
44166
</script>
45167
46168
<style>
@@ -51,4 +173,9 @@ import { testimonials } from '../data/testimonials';
51173
-ms-overflow-style: none;
52174
scrollbar-width: none;
53175
}
176+
177+
.testimonial-scroll-button:hover {
178+
background: oklch(0.9412 0.1999 105.66) !important;
179+
color: black !important;
180+
}
54181
</style>

app/components/WhatWeveBeenUpTo.vue

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,38 @@
1313
class="grid grid-cols-1 md:grid-cols-6 gap-4 md:gap-6 auto-rows-[minmax(8rem,auto)] md:grid-flow-dense"
1414
>
1515
<div v-for="(card, i) in cards" :key="i" :class="card.size">
16+
<div
17+
v-if="card.title === 'Echo Podcast'"
18+
class="group block h-full rounded-3xl transition duration-300 bg-(--card-color)/5 hover:bg-(--card-color)/20"
19+
:style="{ '--card-color': card.color }"
20+
>
21+
<div class="flex h-full flex-col gap-4 p-5 md:p-7">
22+
<!-- Copy -->
23+
<div class="w-full">
24+
<h3
25+
class="text-2xl md:text-4xl font-extrabold tracking-tight text-gray-100 transition-colors duration-300 group-hover:text-(--card-color)"
26+
>
27+
{{ card.title }}
28+
</h3>
29+
<p
30+
class="text-base md:text-lg text-white/60 leading-snug mt-2 transition-colors duration-300 group-hover:text-white"
31+
>
32+
{{ card.description }}
33+
</p>
34+
</div>
35+
36+
<iframe
37+
src="https://embed.acast.com/653bf1eb18e0ae00111ac1a1?episode-order=desc&accentColor=161616&bgColor=fcf404&secondaryColor=161616&feed=true"
38+
frameborder="0"
39+
width="100%"
40+
height="280px"
41+
title="Echo Podcast"
42+
></iframe>
43+
</div>
44+
</div>
45+
1646
<a
47+
v-else
1748
:href="card.link"
1849
class="group block h-full rounded-3xl transition duration-300 bg-(--card-color)/5 hover:bg-(--card-color)/20"
1950
:style="{ '--card-color': card.color }"
@@ -56,4 +87,4 @@
5687

5788
<script setup>
5889
import { activities as cards } from '../data/activities';
59-
</script>
90+
</script>

0 commit comments

Comments
 (0)