feat: FAQ section redesign — compact cards, expand/collapse, bigger text
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -72,6 +72,12 @@ src/
|
|||||||
- 2 addresses in Minsk, Yandex Maps embed with markers
|
- 2 addresses in Minsk, Yandex Maps embed with markers
|
||||||
- Contact: phone, Instagram
|
- Contact: phone, Instagram
|
||||||
|
|
||||||
|
## AST Index
|
||||||
|
- **Always use the AST index** at `memory/ast-index.md` when searching for components, props, hooks, types, or styles
|
||||||
|
- Contains: component tree, all exports, props, hooks, client/server status, CSS classes, keyframes
|
||||||
|
- Covers all 31 TS/TSX files + 4 CSS files
|
||||||
|
- Update the index when adding/removing/renaming files or exports
|
||||||
|
|
||||||
## Git
|
## Git
|
||||||
- Remote: Gitea at `git.dolgolyov-family.by`
|
- Remote: Gitea at `git.dolgolyov-family.by`
|
||||||
- User: diana.dolgolyova
|
- User: diana.dolgolyova
|
||||||
|
|||||||
@@ -1,19 +1,25 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Plus, Minus } from "lucide-react";
|
import { ChevronDown } from "lucide-react";
|
||||||
import { siteContent } from "@/data/content";
|
import { siteContent } from "@/data/content";
|
||||||
import { SectionHeading } from "@/components/ui/SectionHeading";
|
import { SectionHeading } from "@/components/ui/SectionHeading";
|
||||||
import { Reveal } from "@/components/ui/Reveal";
|
import { Reveal } from "@/components/ui/Reveal";
|
||||||
|
|
||||||
|
const VISIBLE_COUNT = 4;
|
||||||
|
|
||||||
export function FAQ() {
|
export function FAQ() {
|
||||||
const { faq } = siteContent;
|
const { faq } = siteContent;
|
||||||
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
const [openIndex, setOpenIndex] = useState<number | null>(null);
|
||||||
|
const [expanded, setExpanded] = useState(false);
|
||||||
|
|
||||||
function toggle(index: number) {
|
function toggle(index: number) {
|
||||||
setOpenIndex(openIndex === index ? null : index);
|
setOpenIndex(openIndex === index ? null : index);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const visibleItems = expanded ? faq.items : faq.items.slice(0, VISIBLE_COUNT);
|
||||||
|
const hasMore = faq.items.length > VISIBLE_COUNT;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section id="faq" className="section-glow relative section-padding bg-neutral-100 dark:bg-[#080808]">
|
<section id="faq" className="section-glow relative section-padding bg-neutral-100 dark:bg-[#080808]">
|
||||||
<div className="section-divider absolute top-0 left-0 right-0" />
|
<div className="section-divider absolute top-0 left-0 right-0" />
|
||||||
@@ -22,44 +28,52 @@ export function FAQ() {
|
|||||||
<SectionHeading centered>{faq.title}</SectionHeading>
|
<SectionHeading centered>{faq.title}</SectionHeading>
|
||||||
</Reveal>
|
</Reveal>
|
||||||
|
|
||||||
<div className="mx-auto mt-14 grid max-w-5xl gap-x-10 lg:grid-cols-2">
|
<div className="mx-auto mt-12 max-w-3xl space-y-2.5">
|
||||||
{[0, 1].map((col) => {
|
{visibleItems.map((item, idx) => {
|
||||||
const half = Math.ceil(faq.items.length / 2);
|
const isOpen = openIndex === idx;
|
||||||
const items = col === 0 ? faq.items.slice(0, half) : faq.items.slice(half);
|
|
||||||
const offset = col === 0 ? 0 : half;
|
|
||||||
return (
|
|
||||||
<div key={col}>
|
|
||||||
{items.map((item, i) => {
|
|
||||||
const idx = offset + i;
|
|
||||||
return (
|
return (
|
||||||
<Reveal key={idx}>
|
<Reveal key={idx}>
|
||||||
<div
|
<div
|
||||||
className={`border-b border-neutral-200 dark:border-white/[0.06] ${
|
className={`rounded-xl border transition-all duration-300 ${
|
||||||
i === 0 ? "border-t" : ""
|
isOpen
|
||||||
|
? "border-[#c9a96e]/30 bg-gradient-to-br from-[#c9a96e]/[0.06] via-transparent to-[#c9a96e]/[0.03] shadow-md shadow-[#c9a96e]/5"
|
||||||
|
: "border-neutral-200 bg-white hover:border-neutral-300 dark:border-white/[0.06] dark:bg-[#0a0a0a] dark:hover:border-white/[0.12]"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<button
|
<button
|
||||||
onClick={() => toggle(idx)}
|
onClick={() => toggle(idx)}
|
||||||
className="flex w-full items-center justify-between gap-4 py-5 text-left transition-colors"
|
className="flex w-full items-center gap-3 px-5 py-4 text-left cursor-pointer"
|
||||||
>
|
>
|
||||||
<span className="text-sm font-medium text-neutral-900 dark:text-white sm:text-base">
|
{/* Number badge */}
|
||||||
|
<span
|
||||||
|
className={`flex h-6 w-6 shrink-0 items-center justify-center rounded-full text-[10px] font-bold transition-colors duration-300 ${
|
||||||
|
isOpen
|
||||||
|
? "bg-[#c9a96e] text-black"
|
||||||
|
: "bg-[#c9a96e]/10 text-[#a08050] dark:text-[#d4b87a]"
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{idx + 1}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span className="flex-1 text-sm sm:text-base font-medium text-neutral-900 dark:text-white leading-snug">
|
||||||
{item.question}
|
{item.question}
|
||||||
</span>
|
</span>
|
||||||
<span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full bg-neutral-100 transition-all duration-300 dark:bg-white/[0.06]">
|
|
||||||
{openIndex === idx ? (
|
<ChevronDown
|
||||||
<Minus size={14} className="text-[#c9a96e]" />
|
size={16}
|
||||||
) : (
|
className={`shrink-0 transition-all duration-300 ${
|
||||||
<Plus size={14} className="text-neutral-400 dark:text-neutral-500" />
|
isOpen ? "text-[#c9a96e] rotate-180" : "text-neutral-400 dark:text-neutral-500"
|
||||||
)}
|
}`}
|
||||||
</span>
|
/>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={`grid transition-all duration-300 ease-out ${
|
className={`grid transition-all duration-300 ease-out ${
|
||||||
openIndex === idx ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"
|
isOpen ? "grid-rows-[1fr] opacity-100" : "grid-rows-[0fr] opacity-0"
|
||||||
}`}
|
}`}
|
||||||
>
|
>
|
||||||
<div className="overflow-hidden">
|
<div className="overflow-hidden">
|
||||||
<div className="pb-5 pr-10 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400 whitespace-pre-line">
|
<div className="px-5 pb-4 pl-14 text-sm leading-relaxed text-neutral-600 dark:text-neutral-400 whitespace-pre-line">
|
||||||
{item.answer}
|
{item.answer}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -68,9 +82,27 @@ export function FAQ() {
|
|||||||
</Reveal>
|
</Reveal>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
|
||||||
|
{/* Show more / less */}
|
||||||
|
{hasMore && (
|
||||||
|
<Reveal>
|
||||||
|
<div className="pt-2 text-center">
|
||||||
|
<button
|
||||||
|
onClick={() => {
|
||||||
|
setExpanded(!expanded);
|
||||||
|
if (expanded) setOpenIndex(null);
|
||||||
|
}}
|
||||||
|
className="inline-flex items-center gap-1.5 rounded-full border border-neutral-200 bg-white px-5 py-2 text-sm font-medium text-neutral-600 transition-all hover:border-[#c9a96e]/40 hover:text-[#c9a96e] dark:border-white/[0.08] dark:bg-white/[0.03] dark:text-neutral-400 dark:hover:border-[#c9a96e]/30 dark:hover:text-[#c9a96e] cursor-pointer"
|
||||||
|
>
|
||||||
|
{expanded ? "Скрыть" : `Ещё ${faq.items.length - VISIBLE_COUNT} вопросов`}
|
||||||
|
<ChevronDown
|
||||||
|
size={14}
|
||||||
|
className={`transition-transform duration-300 ${expanded ? "rotate-180" : ""}`}
|
||||||
|
/>
|
||||||
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
</Reveal>
|
||||||
})}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
Reference in New Issue
Block a user