feat: Add packages/site — landing vitrine (Vue 3 + Tailwind 4)
All checks were successful
Deploy site (production) / build-and-deploy (push) Successful in 48s
All checks were successful
Deploy site (production) / build-and-deploy (push) Successful in 48s
- Scaffold packages/site with 11 Vue components: hero (animated terminal), features, showcase, work groups, how-it-works, security, FAQ accordion, footer - i18n EN/FR via vue-i18n with 82+ keys, autodetect navigator.language, toggle without reload - Animated timeline (terminal, dialog, toast) ported from design, scroll-reveal, copy buttons (2 instances) - Self-hosted JetBrains Mono fonts (@fontsource), optimized assets (mark 28KB, og-cover 1200×630) - SEO: canonical, og:image, theme-color, robots.txt, sitemap.xml for git-arboretum.com - Monorepo integration (decoupled from npm release): build:site / dev:site / preview:site scripts - CI: add build:site step to ci.yml for early detection; create .gitea/workflows/prod.yml for FTPS deploy (lftp mirror) to git-arboretum.com - Validated builds ✓ and UI rendering (EN/FR headless test) ✓
This commit is contained in:
52
packages/site/src/components/FaqSection.vue
Normal file
52
packages/site/src/components/FaqSection.vue
Normal file
@@ -0,0 +1,52 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { FAQS } from '../i18n/content';
|
||||
import type { AppLocale } from '../i18n';
|
||||
|
||||
const { t, locale } = useI18n();
|
||||
const open = ref(0);
|
||||
const items = computed(() => FAQS[locale.value as AppLocale]);
|
||||
|
||||
function toggle(i: number): void {
|
||||
open.value = open.value === i ? -1 : i;
|
||||
}
|
||||
|
||||
function panelStyle(i: number) {
|
||||
return {
|
||||
maxHeight: open.value === i ? '300px' : '0px',
|
||||
opacity: open.value === i ? 1 : 0,
|
||||
overflow: 'hidden',
|
||||
transition: 'max-height .4s ease, opacity .35s ease',
|
||||
} as const;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<section id="faq" class="mx-auto max-w-[780px] scroll-mt-[84px] px-6 pb-[100px]">
|
||||
<div v-reveal class="mb-[38px] text-center">
|
||||
<div class="mb-3.5 font-mono text-xs uppercase tracking-[0.14em] text-emerald-400">{{ t('navFaq') }}</div>
|
||||
<h2 class="m-0 text-[clamp(28px,3.4vw,40px)] font-semibold tracking-[-0.025em] text-zinc-50">{{ t('faqTitle') }}</h2>
|
||||
</div>
|
||||
|
||||
<div v-reveal>
|
||||
<div v-for="(f, i) in items" :key="i" class="border-t" :class="open === i ? 'border-emerald-400/40' : 'border-zinc-800'">
|
||||
<button
|
||||
type="button"
|
||||
:aria-expanded="open === i"
|
||||
:aria-controls="`faq-panel-${i}`"
|
||||
class="flex w-full cursor-pointer items-center justify-between gap-4 border-none bg-transparent px-1 py-5 text-left text-[clamp(16px,2vw,18px)] font-medium text-zinc-50 focus-visible:rounded-md focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-emerald-500/70"
|
||||
@click="toggle(i)"
|
||||
>
|
||||
<span>{{ f.q }}</span>
|
||||
<span class="flex flex-none text-emerald-400 transition-transform duration-300" :class="open === i ? 'rotate-180' : ''">
|
||||
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="#34d399" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6" /></svg>
|
||||
</span>
|
||||
</button>
|
||||
<div :id="`faq-panel-${i}`" :style="panelStyle(i)" :inert="open !== i" :aria-hidden="open !== i">
|
||||
<p class="m-0 max-w-[640px] px-1 pb-5 text-[15.5px] leading-[1.6] text-zinc-400">{{ f.a }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user