Restructure the repo into a pnpm + Turborepo monorepo: - apps/web: Nuxt 4 site, now server-rendered and reading all content from the API; pages cached with SWR and purged on content changes - apps/api: NestJS 12 + Prisma 7 + PostgreSQL. Auth with short-lived access tokens and rotating httpOnly refresh cookies, roles (ADMIN, EDITOR), catalog, editable site sections, media upload to S3-compatible storage (converted to WebP), contact messages, audit log - apps/admin: Vue 3 admin panel (Ant Design Vue, Pinia, Tailwind) for products, categories, site sections, messages, media and users - packages/shared: Zod schemas and types shared by API and admin Also: - seed script that loads the original 8 categories / 89 products and section content - Docker images for api (with a one-off migrate service), web, admin, and an nginx gateway for both domains - dev and production compose files, daily database backup service - API e2e tests Removes the committed static out/ build and the old root Dockerfile. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
82 lines
3.1 KiB
Vue
82 lines
3.1 KiB
Vue
<script setup lang="ts">
|
|
import DynamicIcon from "~/components/ui/DynamicIcon.vue";
|
|
import Lines from "~/components/ui/Lines.vue";
|
|
import Reveal from "~/components/ui/Reveal.vue";
|
|
import SectionHead from "~/components/ui/SectionHead.vue";
|
|
|
|
const about = computed(() => useSiteStore().sections.about);
|
|
</script>
|
|
|
|
<template>
|
|
<section
|
|
id="about"
|
|
aria-labelledby="about-title"
|
|
class="relative overflow-hidden border-t border-line py-24 md:py-32"
|
|
>
|
|
<div class="absolute inset-0 bg-grid-fine opacity-50" />
|
|
|
|
<div class="relative mx-auto max-w-[1480px] px-5 md:px-8">
|
|
<div class="grid items-center gap-14 lg:grid-cols-2">
|
|
<!-- ---- image ---- -->
|
|
<Reveal class="relative order-2 lg:order-1" :delay="0.1">
|
|
<div class="relative overflow-hidden rounded-2xl border border-line">
|
|
<img
|
|
:src="about.image.url"
|
|
:alt="about.image.alt"
|
|
width="800"
|
|
height="600"
|
|
loading="lazy"
|
|
decoding="async"
|
|
class="aspect-[4/3] w-full object-cover"
|
|
/>
|
|
<div class="absolute inset-0 bg-gradient-to-t from-bg/80 via-transparent to-transparent" />
|
|
<div
|
|
class="absolute bottom-5 start-5 flex items-center gap-4 rounded-xl border border-line bg-bg/80 px-5 py-4 backdrop-blur-md"
|
|
>
|
|
<span class="font-mono text-3xl font-bold text-accent">{{ about.badge.value }}</span>
|
|
<span class="text-xs leading-5 text-muted">
|
|
<Lines :text="about.badge.label" />
|
|
</span>
|
|
</div>
|
|
</div>
|
|
<div class="hazard absolute -top-3 -end-3 h-6 w-28 rounded-full opacity-90" />
|
|
</Reveal>
|
|
|
|
<!-- ---- text ---- -->
|
|
<div class="order-1 lg:order-2">
|
|
<SectionHead heading-id="about-title" v-bind="about.head" />
|
|
<Reveal :delay="0.2">
|
|
<p class="mt-6 leading-9 text-muted">
|
|
{{ about.body }}
|
|
</p>
|
|
</Reveal>
|
|
|
|
<div class="mt-10 space-y-1">
|
|
<Reveal v-for="(t, i) in about.timeline" :key="t.year" :delay="0.25 + i * 0.07">
|
|
<div
|
|
class="group border-s-2 border-accent/30 ps-6 transition-colors duration-300 hover:border-accent"
|
|
>
|
|
<span class="font-mono text-sm font-bold text-accent">{{ t.year }}</span>
|
|
<p class="mt-1 pb-5 text-sm leading-7 text-ink/75">{{ t.text }}</p>
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
|
|
<Reveal :delay="0.5">
|
|
<div class="mt-4 flex flex-wrap gap-3">
|
|
<span
|
|
v-for="f in about.features"
|
|
:key="f.label"
|
|
class="inline-flex items-center gap-2 rounded-full border border-line bg-panel/60 px-4 py-2 text-xs text-ink/80 transition-colors hover:border-accent/50 hover:text-ink"
|
|
>
|
|
<DynamicIcon :name="f.icon" class="size-4 text-accent" />
|
|
{{ f.label }}
|
|
</span>
|
|
</div>
|
|
</Reveal>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</section>
|
|
</template>
|