Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
157 changes: 96 additions & 61 deletions src/components/faqs/faqs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ const faqData = [

const FAQs: React.FC = () => {
const [activeIndex, setActiveIndex] = useState<number | null>(null);
const { colorMode, isDark } = useSafeColorMode();
const { isDark } = useSafeColorMode();

const toggleAccordion = (index: number) => {
setActiveIndex(activeIndex === index ? null : index);
Expand All @@ -65,12 +65,12 @@ const FAQs: React.FC = () => {
padding: "2rem 0",
}}
>
<div className="mx-auto px-2 sm:px-4 lg:px-6">
<div className="mx-auto px-2 sm:px-4 lg:max-w-6xl lg:px-10 xl:max-w-7xl xl:px-14">
<div className="flex flex-col items-center justify-center gap-x-8 gap-y-12 lg:flex-row lg:justify-between xl:gap-28">
<div className="w-full">
<div className="mb-8 lg:mb-16">
<div className="mb-8 text-center lg:mb-16">
<h6
className="mb-2 text-center text-lg font-medium lg:text-left"
className="mb-2 text-center text-lg font-medium"
style={{
color: isDark ? "#a78bfa" : "#8b5cf6",
fontWeight: 600,
Expand All @@ -79,7 +79,7 @@ const FAQs: React.FC = () => {
FAQs
</h6>
<h2
className={`text-center text-4xl font-bold lg:text-left ${
className={`text-center text-4xl font-bold ${
isDark ? "text-gray-100" : "text-gray-900"
} leading-snug`}
>
Expand All @@ -88,78 +88,113 @@ const FAQs: React.FC = () => {
<p
className={`${
isDark ? "text-gray-400" : "text-gray-600"
} text-center lg:text-left`}
} mx-auto text-center`}
>
Find answers to the most common questions about recode hive.
</p>
</div>

{/* Accordion Masonry Columns to prevent sibling expansion */}
<div className="columns-1 md:columns-2 md:gap-x-6">
{faqData.map((faq, index) => (
<motion.div
key={index}
className="accordion mb-4 h-fit break-inside-avoid border-gray-200 pb-4 dark:border-gray-700"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<button
className={`accordion-toggle group flex w-full cursor-pointer items-center justify-between rounded-lg p-4 text-lg font-medium transition-all duration-300 focus:outline-none ${
isDark
? "text-gray-200 hover:text-indigo-400"
: "text-gray-700 hover:text-indigo-600"
}`}
{faqData.map((faq, index) => {
const isExpanded = activeIndex === index;
const panelId = `faq-panel-${index}`;
const triggerId = `faq-trigger-${index}`;

return (
<motion.div
key={index}
className="accordion mb-4 h-fit break-inside-avoid overflow-hidden rounded-xl border pb-0 shadow-sm transition-all duration-300 dark:border-gray-700"
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The outer accordion container now has overflow-hidden, but the header button uses an outer focus-visible:ring-* + ring-offset-*. This can cause the focus indicator to be clipped at the card edges for keyboard users. Consider moving overflow-hidden to a lower wrapper (only around the collapsing content) or switching the button to an inset focus style (e.g., inset ring / no ring-offset) so the focus indicator remains fully visible.

Suggested change
className="accordion mb-4 h-fit break-inside-avoid overflow-hidden rounded-xl border pb-0 shadow-sm transition-all duration-300 dark:border-gray-700"
className="accordion mb-4 h-fit break-inside-avoid rounded-xl border pb-0 shadow-sm transition-all duration-300 dark:border-gray-700"

Copilot uses AI. Check for mistakes.
style={{
background: isDark
? "rgba(30, 27, 75, 0.6)"
: "rgba(237, 233, 254, 0.6)",
border: isDark
? "1px solid rgba(139, 92, 246, 0.2)"
: "1px solid rgba(139, 92, 246, 0.3)",
backdropFilter: "blur(10px)",
}}
onClick={() => toggleAccordion(index)}
>
{faq.question}
<motion.span
className="transform transition-transform duration-300"
animate={{ rotate: activeIndex === index ? 180 : 0 }}
>
<FiChevronDown size={22} />
</motion.span>
</button>
<motion.div
className="accordion-content overflow-hidden"
initial={{ height: 0, opacity: 0 }}
animate={{
height: activeIndex === index ? "auto" : 0,
opacity: activeIndex === index ? 1 : 0,
? "rgba(30, 27, 75, 0.55)"
: "rgba(237, 233, 254, 0.7)",
borderColor: isDark
? "rgba(139, 92, 246, 0.25)"
: "rgba(139, 92, 246, 0.28)",
backdropFilter: "blur(12px)",
}}
transition={{ duration: 0.3, ease: "easeInOut" }}
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
<div
className={`mt-2 text-base transition-colors duration-200 ${
isDark ? "text-gray-300" : "text-gray-900"
<button
id={triggerId}
aria-expanded={isExpanded}
aria-controls={panelId}
className={`accordion-toggle group flex w-full cursor-pointer items-center justify-between p-4 text-left text-lg font-medium transition-all duration-300 focus:outline-none focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-indigo-500 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:focus-visible:ring-indigo-400 dark:focus-visible:ring-offset-gray-900 ${
isDark
? "text-gray-200 hover:text-indigo-300"
: "text-gray-700 hover:text-indigo-700"
}`}
style={{
color: isDark ? "#d1d5db" : "#111827",
background: isExpanded
? isDark
? "linear-gradient(135deg, rgba(99,102,241,0.24), rgba(139,92,246,0.16))"
: "linear-gradient(135deg, rgba(224,231,255,0.95), rgba(237,233,254,0.92))"
: isDark
? "linear-gradient(135deg, rgba(67,56,202,0.18), rgba(76,29,149,0.1))"
: "linear-gradient(135deg, rgba(238,242,255,0.85), rgba(243,232,255,0.78))",
boxShadow: isExpanded
? isDark
? "inset 0 1px 0 rgba(255,255,255,0.12), 0 8px 24px -16px rgba(99,102,241,0.7)"
: "inset 0 1px 0 rgba(255,255,255,0.95), 0 10px 26px -18px rgba(99,102,241,0.55)"
: isDark
? "inset 0 1px 0 rgba(255,255,255,0.08)"
: "inset 0 1px 0 rgba(255,255,255,0.8)",
borderBottom: isExpanded
? isDark
? "1px solid rgba(139, 92, 246, 0.35)"
: "1px solid rgba(139, 92, 246, 0.3)"
: "1px solid transparent",
}}
dangerouslySetInnerHTML={{
__html: faq.answer
.replace(
/<strong>/g,
`<strong style="color: ${isDark ? "#f3f4f6" : "#000000"}; font-weight: 600;">`,
)
.replace(
/<a /g,
`<a style="color: ${isDark ? "#818cf8" : "#4f46e5"};" `,
),
onClick={() => toggleAccordion(index)}
>
{faq.question}
<motion.span
className="transform transition-transform duration-300"
animate={{ rotate: isExpanded ? 180 : 0 }}
>
<FiChevronDown size={22} />
</motion.span>
</button>
<motion.div
id={panelId}
aria-labelledby={triggerId}
className="accordion-content overflow-hidden"
initial={{ height: 0, opacity: 0 }}
animate={{
height: isExpanded ? "auto" : 0,
opacity: isExpanded ? 1 : 0,
}}
/>
transition={{ duration: 0.3, ease: "easeInOut" }}
>
<div
className={`border-t px-4 pb-4 pt-3 text-base transition-colors duration-200 ${
isDark ? "text-gray-300" : "text-gray-900"
}`}
style={{
borderColor: isDark
? "rgba(139, 92, 246, 0.22)"
: "rgba(139, 92, 246, 0.24)",
color: isDark ? "#d1d5db" : "#111827",
}}
dangerouslySetInnerHTML={{
__html: faq.answer
.replace(
/<strong>/g,
`<strong style="color: ${isDark ? "#f3f4f6" : "#000000"}; font-weight: 600;">`,
)
.replace(
/<a /g,
`<a style="color: ${isDark ? "#818cf8" : "#4f46e5"};" `,
),
}}
/>
Comment on lines +171 to +193
Copy link

Copilot AI Mar 1, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When the accordion is collapsed, the answer panel is still present in the DOM (height/opacity animated) and can contain focusable elements (e.g., the links in faq.answer). Those links can remain reachable via keyboard tab even when the panel is visually collapsed. To avoid hidden-focus traps, consider disabling focus when collapsed (e.g., hidden/conditional rendering with AnimatePresence, or applying inert/aria-hidden plus a focus management approach).

Suggested change
>
<div
className={`border-t px-4 pb-4 pt-3 text-base transition-colors duration-200 ${
isDark ? "text-gray-300" : "text-gray-900"
}`}
style={{
borderColor: isDark
? "rgba(139, 92, 246, 0.22)"
: "rgba(139, 92, 246, 0.24)",
color: isDark ? "#d1d5db" : "#111827",
}}
dangerouslySetInnerHTML={{
__html: faq.answer
.replace(
/<strong>/g,
`<strong style="color: ${isDark ? "#f3f4f6" : "#000000"}; font-weight: 600;">`,
)
.replace(
/<a /g,
`<a style="color: ${isDark ? "#818cf8" : "#4f46e5"};" `,
),
}}
/>
aria-hidden={!isExpanded}
>
{isExpanded && (
<div
className={`border-t px-4 pb-4 pt-3 text-base transition-colors duration-200 ${
isDark ? "text-gray-300" : "text-gray-900"
}`}
style={{
borderColor: isDark
? "rgba(139, 92, 246, 0.22)"
: "rgba(139, 92, 246, 0.24)",
color: isDark ? "#d1d5db" : "#111827",
}}
dangerouslySetInnerHTML={{
__html: faq.answer
.replace(
/<strong>/g,
`<strong style="color: ${isDark ? "#f3f4f6" : "#000000"}; font-weight: 600;">`,
)
.replace(
/<a /g,
`<a style="color: ${isDark ? "#818cf8" : "#4f46e5"};" `,
),
}}
/>
)}

Copilot uses AI. Check for mistakes.
</motion.div>
</motion.div>
</motion.div>
))}
);
})}
</div>
</div>
</div>
Expand Down
7 changes: 4 additions & 3 deletions src/components/testimonials/TestimonialCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const TestimonialCard: React.FC<TestimonialCardProps> = ({
avatar,
link,
}) => {
const { colorMode, isDark } = useSafeColorMode();
const { isDark } = useSafeColorMode();

const formatLinkDisplay = (url: string) => {
try {
Expand Down Expand Up @@ -57,8 +57,9 @@ const TestimonialCard: React.FC<TestimonialCardProps> = ({
{/* Header */}
<div className="mb-6 flex items-center gap-4">
<div className="relative">
<Avatar className="h-16 w-16 border-2 border-gradient-to-r from-purple-500 to-pink-500">
<AvatarImage src={avatar} className="object-contain" />
<div className="pointer-events-none absolute -inset-1 rounded-full bg-gradient-to-br from-purple-500/35 via-fuchsia-500/25 to-blue-500/35 blur-md" />
<Avatar className="relative z-10 h-20 w-20 overflow-hidden border border-white/70 bg-white/70 shadow-[0_10px_30px_-12px_rgba(147,51,234,0.7)] ring-2 ring-purple-300/50">
<AvatarImage src={avatar} alt={`${name}'s avatar`} className="h-full w-full scale-[3] object-cover object-center" />
<AvatarFallback className="bg-gradient-to-br from-purple-500 to-pink-500 text-white font-semibold">
{name.charAt(0)}
</AvatarFallback>
Expand Down
Loading