This commit is contained in:
Vula Builder
2026-10-11 14:20:28 +00:00
parent 964bd67656
commit 812afdab00
@@ -0,0 +1,199 @@
'use client'
import { useEffect, useState } from 'react'
import { ChevronLeft, ChevronRight, Quote } from 'lucide-react'
const voices = [
{
quote: `A box of those wok-tossed noodles and the walk home from school stops being just a walk. The chilli catches you at the back of the throat — nothing else on this street does that.`,
attribution: `Learner, walking home past the corner`,
detail: `Afternoon rush`,
},
{
quote: `I used to grab a kota every afternoon. Now it is noodles twice a week. Same sort of money, but I can watch it go into the wok and come out steaming.`,
attribution: `Daily commuter`,
detail: `Taxi rank run`,
},
{
quote: `Ben still tosses with the same wrist snap his uncle taught him. You can hear the pan over the amapiano from the street.`,
attribution: `Family, on box duty at the counter`,
detail: `Weekend shift`,
},
{
quote: `The cabbage still has crunch. That is the part I tell people about — they expect soft noodles, and then they bite into it.`,
attribution: `Regular since the red sign went up`,
detail: `Friday takeaway queue`,
},
{
quote: `We drove through from town because someone at work would not stop talking about the dark soy glaze. I did not expect to find it on our own corner.`,
attribution: `Weekend foodie`,
detail: `Came through from Newcastle`,
},
{
quote: `My son does not ask for chips anymore. He asks if we are going past the red sign.`,
attribution: `Parent, school pickup`,
detail: `Weekday afternoons`,
},
]
const streetNotes = [
{
label: `Rush hour`,
note: `When the school bell goes, the boxes stack three deep on the counter and nobody minds the wait — it smells too good to complain.`,
},
{
label: `Made in front of you`,
note: `The wok is loud on purpose. High heat, quick toss, straight into the box while it is still spitting.`,
},
{
label: `Takeaway first`,
note: `Order on the way past, collect at the corner, eat while it is hot. That is how most of Section 4 does it.`,
},
]
export default function NeighbourhoodLoveSection() {
const [index, setIndex] = useState(0)
const [paused, setPaused] = useState(false)
useEffect(() => {
if (paused) return
const motionQuery = window.matchMedia('(prefers-reduced-motion: reduce)')
if (motionQuery.matches) return
const timer = window.setInterval(() => {
setIndex((current) => (current + 1) % voices.length)
}, 8000)
return () => window.clearInterval(timer)
}, [paused])
const goTo = (next: number) => {
setIndex((next + voices.length) % voices.length)
}
return (
<section
id="neighbourhood-love"
data-vula-section="neighbourhoodlove"
className="bg-background text-foreground py-24 sm:py-32"
>
<div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8">
<div className="max-w-3xl">
<div className="flex items-center gap-4">
<span className="h-px w-14 bg-accent" aria-hidden="true" />
<span className="text-[11px] uppercase tracking-[0.32em] text-muted-foreground">Word of mouth, Section 4</span>
</div>
<h2 className="mt-6 text-4xl font-light leading-tight tracking-tight sm:text-5xl">
The queue talks <span className="italic text-accent">before the wok does.</span>
</h2>
<p className="mt-5 max-w-2xl text-base leading-relaxed text-muted-foreground sm:text-lg">
Nobody wrote these down for us. They came off the pavement outside the red sign — learners on the walk home, commuters waiting on a taxi, neighbours who order the same box every Friday.
</p>
</div>
<div
className="mt-14 grid gap-10 lg:grid-cols-12 lg:gap-12"
onMouseEnter={() => setPaused(true)}
onMouseLeave={() => setPaused(false)}
onFocus={() => setPaused(true)}
onBlur={() => setPaused(false)}
>
<div className="lg:col-span-8">
<div className="relative overflow-hidden rounded-lg border border-foreground/20 bg-foreground/10 p-8 backdrop-blur-md sm:p-12">
<Quote className="h-7 w-7 text-accent" aria-hidden="true" />
<div className="relative mt-6 min-h-[340px] sm:min-h-[260px]">
{voices.map((voice, i) => (
<figure
key={voice.attribution}
aria-hidden={i !== index}
className={
i === index
? 'absolute inset-0 opacity-100 transition-opacity duration-700 ease-out'
: 'pointer-events-none absolute inset-0 opacity-0 transition-opacity duration-700 ease-out'
}
>
<blockquote className="text-xl font-light leading-relaxed text-foreground sm:text-2xl">
{voice.quote}
</blockquote>
<figcaption className="mt-8 text-[11px] uppercase tracking-[0.28em] text-muted-foreground">
{voice.attribution}
<span className="mx-3 text-accent" aria-hidden="true">
/
</span>
{voice.detail}
</figcaption>
</figure>
))}
</div>
<div className="mt-10 flex items-center justify-between gap-6 border-t border-foreground/15 pt-6">
<div className="flex flex-wrap gap-2">
{voices.map((voice, i) => (
<button
key={voice.attribution}
type="button"
onClick={() => setIndex(i)}
aria-label={`Show neighbourhood voice ${i + 1} of ${voices.length}`}
aria-current={i === index}
className={
i === index
? 'h-2.5 w-2.5 rounded-full bg-accent transition-all duration-200 ease-out'
: 'h-2.5 w-2.5 rounded-full bg-foreground/25 transition-all duration-200 ease-out hover:bg-foreground/50'
}
/>
))}
</div>
<div className="flex items-center gap-3">
<span className="text-[11px] uppercase tracking-[0.28em] text-muted-foreground">
{String(index + 1).padStart(2, '0')} / {String(voices.length).padStart(2, '0')}
</span>
<button
type="button"
onClick={() => goTo(index - 1)}
aria-label="Previous neighbourhood voice"
className="flex h-11 w-11 items-center justify-center rounded-lg border border-foreground/20 bg-foreground/10 text-foreground backdrop-blur-md transition-all duration-200 ease-out hover:border-accent/60 hover:text-accent"
>
<ChevronLeft className="h-5 w-5" aria-hidden="true" />
</button>
<button
type="button"
onClick={() => goTo(index + 1)}
aria-label="Next neighbourhood voice"
className="flex h-11 w-11 items-center justify-center rounded-lg border border-foreground/20 bg-foreground/10 text-foreground backdrop-blur-md transition-all duration-200 ease-out hover:border-accent/60 hover:text-accent"
>
<ChevronRight className="h-5 w-5" aria-hidden="true" />
</button>
</div>
</div>
</div>
</div>
<div className="lg:col-span-4">
<div className="rounded-lg border border-foreground/20 bg-foreground/10 p-6 backdrop-blur-md">
<p className="text-[11px] uppercase tracking-[0.32em] text-accent">Counter rule</p>
<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
Say hello when you order. Half the street knows the box by name before the order is finished.
</p>
</div>
<div className="mt-6 rounded-lg border border-foreground/20 bg-foreground/10 p-6 backdrop-blur-md">
<p className="text-[11px] uppercase tracking-[0.32em] text-accent">Eaten here?</p>
<p className="mt-4 text-sm leading-relaxed text-muted-foreground">
Your words are welcome on our wall. Tell us at the counter what the chilli did to you.
</p>
</div>
</div>
</div>
<dl className="mt-16 grid border-t border-foreground/15 sm:grid-cols-3">
{streetNotes.map((note) => (
<div
key={note.label}
className="border-foreground/15 py-8 sm:border-l sm:pl-8 sm:first:border-l-0 sm:first:pl-0"
>
<dt className="text-[11px] uppercase tracking-[0.32em] text-accent">{note.label}</dt>
<dd className="mt-3 text-sm leading-relaxed text-muted-foreground">{note.note}</dd>
</div>
))}
</dl>
</div>
</section>
)
}