Repository navigation
Expand file tree
/
Copy pathGuideHub.tsx
More file actions
123 lines (113 loc) · 7.49 KB
/
Copy pathGuideHub.tsx
File metadata and controls
123 lines (113 loc) · 7.49 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
import { useTranslation } from "react-i18next";
import { Link } from "react-router-dom";
import { localizedPath, useRouteLanguage } from "@/lib/seo/localized-routes";
import { AI_DEBATE_LINKS, AI_DEBATE_PARTNER } from "@/lib/seo/ai-debate-cluster";
import { Reveal } from "./Reveal";
/**
* Links from the landing page to the public guides and free tools, so crawlers
* and readers reach them from the home page. Each link is a page in the sitemap;
* scripts/check-links.mjs fails the build when the home page links to too few.
*/
const GROUPS = [
{ id: "practice", paths: ["/ai-debate", "/ai-debate-practice", "/ai-debate-case-generator", "/ai-rebuttal-generator", "/ai-debate-coach", "/debate-evidence-finder"] },
{ id: "formats", paths: ["/public-forum-debate", "/lincoln-douglas-debate", "/policy-debate", "/british-parliamentary-debate", "/world-schools-debate", "/congress-debate"] },
{ id: "skills", paths: ["/how-to-write-a-debate-case", "/debate-case-example", "/how-to-practice-rebuttals", "/debate-clash-points", "/debate-fallacies", "/debate-judge-adaptation", "/how-to-prepare-for-a-debate-tournament"] },
{ id: "tools", paths: ["/tools", "/tools/debate-timer", "/tools/debate-motion-generator", "/tools/debate-flow-sheet", "/tools/speech-time-calculator"] },
{ id: "compare", paths: ["/best-ai-debate-tools", "/best-ai-for-debate", "/free-vs-paid-ai-debate-tools", "/chatgpt-vs-claude-for-debate", "/best-ai-for-public-forum"] },
// Every /debateowl-vs-* page, so each one is a click from the home page.
{
id: "versus",
paths: [
"/debateowl-vs-chatgpt", "/debateowl-vs-claude", "/debateowl-vs-gemini", "/debateowl-vs-hyperwrite", "/debateowl-vs-schoolai",
"/debateowl-vs-publicforumai", "/debateowl-vs-windebate", "/debateowl-vs-debateclub", "/debateowl-vs-debate-ladder",
"/debateowl-vs-argufight", "/debateowl-vs-mindduel", "/debateowl-vs-debatable", "/debateowl-vs-prilo",
"/debateowl-vs-debatethis", "/debateowl-vs-kialo-edu",
],
},
] as const;
const TOOLS = GROUPS.find((group) => group.id === "tools")!;
/** The "which AI for debate" answer page written in each translated language (generated by scripts/seo_comparison_pages.py). */
const NATIVE_ANSWER = { hu: "/hu/melyik-ai-a-legjobb-vitara", es: "/es/mejor-ia-para-preparar-un-debate" } as const;
const chipClass = "inline-flex shrink-0 items-center whitespace-nowrap rounded-full border-2 border-border bg-card px-3.5 py-1.5 text-sm font-semibold text-foreground transition-colors hover:border-brand-tint-border hover:text-primary pointer-coarse:min-h-11";
/** One sideways-scrolling row of chips per group on phones; the chips wrap from md up. */
const chipRowClass = "-mx-4 flex gap-2 overflow-x-auto px-4 pb-1 sm:-mx-6 sm:px-6 md:mx-0 md:flex-wrap md:overflow-visible md:px-0";
/** One group of chips. Static pages, not app routes: a plain link loads each from the server. */
const HubGroup = ({ id, title, links }: { id: string; title: string; links: readonly { path: string; label: string }[] }) => (
<nav aria-labelledby={`guide-hub-${id}`}>
<h3 id={`guide-hub-${id}`} className="mb-3 font-display text-lg font-[600] text-heading">{title}</h3>
<ul className={chipRowClass}>
{links.map(({ path, label }) => (
<li key={path} className="shrink-0">
<a href={path} className={chipClass}>{label}</a>
</li>
))}
</ul>
</nav>
);
const HubHeading = () => {
const { t } = useTranslation();
return (
<Reveal className="mb-8 text-center md:mb-10">
<h2 id="guide-hub-title" className="section-heading">{t('guideHub.title')}</h2>
<p className="mx-auto mt-4 hidden max-w-2xl text-lg text-muted-foreground md:block">{t('guideHub.subtitle')}</p>
</Reveal>
);
};
/**
* /hu and /es: the guides are in English, so only the free tools show, at their
* addresses in that language, plus one link to the English guides on the home page.
*/
const TranslatedToolsHub = ({ language }: { language: "hu" | "es" }) => {
const { t } = useTranslation();
return (
<section aria-labelledby="guide-hub-title" className="py-12 md:py-20">
<div className="section-container">
<HubHeading />
<Reveal delay={0.05} className="mx-auto max-w-2xl">
<nav aria-labelledby="guide-hub-tools">
<h3 id="guide-hub-tools" className="mb-3 font-display text-lg font-[600] text-heading">{t('guideHub.tools')}</h3>
<ul className={chipRowClass}>
{TOOLS.paths.map((path) => (
<li key={path} className="shrink-0">
<Link to={localizedPath(path, language)} className={chipClass}>{t(`guideHub.links.${path.slice(1)}`)}</Link>
</li>
))}
</ul>
</nav>
{/* The English home page lists every guide; a plain link loads it from the server. */}
<p className="mt-5 flex flex-col items-start gap-2">
<a href={NATIVE_ANSWER[language]} hrefLang={language} className="inline-flex items-center font-semibold text-primary underline-offset-4 hover:underline pointer-coarse:min-h-11">{t('guideHub.nativeAnswer')}</a>
{/* No translated AI debate page yet, so this one is the English page. */}
{AI_DEBATE_PARTNER && (
<a href={AI_DEBATE_PARTNER.path} hrefLang="en" className="inline-flex items-center font-semibold text-primary underline-offset-4 hover:underline pointer-coarse:min-h-11">{t('guideHub.aiDebateInEnglish')}</a>
)}
{language === "hu" && (
<a href="/hu/mi-vitafelkeszules" hrefLang="hu" className="inline-flex items-center font-semibold text-primary underline-offset-4 hover:underline pointer-coarse:min-h-11">{t('guideHub.hungarianPrep')}</a>
)}
<a href="/#guide-hub-title" hrefLang="en" className="inline-flex items-center font-semibold text-primary underline-offset-4 hover:underline pointer-coarse:min-h-11">{t('guideHub.englishGuides')}</a>
</p>
</Reveal>
</div>
</section>
);
};
const GuideHub = () => {
const { t } = useTranslation();
const language = useRouteLanguage();
if (language !== "en") return <TranslatedToolsHub language={language} />;
return (
<section aria-labelledby="guide-hub-title" className="py-12 md:py-20">
<div className="section-container">
<HubHeading />
<Reveal delay={0.05} className="grid grid-cols-1 gap-6 md:grid-cols-2 md:gap-8 lg:grid-cols-3">
{/* The AI debate pages by the phrases people search for them with, from src/config/ai-debate-cluster.json. */}
<HubGroup id="ai-debate" title={t('guideHub.aiDebate')} links={AI_DEBATE_LINKS.map(({ path, anchor }) => ({ path, label: anchor[0].toUpperCase() + anchor.slice(1) }))} />
{GROUPS.map((group) => (
<HubGroup key={group.id} id={group.id} title={t(`guideHub.${group.id}`)} links={group.paths.map((path) => ({ path, label: t(`guideHub.links.${path.slice(1)}`) }))} />
))}
</Reveal>
</div>
</section>
);
};
export default GuideHub;