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
13 changes: 13 additions & 0 deletions .hintrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"extends": [
"development"
],
"hints": {
"axe/aria": [
"default",
{
"aria-valid-attr-value": "off"
}
Comment on lines +7 to +10
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

Disabling the 'aria-valid-attr-value' rule in the hint configuration removes an important accessibility check that validates ARIA attribute values. This rule helps catch invalid ARIA attribute values that could break screen reader functionality. Unless there's a specific false positive you're addressing, consider removing this override to maintain accessibility validation. If there is a specific issue, document it and use more targeted exclusions.

Suggested change
"default",
{
"aria-valid-attr-value": "off"
}
"default"

Copilot uses AI. Check for mistakes.
]
}
}
5 changes: 4 additions & 1 deletion App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,9 @@ export default function App() {
<ErrorBoundary>
<Router>
<ScrollToTop />
<a href="#main-content" className="sr-only focus:not-sr-only absolute top-4 left-4 z-[100] bg-white text-black px-4 py-2 rounded-md font-bold shadow-lg">
Langsung ke konten utama
</a>
<div className="min-h-screen bg-[#030303] text-white selection:bg-rose-500/30 font-sans overflow-x-hidden flex flex-col relative">
<div className="fixed inset-0 z-0 pointer-events-none overflow-hidden">
<div className="absolute inset-0 opacity-[0.02]" style={{ backgroundImage: `url("https://grainy-gradients.vercel.app/noise.svg")`, backgroundSize: '100px 100px' }}></div>
Expand All @@ -87,7 +90,7 @@ export default function App() {

<div className="relative z-10 flex flex-col min-h-screen">
<Navbar />
<main className="flex-grow container mx-auto px-4 md:px-6 lg:px-8 max-w-7xl">
<main id="main-content" className="flex-grow container mx-auto px-4 md:px-6 lg:px-8 max-w-7xl">
<AnimatedRoutes />
</main>
<Footer />
Expand Down
52 changes: 50 additions & 2 deletions components/ContributorModal.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import React, { useRef } from 'react';
import React, { useRef, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { Download, ExternalLink, X, Sparkles, Wand2, Heart, Palette } from 'lucide-react';
import html2canvas from 'html2canvas';
Expand Down Expand Up @@ -36,6 +36,50 @@ export const ContributorModal: React.FC<ContributorDetailProps> = ({
isOwner,
}) => {
const modalRef = useRef<HTMLDivElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);

// Focus Trap
useEffect(() => {
if (!isOpen) return;

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
return;
}

if (e.key === 'Tab') {
const focusableElements = modalRef.current?.parentElement?.querySelectorAll(
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The focus trap query selector is using modalRef.current?.parentElement?.querySelectorAll() which searches beyond the modal content. Looking at the DOM structure, modalRef is assigned to the inner scrollable content div, and parentElement would be the motion.div containing the entire modal window. However, this inconsistency with CreationStudio (which uses modalRef.current directly) could lead to including elements outside the intended modal scope. For proper focus containment, the query should be scoped to the actual modal container, not its parent element.

Suggested change
const focusableElements = modalRef.current?.parentElement?.querySelectorAll(
const focusableElements = modalRef.current?.querySelectorAll(

Copilot uses AI. Check for mistakes.
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
Comment on lines +52 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

critical

Implementasi focus trap saat ini memiliki bug kritis. Query modalRef.current?.parentElement?.querySelectorAll(...) menargetkan elemen yang salah dan tidak akan menemukan elemen yang dapat difokuskan seperti tombol tutup atau tautan eksternal, karena elemen-elemen tersebut tidak berada di dalam parentElement dari modalRef.current. Akibatnya, focus trap tidak berfungsi seperti yang diharapkan.

Untuk memperbaikinya, Anda perlu menargetkan container modal yang benar, yaitu dua level di atas modalRef.current (parentElement.parentElement).

Suggested change
const focusableElements = modalRef.current?.parentElement?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);
const focusableElements = modalRef.current?.parentElement?.parentElement?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);

Comment on lines +52 to +54
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🟑 Focus trap in ContributorModal excludes close button due to incorrect DOM traversal

The focus trap implementation in ContributorModal queries focusable elements using modalRef.current?.parentElement?.querySelectorAll(...) (line 52). However, modalRef is attached to an inner content div (line 145), making parentElement the scrollable container div (line 144). The close button (lines 134-141) is a sibling of this scrollable container, not a child of it.

DOM structure:

<motion.div> (modal window - line 129)
  <button ref={closeButtonRef}> (close button - line 134) ← NOT included in query
  <div> (scrollable container - line 144) ← This is modalRef.current.parentElement
    <div ref={modalRef}> (content - line 145)

When Tab is pressed, the focus trap calculates firstElement and lastElement from elements inside the scrollable container only. The close button is excluded from this list. This means:

  1. If the user tabs to the close button and then tabs again, the focus trap logic won't work correctly because document.activeElement (the close button) won't match lastElement (which is inside the scrollable container)
  2. The focus trap doesn't properly contain focus within all interactive elements of the modal

This undermines the accessibility goal of the focus trap implementation.

Recommendation: Query from the modal window container instead. Either move modalRef to the modal window div (line 129's motion.div), or use a separate ref for the focus trap that encompasses both the close button and the scrollable content. For example: const focusableElements = modalRef.current?.parentElement?.parentElement?.querySelectorAll(...) to get the modal window, or better yet, add a new ref to the motion.div at line 129.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.


if (!focusableElements || focusableElements.length === 0) return;

const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;

if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
};

document.addEventListener('keydown', handleKeyDown);
// Focus the close button initially or the modal itself
setTimeout(() => closeButtonRef.current?.focus(), 100);

return () => {
document.removeEventListener('keydown', handleKeyDown);
Comment on lines +77 to +80
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The setTimeout call is not cleaned up if the component unmounts or isOpen changes before the timeout executes. This could cause a focus attempt on an unmounted component, resulting in errors or memory leaks. Store the timeout ID and clear it in the cleanup function.

Suggested change
setTimeout(() => closeButtonRef.current?.focus(), 100);
return () => {
document.removeEventListener('keydown', handleKeyDown);
const timeoutId = window.setTimeout(() => closeButtonRef.current?.focus(), 100);
return () => {
document.removeEventListener('keydown', handleKeyDown);
clearTimeout(timeoutId);

Copilot uses AI. Check for mistakes.
};
}, [isOpen, onClose]);

const handleDownload = async () => {
if (!modalRef.current) return;
Expand Down Expand Up @@ -71,7 +115,7 @@ export const ContributorModal: React.FC<ContributorDetailProps> = ({

return (
<AnimatePresence>
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 overflow-hidden" style={{ paddingTop: 'max(1rem, env(safe-area-inset-top))' }}>
<div className="fixed inset-0 z-[100] flex items-center justify-center p-4 overflow-hidden pt-[max(1rem,env(safe-area-inset-top))]">
{/* Backdrop with blur */}
<motion.div
initial={{ opacity: 0 }}
Expand All @@ -88,8 +132,10 @@ export const ContributorModal: React.FC<ContributorDetailProps> = ({
>
{/* Close Button */}
<button
ref={closeButtonRef}
onClick={onClose}
className="absolute top-6 right-6 z-30 p-2 bg-white/5 hover:bg-white/20 text-white rounded-full transition-all backdrop-blur-md"
aria-label="Tutup detail kontributor"
>
<X size={20} />
</button>
Expand Down Expand Up @@ -161,6 +207,8 @@ export const ContributorModal: React.FC<ContributorDetailProps> = ({
target="_blank"
rel="noopener noreferrer"
className="p-3 rounded-full bg-white/5 text-white hover:bg-white hover:text-black transition-all duration-300"
title="Lihat profil GitHub"
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

Adding both title and aria-label with identical values is redundant. Screen readers will announce the aria-label, and sighted users will see the title as a tooltip. For icon-only links, aria-label alone is sufficient for screen reader users. The title attribute can be removed to reduce duplication, or kept if you specifically want a visual tooltip for sighted users (though the redundancy is acceptable in this case).

Suggested change
title="Lihat profil GitHub"

Copilot uses AI. Check for mistakes.
aria-label="Lihat profil GitHub"
>
<ExternalLink size={18} />
</a>
Expand Down
56 changes: 53 additions & 3 deletions components/CreationStudio/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,51 @@ export const CreationStudio: React.FC<Props> = ({ isOpen, onClose, onPublish })

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

(Refers to lines 52-53)

🚩 consoleOutput state is accumulated but never displayed

The consoleOutput state variable at components/CreationStudio/index.tsx:52 is set via setConsoleOutput callbacks from PyodideSandbox (lines 368-369), but the accumulated output is never rendered or displayed anywhere in the component. This appears to be intentional infrastructure for future Python console output display, but currently the output is lost. The state keeps accumulating with prev + out which could lead to memory growth if many Python scripts are run in a session.

Open in Devin Review

Was this helpful? React with πŸ‘ or πŸ‘Ž to provide feedback.

// Refs
const fileInputRef = useRef<HTMLInputElement>(null);
const modalRef = useRef<HTMLDivElement>(null);
const closeButtonRef = useRef<HTMLButtonElement>(null);

// Focus Trap
React.useEffect(() => {
if (!isOpen) return;

const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Escape') {
onClose();
return;
}

if (e.key === 'Tab') {
const focusableElements = modalRef.current?.querySelectorAll(
'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
);

if (!focusableElements || focusableElements.length === 0) return;

const firstElement = focusableElements[0] as HTMLElement;
const lastElement = focusableElements[focusableElements.length - 1] as HTMLElement;

if (e.shiftKey) {
if (document.activeElement === firstElement) {
e.preventDefault();
lastElement.focus();
}
} else {
if (document.activeElement === lastElement) {
e.preventDefault();
firstElement.focus();
}
}
}
};

document.addEventListener('keydown', handleKeyDown);
// Focus the close button initially
setTimeout(() => closeButtonRef.current?.focus(), 100);

return () => {
document.removeEventListener('keydown', handleKeyDown);
Comment on lines +96 to +99
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The setTimeout call is not cleaned up if the component unmounts or isOpen changes before the timeout executes. This could cause a focus attempt on an unmounted component, resulting in errors or memory leaks. Store the timeout ID and clear it in the cleanup function.

Suggested change
setTimeout(() => closeButtonRef.current?.focus(), 100);
return () => {
document.removeEventListener('keydown', handleKeyDown);
const timeoutId = window.setTimeout(() => closeButtonRef.current?.focus(), 100);
return () => {
document.removeEventListener('keydown', handleKeyDown);
clearTimeout(timeoutId);

Copilot uses AI. Check for mistakes.
};
}, [isOpen, onClose]);
Comment on lines +61 to +101
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Logika untuk focus trap di dalam useEffect ini diduplikasi di dua komponen: CreationStudio.tsx dan ContributorModal.tsx. Ini membuat kode lebih sulit untuk dipelihara.

Saya sarankan untuk mengekstrak logika ini ke dalam custom hook yang dapat digunakan kembali, misalnya useFocusTrap. Ini akan membuat kode lebih bersih, mengurangi duplikasi, dan mempermudah pemeliharaan di masa mendatang.

Contoh struktur hook:

function useFocusTrap(containerRef, initialFocusRef, isOpen, onClose) {
  useEffect(() => {
    if (!isOpen) return;

    // ... all the logic from handleKeyDown ...

    document.addEventListener('keydown', handleKeyDown);
    setTimeout(() => initialFocusRef.current?.focus(), 100);

    return () => {
      document.removeEventListener('keydown', handleKeyDown);
    };
  }, [isOpen, onClose, containerRef, initialFocusRef]);
}


const handleMediumSelect = (m: Medium) => {
setMedium(m);
Expand Down Expand Up @@ -120,6 +165,7 @@ export const CreationStudio: React.FC<Props> = ({ isOpen, onClose, onPublish })
/>

<motion.div
ref={modalRef}
initial={{ opacity: 0, scale: 0.95 }}
animate={{ opacity: 1, scale: 1 }}
exit={{ opacity: 0, scale: 0.95 }}
Expand All @@ -129,7 +175,7 @@ export const CreationStudio: React.FC<Props> = ({ isOpen, onClose, onPublish })
<div className="flex items-center justify-between p-6 border-b border-white/5 bg-black/50 backdrop-blur-md z-10">
<div className="flex items-center gap-4">
{step !== 'selection' && (
<button onClick={() => setStep(step === 'details' ? 'editor' : 'selection')} className="p-2 hover:bg-white/10 rounded-full transition-colors text-gray-400 hover:text-white">
<button onClick={() => setStep(step === 'details' ? 'editor' : 'selection')} className="p-2 hover:bg-white/10 rounded-full transition-colors text-gray-400 hover:text-white" aria-label="Kembali">
<ArrowLeft size={20} />
</button>
)}
Expand Down Expand Up @@ -164,7 +210,7 @@ export const CreationStudio: React.FC<Props> = ({ isOpen, onClose, onPublish })
</div>
)}

<button onClick={onClose} className="p-2 hover:bg-white/10 rounded-full transition-colors text-gray-400 hover:text-white">
<button ref={closeButtonRef} onClick={onClose} className="p-2 hover:bg-white/10 rounded-full transition-colors text-gray-400 hover:text-white" aria-label="Tutup Creation Studio">
<X size={20} />
</button>
</div>
Expand Down Expand Up @@ -259,6 +305,7 @@ export const CreationStudio: React.FC<Props> = ({ isOpen, onClose, onPublish })
onChange={handleImageUpload}
accept="image/*"
className="hidden"
title="Upload Image"
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

medium

Atribut title pada elemen <input> yang tersembunyi (className="hidden") tidak memberikan manfaat aksesibilitas karena elemen ini tidak dapat difokuskan atau diakses oleh screen reader.

Untuk meningkatkan aksesibilitas, area yang dapat diklik (div yang membungkus input ini) harus dibuat dapat diakses. Anda dapat melakukannya dengan menambahkan atribut berikut ke div tersebut:

  • role="button"
  • tabIndex="0"
  • aria-label="Upload Image"
  • Handler onKeyDown untuk memicu klik saat tombol Enter atau Spasi ditekan.

Karena title ini tidak efektif, saya sarankan untuk menghapusnya. Hal yang sama berlaku untuk input unggah video di baris 352.

Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The title attribute on hidden file inputs is not meaningful for accessibility. Screen readers won't announce these hidden inputs, and sighted users won't see them. The title is typically used for tooltips on visible, interactive elements. Consider removing these title attributes or ensuring they serve a clear purpose in your accessibility strategy.

Suggested change
title="Upload Image"

Copilot uses AI. Check for mistakes.
/>
</div>
)}
Expand Down Expand Up @@ -302,6 +349,7 @@ export const CreationStudio: React.FC<Props> = ({ isOpen, onClose, onPublish })
onChange={handleImageUpload}
accept="video/*"
className="hidden"
title="Upload Video"
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

The title attribute on hidden file inputs is not meaningful for accessibility. Screen readers won't announce these hidden inputs, and sighted users won't see them. The title is typically used for tooltips on visible, interactive elements. Consider removing these title attributes or ensuring they serve a clear purpose in your accessibility strategy.

Suggested change
title="Upload Video"

Copilot uses AI. Check for mistakes.
/>
</div>
)
Expand Down Expand Up @@ -352,11 +400,13 @@ export const CreationStudio: React.FC<Props> = ({ isOpen, onClose, onPublish })

<div className="grid grid-cols-1 md:grid-cols-2 gap-6">
<div className="space-y-2">
<label className="block text-xs font-bold text-gray-500 uppercase tracking-wider">Division</label>
<label htmlFor="division-select" className="block text-xs font-bold text-gray-500 uppercase tracking-wider">Division</label>
<select
id="division-select"
value={formData.division}
onChange={(e) => setFormData(prev => ({ ...prev, division: e.target.value as any }))}
className="w-full bg-[#111] border border-white/10 rounded-xl px-4 py-3 text-white focus:outline-none"
title="Select Division"
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

Adding a title attribute to a select element that already has an associated label (via htmlFor/id) is redundant. The label provides the accessible name, and the title would only create a tooltip on hover. For accessibility, the label association is sufficient and preferred. Consider removing the title attribute to avoid duplication.

Suggested change
title="Select Division"

Copilot uses AI. Check for mistakes.
>
{divisions.map(d => <option key={d.id} value={d.id}>{d.name}</option>)}
</select>
Expand Down
4 changes: 2 additions & 2 deletions components/Footer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ export const Footer = () => {
</div>

<div className="flex items-center gap-8">
<a href="https://www.instagram.com/ourcreativity.ofc/" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-white transition-colors" title="Instagram">
<a href="https://www.instagram.com/ourcreativity.ofc/" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-white transition-colors" title="Instagram" aria-label="Instagram">
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

Adding both title and aria-label with identical values is redundant. Screen readers will announce the aria-label, and sighted users will see the title as a tooltip. For icon-only links, aria-label alone is sufficient for screen reader users. The title attribute can be removed to reduce duplication, or kept if you specifically want a visual tooltip for sighted users (though the redundancy is acceptable in this case).

Copilot uses AI. Check for mistakes.
<Instagram size={20} />
</a>
<a href="https://www.tiktok.com/@ourcreativity.ofc" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-white transition-colors" title="TikTok">
<a href="https://www.tiktok.com/@ourcreativity.ofc" target="_blank" rel="noopener noreferrer" className="text-gray-500 hover:text-white transition-colors" title="TikTok" aria-label="TikTok">
Copy link

Copilot AI Dec 24, 2025

Choose a reason for hiding this comment

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

Adding both title and aria-label with identical values is redundant. Screen readers will announce the aria-label, and sighted users will see the title as a tooltip. For icon-only links, aria-label alone is sufficient for screen reader users. The title attribute can be removed to reduce duplication, or kept if you specifically want a visual tooltip for sighted users (though the redundancy is acceptable in this case).

Copilot uses AI. Check for mistakes.
<Music2 size={20} />
</a>
</div>
Expand Down
3 changes: 2 additions & 1 deletion components/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ export const Navbar = () => {
>
<motion.div layout className={`flex items-center justify-between w-full ${isMobileMenuOpen ? '' : (showFullMenu ? 'gap-8' : 'gap-2')}`}>
{/* Logo */}
<Link to="/" className="flex items-center gap-2 group shrink-0" onClick={() => setIsMobileMenuOpen(false)}>
<Link to="/" className="flex items-center gap-2 group shrink-0" onClick={() => setIsMobileMenuOpen(false)} aria-label="Beranda">
<motion.div
layout
className="w-8 h-8 bg-white/10 rounded-full flex items-center justify-center text-white group-hover:bg-white group-hover:text-black transition-colors"
Expand Down Expand Up @@ -150,6 +150,7 @@ export const Navbar = () => {
<button
className="md:hidden text-white p-2"
onClick={() => setIsMobileMenuOpen(!isMobileMenuOpen)}
aria-label={isMobileMenuOpen ? "Tutup Menu" : "Buka Menu"}
>
{isMobileMenuOpen ? <X size={20} /> : <Menu size={20} />}
</button>
Expand Down
4 changes: 2 additions & 2 deletions pages/Info.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,7 @@ const faqs = [

const Noise = () => (
<div
className="absolute inset-0 opacity-[0.05] pointer-events-none z-0 mix-blend-overlay"
style={{ backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)' opacity='1'/%3E%3C/svg%3E")` }}
className="absolute inset-0 opacity-[0.05] pointer-events-none z-0 mix-blend-overlay bg-[url('data:image/svg+xml,%3Csvg%20viewBox=%270%200%20200%20200%27%20xmlns=%27http://www.w3.org/2000/svg%27%3E%3Cfilter%20id=%27noiseFilter%27%3E%3CfeTurbulence%20type=%27fractalNoise%27%20baseFrequency=%270.65%27%20numOctaves=%273%27%20stitchTiles=%27stitch%27/%3E%3C/filter%3E%3Crect%20width=%27100%25%27%20height=%27100%25%27%20filter=%27url(%23noiseFilter)%27%20opacity=%271%27/%3E%3C/svg%3E')]"
></div>
);

Expand Down Expand Up @@ -126,6 +125,7 @@ export const Info = () => {
<button
onClick={() => setOpenFaq(openFaq === i ? null : i)}
className="w-full flex items-center justify-between p-6 text-left hover:bg-white/5 transition-colors"
aria-expanded={openFaq === i}
>
<span className="font-bold text-white text-lg">{faq.q}</span>
<ChevronDown
Expand Down