@naeil/ui 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (67) hide show
  1. package/README.md +36 -0
  2. package/messages/en.json +221 -0
  3. package/messages/ja.json +221 -0
  4. package/messages/ko.json +221 -0
  5. package/package.json +73 -0
  6. package/postcss.config.mjs +7 -0
  7. package/src/app/[locale]/(content)/baby-agent/page.tsx +30 -0
  8. package/src/app/[locale]/(content)/blog/page.tsx +68 -0
  9. package/src/app/[locale]/(content)/cc/page.tsx +216 -0
  10. package/src/app/[locale]/(content)/design/page.tsx +703 -0
  11. package/src/app/[locale]/(content)/layout.tsx +7 -0
  12. package/src/app/[locale]/(content)/pkm/page.tsx +31 -0
  13. package/src/app/[locale]/(content)/projects/page.tsx +100 -0
  14. package/src/app/[locale]/(content)/sa/page.tsx +179 -0
  15. package/src/app/[locale]/(landing)/page.tsx +157 -0
  16. package/src/app/[locale]/layout.tsx +72 -0
  17. package/src/app/[locale]/login/actions.ts +10 -0
  18. package/src/app/[locale]/login/oauth-buttons.tsx +78 -0
  19. package/src/app/[locale]/login/page.tsx +85 -0
  20. package/src/app/auth/callback/route.ts +78 -0
  21. package/src/app/favicon.ico +0 -0
  22. package/src/app/globals.css +32 -0
  23. package/src/app/layout.tsx +22 -0
  24. package/src/app/sitemap.ts +26 -0
  25. package/src/components/accent-picker.tsx +62 -0
  26. package/src/components/auth-slot.tsx +96 -0
  27. package/src/components/footer-wrapper.tsx +8 -0
  28. package/src/components/footer.tsx +193 -0
  29. package/src/components/hero-scene.tsx +948 -0
  30. package/src/components/hero-section.tsx +57 -0
  31. package/src/components/locale-switcher.tsx +74 -0
  32. package/src/components/logo.tsx +51 -0
  33. package/src/components/nav-server-wrapper.tsx +22 -0
  34. package/src/components/nav-wrapper.tsx +45 -0
  35. package/src/components/nav.tsx +550 -0
  36. package/src/components/paraglider-cursor.tsx +112 -0
  37. package/src/components/project-layout.tsx +194 -0
  38. package/src/components/theme-provider.tsx +10 -0
  39. package/src/components/theme-toggle-icon.tsx +62 -0
  40. package/src/components/theme-toggle.tsx +39 -0
  41. package/src/components/typography.tsx +27 -0
  42. package/src/components/ui/avatar.tsx +109 -0
  43. package/src/components/ui/badge.tsx +47 -0
  44. package/src/components/ui/button.tsx +65 -0
  45. package/src/components/ui/card.tsx +106 -0
  46. package/src/components/ui/dialog.tsx +158 -0
  47. package/src/components/ui/dropdown-menu.tsx +257 -0
  48. package/src/components/ui/input.tsx +21 -0
  49. package/src/components/ui/sonner.tsx +54 -0
  50. package/src/components/workflow-diagram.tsx +414 -0
  51. package/src/i18n/config.ts +8 -0
  52. package/src/i18n/request.ts +16 -0
  53. package/src/i18n/routing.ts +12 -0
  54. package/src/lib/supabase/client.ts +18 -0
  55. package/src/lib/supabase/middleware.ts +42 -0
  56. package/src/lib/supabase/server.ts +37 -0
  57. package/src/lib/utils.ts +6 -0
  58. package/src/proxy.ts +62 -0
  59. package/src/tokens/.gitkeep +0 -0
  60. package/src/tokens/color.json +56 -0
  61. package/src/tokens/radius.json +9 -0
  62. package/src/tokens/semantic.json +81 -0
  63. package/src/tokens/shadow.json +7 -0
  64. package/src/tokens/spacing.json +16 -0
  65. package/src/tokens/typography.json +34 -0
  66. package/src/types/i18n.d.ts +6 -0
  67. package/src/utils/.gitkeep +0 -0
@@ -0,0 +1,7 @@
1
+ export default function ContentLayout({
2
+ children,
3
+ }: {
4
+ children: React.ReactNode;
5
+ }) {
6
+ return <main className="pt-28">{children}</main>;
7
+ }
@@ -0,0 +1,31 @@
1
+ import { setRequestLocale } from "next-intl/server";
2
+ import { useTranslations } from "next-intl";
3
+ import { ProjectLayout } from "@/components/project-layout";
4
+ import type { ProjectData } from "@/components/project-layout";
5
+
6
+ export default async function PKMPage({
7
+ params,
8
+ }: {
9
+ params: Promise<{ locale: string }>;
10
+ }) {
11
+ const { locale } = await params;
12
+ setRequestLocale(locale);
13
+
14
+ return <PKMContent />;
15
+ }
16
+
17
+ function PKMContent() {
18
+ const t = useTranslations("pkm");
19
+
20
+ const data: ProjectData = {
21
+ icon: "/images/jellyfish.png",
22
+ name: t("name"),
23
+ description: t("description"),
24
+ accent: "oklch(0.627 0.265 303)",
25
+ tags: ["Python", "Embeddings", "Semantic Search"],
26
+ github: "https://github.com/naeil-dev/pkm",
27
+ comingSoon: true,
28
+ };
29
+
30
+ return <ProjectLayout data={data} />;
31
+ }
@@ -0,0 +1,100 @@
1
+ import { setRequestLocale } from "next-intl/server";
2
+ import { useTranslations } from "next-intl";
3
+ import { Link } from "@/i18n/routing";
4
+ import {
5
+ Card,
6
+ CardHeader,
7
+ CardTitle,
8
+ CardDescription,
9
+ CardContent,
10
+ } from "@/components/ui/card";
11
+ import { Badge } from "@/components/ui/badge";
12
+ import { PageTitle } from "@/components/typography";
13
+
14
+ export default async function ProjectsPage({
15
+ params,
16
+ }: {
17
+ params: Promise<{ locale: string }>;
18
+ }) {
19
+ const { locale } = await params;
20
+ setRequestLocale(locale);
21
+
22
+ return <ProjectsContent />;
23
+ }
24
+
25
+ function ProjectsContent() {
26
+ const t = useTranslations();
27
+
28
+ const PROJECTS = [
29
+ {
30
+ name: t("projects.cc.name"),
31
+ description: t("projects.cc.description"),
32
+ href: "/cc" as const,
33
+ tags: ["Python", "RSS", "AI"],
34
+ status: t("projects.status.active"),
35
+ coming: false,
36
+ },
37
+ {
38
+ name: t("projects.pkm.name"),
39
+ description: t("projects.pkm.description"),
40
+ href: "/pkm" as const,
41
+ tags: ["Python", "Embeddings", "Search"],
42
+ status: t("projects.status.coming"),
43
+ coming: true,
44
+ },
45
+ {
46
+ name: t("projects.naeilUi.name"),
47
+ description: t("projects.naeilUi.description"),
48
+ href: "/design" as const,
49
+ tags: ["React", "Tailwind", "Design Tokens"],
50
+ status: t("projects.status.active"),
51
+ coming: false,
52
+ },
53
+ {
54
+ name: t("projects.babyAgent.name"),
55
+ description: t("projects.babyAgent.description"),
56
+ href: "/baby-agent" as const,
57
+ tags: ["OpenClaw", "LINE", "AI"],
58
+ status: t("projects.status.coming"),
59
+ coming: true,
60
+ },
61
+ ];
62
+
63
+ return (
64
+ <section className="mx-auto max-w-4xl px-6 pb-24">
65
+ <div className="mb-10">
66
+ <PageTitle className="mb-3">{t("projects.title")}</PageTitle>
67
+ <p className="text-muted-foreground text-lg">
68
+ {t("projects.subtitle")}
69
+ </p>
70
+ </div>
71
+
72
+ <div className="grid gap-4">
73
+ {PROJECTS.map((project) => (
74
+ <Link key={project.name} href={project.href}>
75
+ <Card className="transition-colors hover:border-foreground/20">
76
+ <CardHeader>
77
+ <div className="flex items-center justify-between">
78
+ <CardTitle className="text-base">{project.name}</CardTitle>
79
+ <Badge variant={project.coming ? "outline" : "secondary"}>
80
+ {project.status}
81
+ </Badge>
82
+ </div>
83
+ <CardDescription>{project.description}</CardDescription>
84
+ </CardHeader>
85
+ <CardContent>
86
+ <div className="flex flex-wrap gap-1.5">
87
+ {project.tags.map((tag) => (
88
+ <Badge key={tag} variant="outline" className="text-xs">
89
+ {tag}
90
+ </Badge>
91
+ ))}
92
+ </div>
93
+ </CardContent>
94
+ </Card>
95
+ </Link>
96
+ ))}
97
+ </div>
98
+ </section>
99
+ );
100
+ }
@@ -0,0 +1,179 @@
1
+ import { setRequestLocale } from "next-intl/server";
2
+ import { useTranslations } from "next-intl";
3
+ import { ProjectLayout } from "@/components/project-layout";
4
+ import type { ProjectData } from "@/components/project-layout";
5
+
6
+ export default async function SAPage({
7
+ params,
8
+ }: {
9
+ params: Promise<{ locale: string }>;
10
+ }) {
11
+ const { locale } = await params;
12
+ setRequestLocale(locale);
13
+
14
+ return <SAContent />;
15
+ }
16
+
17
+ function SAContent() {
18
+ const t = useTranslations("sa");
19
+
20
+ const data: ProjectData = {
21
+ icon: "/images/fish.png",
22
+ name: t("name"),
23
+ description: t("description"),
24
+ accent: "oklch(0.723 0.219 149)",
25
+ tags: ["Python", "PostgreSQL", "pgvector", "BGE-M3", "Claude", "Docling"],
26
+ github: "https://github.com/naeil-dev/sustainability-analyzer",
27
+
28
+ overview: t("overview"),
29
+
30
+ features: [
31
+ {
32
+ icon: "📄",
33
+ title: t("features.ingest.title"),
34
+ description: t("features.ingest.desc"),
35
+ },
36
+ {
37
+ icon: "🔍",
38
+ title: t("features.embed.title"),
39
+ description: t("features.embed.desc"),
40
+ },
41
+ {
42
+ icon: "🗂️",
43
+ title: t("features.topics.title"),
44
+ description: t("features.topics.desc"),
45
+ },
46
+ {
47
+ icon: "📊",
48
+ title: t("features.assess.title"),
49
+ description: t("features.assess.desc"),
50
+ },
51
+ {
52
+ icon: "⚖️",
53
+ title: t("features.compare.title"),
54
+ description: t("features.compare.desc"),
55
+ },
56
+ {
57
+ icon: "⚡",
58
+ title: t("features.zeroCost.title"),
59
+ description: t("features.zeroCost.desc"),
60
+ },
61
+ ],
62
+
63
+ workflow: {
64
+ nodes: [
65
+ {
66
+ id: "pdf",
67
+ label: "ESG Reports",
68
+ type: "source",
69
+ position: { x: 50, y: 5 },
70
+ detail: {
71
+ description: t("workflow.pdf.desc"),
72
+ tech: ["PDF"],
73
+ meta: t("workflow.pdf.meta"),
74
+ },
75
+ },
76
+ {
77
+ id: "docling",
78
+ label: "Docling Parser",
79
+ type: "process",
80
+ position: { x: 50, y: 18 },
81
+ detail: {
82
+ description: t("workflow.docling.desc"),
83
+ tech: ["Docling", "Python"],
84
+ meta: t("workflow.docling.meta"),
85
+ },
86
+ },
87
+ {
88
+ id: "chunk",
89
+ label: "Hierarchical Chunker",
90
+ type: "process",
91
+ position: { x: 50, y: 32 },
92
+ detail: {
93
+ description: t("workflow.chunk.desc"),
94
+ tech: ["Parent-Child", "Layout-aware"],
95
+ meta: t("workflow.chunk.meta"),
96
+ },
97
+ },
98
+ {
99
+ id: "bgem3",
100
+ label: "BGE-M3",
101
+ type: "ai",
102
+ position: { x: 25, y: 46 },
103
+ detail: {
104
+ description: t("workflow.bgem3.desc"),
105
+ tech: ["FlagEmbedding", "PyTorch"],
106
+ meta: t("workflow.bgem3.meta"),
107
+ },
108
+ },
109
+ {
110
+ id: "pgvector",
111
+ label: "PostgreSQL",
112
+ type: "storage",
113
+ position: { x: 75, y: 46 },
114
+ detail: {
115
+ description: t("workflow.pgvector.desc"),
116
+ tech: ["PostgreSQL 17", "pgvector", "Docker"],
117
+ },
118
+ },
119
+ {
120
+ id: "search",
121
+ label: "Hybrid Search",
122
+ type: "process",
123
+ position: { x: 50, y: 60 },
124
+ detail: {
125
+ description: t("workflow.search.desc"),
126
+ tech: ["Dense", "Sparse", "pgvector"],
127
+ },
128
+ },
129
+ {
130
+ id: "claude",
131
+ label: "Claude Sonnet",
132
+ type: "ai",
133
+ position: { x: 50, y: 74 },
134
+ detail: {
135
+ description: t("workflow.claude.desc"),
136
+ tech: ["Claude Sonnet", "Anthropic API"],
137
+ meta: t("workflow.claude.meta"),
138
+ },
139
+ },
140
+ {
141
+ id: "report",
142
+ label: "Comparison Report",
143
+ type: "output",
144
+ position: { x: 50, y: 90 },
145
+ detail: {
146
+ description: t("workflow.report.desc"),
147
+ tech: ["Jinja2", "HTML"],
148
+ meta: t("workflow.report.meta"),
149
+ },
150
+ },
151
+ ],
152
+ edges: [
153
+ { from: "pdf", to: "docling" },
154
+ { from: "docling", to: "chunk" },
155
+ { from: "chunk", to: "bgem3" },
156
+ { from: "chunk", to: "pgvector" },
157
+ { from: "bgem3", to: "pgvector" },
158
+ { from: "pgvector", to: "search" },
159
+ { from: "search", to: "claude" },
160
+ { from: "claude", to: "pgvector" },
161
+ { from: "claude", to: "report" },
162
+ ],
163
+ },
164
+
165
+ stack: [
166
+ { icon: "🐍", name: "Python 3.12" },
167
+ { icon: "🐘", name: "PostgreSQL 17" },
168
+ { icon: "🧮", name: "pgvector 0.8" },
169
+ { icon: "🔤", name: "BGE-M3" },
170
+ { icon: "🧠", name: "Claude Sonnet" },
171
+ { icon: "📄", name: "Docling" },
172
+ { icon: "🔥", name: "PyTorch" },
173
+ { icon: "🐳", name: "Docker Compose" },
174
+ { icon: "📊", name: "Jinja2" },
175
+ ],
176
+ };
177
+
178
+ return <ProjectLayout data={data} />;
179
+ }
@@ -0,0 +1,157 @@
1
+ import { setRequestLocale } from "next-intl/server";
2
+ import { useTranslations } from "next-intl";
3
+ import { Link } from "@/i18n/routing";
4
+ import { Badge } from "@/components/ui/badge";
5
+ import { HeroSection } from "@/components/hero-section";
6
+ import Image from "next/image";
7
+
8
+ export default async function HubPage({
9
+ params,
10
+ }: {
11
+ params: Promise<{ locale: string }>;
12
+ }) {
13
+ const { locale } = await params;
14
+ setRequestLocale(locale);
15
+
16
+ return <HubContent />;
17
+ }
18
+
19
+ const PROJECTS = [
20
+ {
21
+ key: "cc" as const,
22
+ href: "/cc" as const,
23
+ icon: "/images/coral.png",
24
+ accent: "oklch(0.704 0.140 181)",
25
+ tags: ["Python", "RSS", "AI"],
26
+ status: "active" as const,
27
+ featured: true,
28
+ },
29
+ {
30
+ key: "pkm" as const,
31
+ href: "/pkm" as const,
32
+ icon: "/images/jellyfish.png",
33
+ accent: "oklch(0.702 0.183 293)",
34
+ tags: ["Python", "Embeddings", "Search"],
35
+ status: "coming" as const,
36
+ featured: false,
37
+ },
38
+ {
39
+ key: "naeilUi" as const,
40
+ href: "/design" as const,
41
+ icon: "/images/whale.png",
42
+ accent: "oklch(0.623 0.214 259)",
43
+ tags: ["React", "Tailwind", "OKLCH"],
44
+ status: "active" as const,
45
+ featured: false,
46
+ },
47
+ {
48
+ key: "sa" as const,
49
+ href: "/sa" as const,
50
+ icon: "/images/fish.png",
51
+ accent: "oklch(0.723 0.219 149)",
52
+ tags: ["Python", "pgvector", "Claude"],
53
+ status: "active" as const,
54
+ featured: false,
55
+ },
56
+ {
57
+ key: "babyAgent" as const,
58
+ href: "/baby-agent" as const,
59
+ icon: "/images/turtle.png",
60
+ accent: "oklch(0.769 0.188 70.08)",
61
+ tags: ["LINE", "AI"],
62
+ status: "coming" as const,
63
+ featured: false,
64
+ },
65
+ ] as const;
66
+
67
+ function HubContent() {
68
+ const t = useTranslations();
69
+
70
+ return (
71
+ <>
72
+ {/* Hero */}
73
+ <HeroSection />
74
+
75
+ {/* Projects — Bento Grid */}
76
+ <section className="mx-auto max-w-4xl px-6 py-24">
77
+ <h2 className="text-foreground mb-2 text-2xl font-bold tracking-tight">
78
+ {t("projects.title")}
79
+ </h2>
80
+ <p className="text-muted-foreground mb-10 text-sm">
81
+ {t("projects.subtitle")}
82
+ </p>
83
+
84
+ <div className="grid grid-cols-1 gap-3 sm:grid-cols-2">
85
+ {PROJECTS.map((project) => (
86
+ <Link
87
+ key={project.key}
88
+ href={project.href}
89
+ className={[
90
+ "group relative rounded-2xl border border-border-subtle bg-surface-subtle p-7 transition-all duration-250",
91
+ "hover:-translate-y-0.5 hover:border-border-subtle-hover hover:shadow-[0_8px_32px_rgba(0,0,0,0.08)] dark:hover:shadow-[0_8px_32px_rgba(0,0,0,0.3)]",
92
+ project.featured ? "sm:col-span-2 sm:flex sm:items-center sm:gap-8" : "",
93
+ ].join(" ")}
94
+ style={{ "--accent": project.accent } as React.CSSProperties}
95
+ >
96
+ {/* Icon */}
97
+ <div
98
+ className={[
99
+ "flex shrink-0 items-center justify-center rounded-[14px]",
100
+ project.featured
101
+ ? "mb-5 h-14 w-14 sm:mb-0 sm:h-20 sm:w-20"
102
+ : "mb-5 h-14 w-14",
103
+ ].join(" ")}
104
+ style={{
105
+ background: `color-mix(in oklch, ${project.accent} 8%, transparent)`,
106
+ border: `1px solid color-mix(in oklch, ${project.accent} 20%, transparent)`,
107
+ }}
108
+ >
109
+ <Image
110
+ src={project.icon}
111
+ alt=""
112
+ width={project.featured ? 52 : 36}
113
+ height={project.featured ? 52 : 36}
114
+ className="object-contain"
115
+ />
116
+ </div>
117
+
118
+ {/* Text */}
119
+ <div className={project.featured ? "flex-1" : ""}>
120
+ <div className="text-foreground mb-1.5 text-[15px] font-semibold">
121
+ {t(`projects.${project.key}.name`)}
122
+ {project.status === "coming" && (
123
+ <Badge
124
+ variant="outline"
125
+ className="text-muted-foreground ml-2 px-1.5 py-0 text-[9px] align-middle"
126
+ >
127
+ {t("projects.status.coming")}
128
+ </Badge>
129
+ )}
130
+ </div>
131
+ <p className="text-muted-foreground mb-3 text-[13px] leading-relaxed">
132
+ {t(`projects.${project.key}.description`)}
133
+ </p>
134
+ <div className="flex flex-wrap gap-1.5">
135
+ {project.tags.map((tag) => (
136
+ <span
137
+ key={tag}
138
+ className="rounded-full border border-border-subtle px-2 py-0.5 text-[10px] text-zinc-400"
139
+ >
140
+ {tag}
141
+ </span>
142
+ ))}
143
+ </div>
144
+ </div>
145
+
146
+ {/* Arrow */}
147
+ <span className="text-muted-foreground/40 group-hover:text-foreground absolute right-6 top-6 text-sm transition-colors">
148
+ ↗
149
+ </span>
150
+ </Link>
151
+ ))}
152
+ </div>
153
+ </section>
154
+
155
+ </>
156
+ );
157
+ }
@@ -0,0 +1,72 @@
1
+ import type { Metadata } from "next";
2
+ import { NextIntlClientProvider } from "next-intl";
3
+ import {
4
+ getMessages,
5
+ getTranslations,
6
+ setRequestLocale,
7
+ } from "next-intl/server";
8
+ import { notFound } from "next/navigation";
9
+ import { ThemeProvider } from "@/components/theme-provider";
10
+ import { Toaster } from "@/components/ui/sonner";
11
+ import { NavServerWrapper } from "@/components/nav-server-wrapper";
12
+ import { FooterWrapper } from "@/components/footer-wrapper";
13
+ import { locales, type Locale } from "@/i18n/config";
14
+
15
+ export function generateStaticParams() {
16
+ return locales.map((locale) => ({ locale }));
17
+ }
18
+
19
+ export async function generateMetadata({
20
+ params,
21
+ }: {
22
+ params: Promise<{ locale: string }>;
23
+ }): Promise<Metadata> {
24
+ const { locale } = await params;
25
+ const t = await getTranslations({ locale, namespace: "common" });
26
+ return {
27
+ title: t("appName"),
28
+ description: "Personal projects by Jay",
29
+ alternates: {
30
+ canonical: `https://naeil.dev/${locale}`,
31
+ languages: {
32
+ en: "https://naeil.dev/en",
33
+ ko: "https://naeil.dev/ko",
34
+ ja: "https://naeil.dev/ja",
35
+ "x-default": "https://naeil.dev/en",
36
+ },
37
+ },
38
+ };
39
+ }
40
+
41
+ export default async function LocaleLayout({
42
+ children,
43
+ params,
44
+ }: {
45
+ children: React.ReactNode;
46
+ params: Promise<{ locale: string }>;
47
+ }) {
48
+ const { locale } = await params;
49
+
50
+ if (!locales.includes(locale as Locale)) {
51
+ notFound();
52
+ }
53
+
54
+ setRequestLocale(locale);
55
+ const messages = await getMessages();
56
+
57
+ return (
58
+ <NextIntlClientProvider messages={messages}>
59
+ <ThemeProvider
60
+ attribute="class"
61
+ defaultTheme="dark"
62
+ enableSystem
63
+ disableTransitionOnChange
64
+ >
65
+ <NavServerWrapper />
66
+ <main>{children}</main>
67
+ <FooterWrapper />
68
+ <Toaster />
69
+ </ThemeProvider>
70
+ </NextIntlClientProvider>
71
+ );
72
+ }
@@ -0,0 +1,10 @@
1
+ "use server";
2
+
3
+ import { redirect } from "@/i18n/routing";
4
+ import { createClient } from "@/lib/supabase/server";
5
+
6
+ export async function logout(locale: string) {
7
+ const supabase = await createClient();
8
+ await supabase.auth.signOut();
9
+ redirect({ href: "/", locale });
10
+ }
@@ -0,0 +1,78 @@
1
+ "use client";
2
+
3
+ import { useTranslations } from "next-intl";
4
+ import { Button } from "@/components/ui/button";
5
+ import { createClient } from "@/lib/supabase/client";
6
+
7
+ export function OAuthButtons({ next }: { next?: string }) {
8
+ const t = useTranslations("auth");
9
+
10
+ const handleOAuth = async (provider: "google" | "github") => {
11
+ const supabase = createClient();
12
+ await supabase.auth.signInWithOAuth({
13
+ provider,
14
+ options: {
15
+ redirectTo: `${window.location.origin}/auth/callback`,
16
+ },
17
+ });
18
+ };
19
+
20
+ return (
21
+ <div className="flex flex-col gap-3">
22
+ <Button
23
+ variant="outline"
24
+ size="lg"
25
+ className="w-full"
26
+ onClick={() => handleOAuth("google")}
27
+ >
28
+ <GoogleIcon />
29
+ {t("loginWithGoogle")}
30
+ </Button>
31
+ <Button
32
+ variant="outline"
33
+ size="lg"
34
+ className="w-full"
35
+ onClick={() => handleOAuth("github")}
36
+ >
37
+ <GitHubIcon />
38
+ {t("loginWithGithub")}
39
+ </Button>
40
+ </div>
41
+ );
42
+ }
43
+
44
+ function GoogleIcon() {
45
+ return (
46
+ <svg width="16" height="16" viewBox="0 0 24 24" fill="none">
47
+ <path
48
+ d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92a5.06 5.06 0 0 1-2.2 3.32v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.1z"
49
+ fill="#4285F4"
50
+ />
51
+ <path
52
+ d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"
53
+ fill="#34A853"
54
+ />
55
+ <path
56
+ d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"
57
+ fill="#FBBC05"
58
+ />
59
+ <path
60
+ d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"
61
+ fill="#EA4335"
62
+ />
63
+ </svg>
64
+ );
65
+ }
66
+
67
+ function GitHubIcon() {
68
+ return (
69
+ <svg
70
+ width="16"
71
+ height="16"
72
+ viewBox="0 0 24 24"
73
+ fill="currentColor"
74
+ >
75
+ <path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" />
76
+ </svg>
77
+ );
78
+ }