feat: build juozas auto site

This commit is contained in:
9a0ffedc5b31823b
2026-05-02 22:32:02 +00:00
parent c44b6fa229
commit 5c47bdecb6
48 changed files with 9005 additions and 1 deletions

View File

@@ -0,0 +1,128 @@
---
import { getImage } from 'astro:assets';
import { getCollection, type CollectionEntry } from 'astro:content';
import CarCard from '../../components/CarCard.astro';
import CarGallery from '../../components/CarGallery.astro';
import ContactButtons from '../../components/ContactButtons.astro';
import Footer from '../../components/Footer.astro';
import Header from '../../components/Header.astro';
import JsonLd from '../../components/JsonLd.astro';
import SpecStrip from '../../components/SpecStrip.astro';
import { lt } from '../../i18n/lt';
import { EMAIL, PHONE_DISPLAY } from '../../lib/contact';
import { excerpt, formatEngineSize, formatMileage, formatPower, formatPrice } from '../../lib/format';
import { vehicleJsonLd } from '../../lib/jsonLd';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { site } from '../../site';
export async function getStaticPaths() {
const cars = await getCollection('cars');
return cars.map((car) => ({ params: { slug: car.slug }, props: { car } }));
}
const { car } = Astro.props as { car: CollectionEntry<'cars'> };
const allCars = await getCollection('cars');
const similarCars = allCars.filter((item) => item.id !== car.id && !item.data.sold).slice(0, 3);
const { Content } = await car.render();
const carName = `${car.data.make} ${car.data.model}`;
const title = `${car.data.year} ${car.data.make} ${car.data.model} — ${formatPrice(car.data.price)} — Juozas Auto`;
const ogTitle = `${car.data.year} ${car.data.make} ${car.data.model} — ${formatPrice(car.data.price)}`;
const description = excerpt(car.body);
const canonicalPath = `/automobiliai/${car.slug}`;
const canonicalUrl = new URL(canonicalPath, site.url).toString();
const ogImage = await getImage({ src: car.data.photos[0], width: 1200, height: 630, format: 'jpg' });
const ogImageUrl = new URL(ogImage.src, site.url).toString();
const specs = [
[lt.labels.year, String(car.data.year)],
[lt.labels.mileage, formatMileage(car.data.mileage)],
[lt.labels.fuel, car.data.fuel],
[lt.labels.gearbox, car.data.transmission],
[lt.labels.bodyType, car.data.bodyType],
[lt.labels.color, car.data.color],
car.data.drivetrain ? [lt.labels.drivetrain, car.data.drivetrain] : null,
car.data.power ? [lt.labels.power, formatPower(car.data.power)] : null,
car.data.engineSize ? [lt.labels.engineSize, formatEngineSize(car.data.engineSize)] : null,
car.data.firstRegistration ? [lt.labels.firstRegistration, car.data.firstRegistration] : null,
car.data.vin ? [lt.labels.vin, car.data.vin] : null,
].filter(Boolean) as [string, string][];
const jsonLd = vehicleJsonLd({ car: car.data, canonicalUrl, imageUrl: ogImageUrl });
---
<BaseLayout title={title} ogTitle={ogTitle} description={description} canonicalPath={canonicalPath} image={ogImageUrl} type="product">
<Header />
<JsonLd data={jsonLd} />
<main class="mx-auto max-w-7xl px-5 pb-8 sm:px-8 lg:px-10">
<div class="grid min-w-0 gap-10 lg:grid-cols-[minmax(0,1.08fr)_minmax(380px,0.72fr)] lg:items-start">
<div class="min-w-0 lg:sticky lg:top-6">
<CarGallery photos={car.data.photos} title={carName} />
</div>
<aside class="w-full max-w-full min-w-0 overflow-hidden lg:sticky lg:top-6">
<p class="text-sm text-muted">{car.data.year} · {car.data.city}</p>
<h1 class="mt-2 text-2xl font-semibold tracking-[-0.06em] text-ink sm:text-3xl">{carName}</h1>
<p class="mt-5 text-3xl font-semibold tracking-[-0.07em] text-burgundy-700 tabular">{formatPrice(car.data.price)}</p>
<div class="mt-6 lg:hidden">
<ContactButtons make={car.data.make} model={car.data.model} compact />
</div>
<div class="mt-7">
<SpecStrip year={car.data.year} mileage={car.data.mileage} fuel={car.data.fuel} transmission={car.data.transmission} />
</div>
<div class="mt-7 hidden lg:block">
<ContactButtons make={car.data.make} model={car.data.model} />
<p class="mt-4 text-sm text-muted">Tiesioginis kontaktas: {PHONE_DISPLAY}, {EMAIL}</p>
</div>
</aside>
</div>
<div class="mt-12 grid gap-12 lg:grid-cols-[minmax(0,1fr)_380px]">
<article class="prose-lite measure">
<h2 class="mb-5 text-2xl font-semibold tracking-[-0.05em] text-ink">{lt.sections.description}</h2>
<Content />
</article>
<section aria-labelledby="specs-heading">
<h2 id="specs-heading" class="text-2xl font-semibold tracking-[-0.05em]">{lt.sections.specs}</h2>
<dl class="mt-5 divide-y divide-line rounded-[1.75rem] border border-line bg-paper">
{specs.map(([label, value]) => (
<div class="grid grid-cols-[1fr_1.25fr] gap-4 px-5 py-4 text-sm">
<dt class="text-muted">{label}</dt>
<dd class="font-semibold text-ink tabular">{value}</dd>
</div>
))}
</dl>
</section>
</div>
<section class="mt-16 rounded-[2rem] bg-wash p-6 sm:p-8" aria-labelledby="location-heading">
<p class="text-xs font-semibold uppercase tracking-[0.18em] text-muted">{lt.sections.location}</p>
<h2 id="location-heading" class="mt-2 text-2xl font-semibold tracking-[-0.05em]">{car.data.city}</h2>
<p class="mt-3 max-w-2xl text-muted">Apžiūra derinama telefonu. Tikslus adresas pateikiamas susitarus dėl laiko.</p>
</section>
{similarCars.length > 0 && (
<section class="mt-20" aria-labelledby="similar-heading">
<h2 id="similar-heading" class="text-2xl font-semibold tracking-[-0.05em]">{lt.sections.similar}</h2>
<div class="mt-7 grid gap-x-7 gap-y-12 sm:grid-cols-2 lg:grid-cols-3">
{similarCars.map((item) => <CarCard car={item} />)}
</div>
</section>
)}
</main>
<div class="fixed inset-x-0 bottom-0 z-40 translate-y-full border-t border-line bg-paper/95 px-4 pb-[max(1rem,env(safe-area-inset-bottom))] pt-3 shadow-soft lg:hidden" data-sticky-contact>
<ContactButtons make={car.data.make} model={car.data.model} compact />
</div>
<Footer />
</BaseLayout>
<script is:inline>
const sticky = document.querySelector('[data-sticky-contact]');
if (sticky) {
const toggle = () => sticky.classList.toggle('translate-y-full', window.scrollY < 200);
sticky.classList.add('transition-transform', 'duration-300', 'ease-out-quart');
toggle();
window.addEventListener('scroll', toggle, { passive: true });
}
</script>