+
+ Practice Impromptu{" "}
+ Speaking
+
+
+ Practice table-topics and hone your skills. Track your progress
+ and build confidence with timed speaking sessions.
+
+
+
+
+
+
+
+
-
-
-
Documentation →
-
- Learn more about Create T3 App, the libraries it uses, and how
- to deploy it.
+
+
+
-
-
- {session && Logged in as {session.user?.name}}
+
+
+
+
+
+
+ {/* Left Mikey - visible on large screens */}
+
+
+
+
+
+
+ Everything You Need to{" "}
+ Level{" "}
+ Up
+
+
+ Whether you're preparing for a contest or just practicing
+ to stay sharp, TableTopicker can help!
-
- {session ? "Sign out" : "Sign in"}
-
+
+
+ {/* Right Mikey - visible on large screens */}
+
+
+
+
+
+
+
+
+
+ Timed Practice
+
+ 1-2 minute rounds with built-in timer and stop-light color
+ notifiers. Speak concisely and confidently.
+
+
+
+
+
+
+
+
+
+
+
+
+ Track Your Progress
+
+ Rate your performance. Watch your skills grow over time
+ through self analysis.
+
+
+
+
+
+
+
+
+
+
+ AI-Generated Topics
+
+ Premium
+
+
+
+ Unlimited questions for any theme you can imagine.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ All your conversations are{" "}
+ impromptu so it's
+ something you should practice.
+
+
+
+
+
-
+
);
}
diff --git a/src/components/footer.tsx b/src/components/footer.tsx
new file mode 100644
index 0000000..5d98be7
--- /dev/null
+++ b/src/components/footer.tsx
@@ -0,0 +1,73 @@
+import Link from "next/link";
+import { Sparkles } from "lucide-react";
+
+export function Footer() {
+ return (
+
+ );
+}
diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx
new file mode 100644
index 0000000..2a33cea
--- /dev/null
+++ b/src/components/navigation.tsx
@@ -0,0 +1,189 @@
+"use client";
+
+import { useState, useEffect } from "react";
+import Link from "next/link";
+import { useSession, signOut } from "next-auth/react";
+import { useTheme } from "next-themes";
+import { Sun, Moon, Menu, X, LogOut, User } from "lucide-react";
+import { Button } from "~/components/ui/button";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuLabel,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "~/components/ui/dropdown-menu";
+import { cn } from "~/lib/utils";
+
+export function Navigation() {
+ const [isScrolled, setIsScrolled] = useState(false);
+ const [isMobileMenuOpen, setIsMobileMenuOpen] = useState(false);
+ const { data: session } = useSession();
+ const { theme, setTheme } = useTheme();
+ const [mounted, setMounted] = useState(false);
+
+ // Prevent hydration mismatch for theme-dependent UI
+ useEffect(() => {
+ setMounted(true);
+ }, []);
+
+ // Scroll detection
+ useEffect(() => {
+ const handleScroll = () => {
+ setIsScrolled(window.scrollY > 10);
+ };
+
+ window.addEventListener("scroll", handleScroll);
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ const toggleTheme = () => {
+ setTheme(theme === "light" ? "dark" : "light");
+ };
+
+ return (
+
+ );
+}
diff --git a/src/components/theme-provider.tsx b/src/components/theme-provider.tsx
new file mode 100644
index 0000000..189a2b1
--- /dev/null
+++ b/src/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+"use client";
+
+import * as React from "react";
+import { ThemeProvider as NextThemesProvider } from "next-themes";
+
+export function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps
) {
+ return {children};
+}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..20f8cd0
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,269 @@
+"use client";
+
+import * as React from "react";
+import { Menu as MenuPrimitive } from "@base-ui/react/menu";
+
+import { cn } from "~/lib/utils";
+import { HugeiconsIcon } from "@hugeicons/react";
+import { ArrowRight01Icon, Tick02Icon } from "@hugeicons/core-free-icons";
+
+function DropdownMenu({ ...props }: MenuPrimitive.Root.Props) {
+ return ;
+}
+
+function DropdownMenuPortal({ ...props }: MenuPrimitive.Portal.Props) {
+ return ;
+}
+
+function DropdownMenuTrigger({ ...props }: MenuPrimitive.Trigger.Props) {
+ return ;
+}
+
+function DropdownMenuContent({
+ align = "start",
+ alignOffset = 0,
+ side = "bottom",
+ sideOffset = 4,
+ className,
+ ...props
+}: MenuPrimitive.Popup.Props &
+ Pick<
+ MenuPrimitive.Positioner.Props,
+ "align" | "alignOffset" | "side" | "sideOffset"
+ >) {
+ return (
+
+
+
+
+
+ );
+}
+
+function DropdownMenuGroup({ ...props }: MenuPrimitive.Group.Props) {
+ return ;
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: MenuPrimitive.GroupLabel.Props & {
+ inset?: boolean;
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: MenuPrimitive.Item.Props & {
+ inset?: boolean;
+ variant?: "default" | "destructive";
+}) {
+ return (
+
+ );
+}
+
+function DropdownMenuSub({ ...props }: MenuPrimitive.SubmenuRoot.Props) {
+ return ;
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: MenuPrimitive.SubmenuTrigger.Props & {
+ inset?: boolean;
+}) {
+ return (
+
+ {children}
+
+
+ );
+}
+
+function DropdownMenuSubContent({
+ align = "start",
+ alignOffset = -3,
+ side = "right",
+ sideOffset = 0,
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ );
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: MenuPrimitive.CheckboxItem.Props) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuRadioGroup({ ...props }: MenuPrimitive.RadioGroup.Props) {
+ return (
+
+ );
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: MenuPrimitive.RadioItem.Props) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ );
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: MenuPrimitive.Separator.Props) {
+ return (
+
+ );
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ );
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+};
diff --git a/src/styles/globals.css b/src/styles/globals.css
index 80049c6..60922de 100644
--- a/src/styles/globals.css
+++ b/src/styles/globals.css
@@ -56,18 +56,18 @@
--card-foreground: oklch(0.147 0.004 49.25);
--popover: oklch(1 0 0);
--popover-foreground: oklch(0.147 0.004 49.25);
- --primary: oklch(0.216 0.006 56.043);
+ --primary: oklch(0.45 0.08 240);
--primary-foreground: oklch(0.985 0.001 106.423);
--secondary: oklch(0.97 0.001 106.424);
--secondary-foreground: oklch(0.216 0.006 56.043);
--muted: oklch(0.97 0.001 106.424);
--muted-foreground: oklch(0.553 0.013 58.071);
- --accent: oklch(0.97 0.001 106.424);
- --accent-foreground: oklch(0.216 0.006 56.043);
+ --accent: oklch(0.72 0.14 65);
+ --accent-foreground: oklch(0.147 0.004 49.25);
--destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.923 0.003 48.717);
--input: oklch(0.923 0.003 48.717);
- --ring: oklch(0.709 0.01 56.259);
+ --ring: oklch(0.45 0.08 240);
--chart-1: oklch(0.646 0.222 41.116);
--chart-2: oklch(0.6 0.118 184.704);
--chart-3: oklch(0.398 0.07 227.392);
@@ -75,12 +75,12 @@
--chart-5: oklch(0.769 0.188 70.08);
--sidebar: oklch(0.985 0.001 106.423);
--sidebar-foreground: oklch(0.147 0.004 49.25);
- --sidebar-primary: oklch(0.216 0.006 56.043);
+ --sidebar-primary: oklch(0.45 0.08 240);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.97 0.001 106.424);
--sidebar-accent-foreground: oklch(0.216 0.006 56.043);
--sidebar-border: oklch(0.923 0.003 48.717);
- --sidebar-ring: oklch(0.709 0.01 56.259);
+ --sidebar-ring: oklch(0.45 0.08 240);
--background: oklch(1 0 0);
--foreground: oklch(0.147 0.004 49.25);
}
@@ -92,18 +92,18 @@
--card-foreground: oklch(0.985 0.001 106.423);
--popover: oklch(0.216 0.006 56.043);
--popover-foreground: oklch(0.985 0.001 106.423);
- --primary: oklch(0.923 0.003 48.717);
- --primary-foreground: oklch(0.216 0.006 56.043);
+ --primary: oklch(0.55 0.1 240);
+ --primary-foreground: oklch(0.985 0.001 106.423);
--secondary: oklch(0.268 0.007 34.298);
--secondary-foreground: oklch(0.985 0.001 106.423);
--muted: oklch(0.268 0.007 34.298);
--muted-foreground: oklch(0.709 0.01 56.259);
- --accent: oklch(0.268 0.007 34.298);
- --accent-foreground: oklch(0.985 0.001 106.423);
+ --accent: oklch(0.75 0.16 65);
+ --accent-foreground: oklch(0.147 0.004 49.25);
--destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
- --ring: oklch(0.553 0.013 58.071);
+ --ring: oklch(0.55 0.1 240);
--chart-1: oklch(0.488 0.243 264.376);
--chart-2: oklch(0.696 0.17 162.48);
--chart-3: oklch(0.769 0.188 70.08);
@@ -111,12 +111,12 @@
--chart-5: oklch(0.645 0.246 16.439);
--sidebar: oklch(0.216 0.006 56.043);
--sidebar-foreground: oklch(0.985 0.001 106.423);
- --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary: oklch(0.55 0.1 240);
--sidebar-primary-foreground: oklch(0.985 0.001 106.423);
--sidebar-accent: oklch(0.268 0.007 34.298);
--sidebar-accent-foreground: oklch(0.985 0.001 106.423);
--sidebar-border: oklch(1 0 0 / 10%);
- --sidebar-ring: oklch(0.553 0.013 58.071);
+ --sidebar-ring: oklch(0.55 0.1 240);
}
@layer base {
@@ -137,4 +137,58 @@
.hide-scrollbar::-webkit-scrollbar {
display: none; /* Chrome, Safari and Opera */
}
+
+ /* Speech bubble styles */
+ .speech-bubble {
+ position: relative;
+ display: inline-block;
+ width: fit-content;
+ background: var(--color-accent);
+ border-radius: var(--radius-lg);
+ padding: 0.5rem 1rem;
+ white-space: nowrap;
+ }
+
+ .speech-bubble::after {
+ content: "";
+ position: absolute;
+ bottom: -8px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 0;
+ height: 0;
+ border-left: 8px solid transparent;
+ border-right: 8px solid transparent;
+ border-top: 8px solid var(--color-accent);
+ }
+
+ /* Wobble animation */
+ @keyframes wobble {
+ 0%,
+ 100% {
+ transform: rotate(0deg);
+ }
+ 25% {
+ transform: rotate(-2deg);
+ }
+ 75% {
+ transform: rotate(2deg);
+ }
+ }
+
+ .animate-wobble {
+ animation: wobble 0.5s ease-in-out;
+ }
+
+ .hover-wobble:hover {
+ animation: wobble 0.5s ease-in-out;
+ }
+
+ /* Respect prefers-reduced-motion */
+ @media (prefers-reduced-motion: reduce) {
+ .animate-wobble,
+ .hover-wobble:hover {
+ animation: none;
+ }
+ }
}