diff --git a/.claude/commands/commit.md b/.claude/commands/commit.md index d66761c..f9fb5ee 100644 --- a/.claude/commands/commit.md +++ b/.claude/commands/commit.md @@ -44,7 +44,7 @@ description: Create git commit for staged changes using template - `refactor:` code changes that neither fix bugs nor add features - `style:` code formatting, visual consistency, linting fixes; no functional change - `chore:` dev workflow, workspace config, dependency updates, dev tools e.g. `.vscode/**/*`, `pyproject.toml`, `.gitignore` -- `docs:` documentation changes only e.g. `README.md`, `docs/**/*.md` +- `docs:` documentation changes only e.g. `README.md`, `docs/**/*.md`, `x_docs/**/*.md` - `feature:` new feature for users (adds functionality) diff --git a/.claude/commands/merge-cleanup.md b/.claude/commands/merge-cleanup.md new file mode 100644 index 0000000..8de79e3 --- /dev/null +++ b/.claude/commands/merge-cleanup.md @@ -0,0 +1,22 @@ +--- +description: Post-merge cleanup: switch to main, pull, delete merged branch, prune +--- + +## Context + +- Current branch: !`git branch --show-current` +- All local branches: !`git branch` +- Remote branches: !`git branch -r` + +## Task + +Michelle has merged her PR and wants to clean up. Please: + +1. Switch to main branch +2. Pull latest changes +3. Delete the previous branch (the one shown above that is not main) +4. Prune stale remote-tracking references: `git fetch --prune` +5. Check both Git and GitHub to ensure clean status +6. Create narrow summary table with emojies to show everything is clean + +If already on main with no other branches, just confirm everything is clean. diff --git a/.claude/settings.json b/.claude/settings.json index 3ff6747..25b42b8 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -8,6 +8,7 @@ "mcp__playwright__browser_close", "mcp__playwright__browser_console_messages", + "mcp__playwright__browser_evaluate", "mcp__playwright__browser_navigate", "mcp__playwright__browser_navigate_back", "mcp__playwright__browser_network_requests", diff --git a/app/favicon.ico b/app/favicon.ico index 718d6fe..ad21ce7 100644 Binary files a/app/favicon.ico and b/app/favicon.ico differ diff --git a/app/fonts.ts b/app/fonts.ts index 3eb6faf..37da715 100644 --- a/app/fonts.ts +++ b/app/fonts.ts @@ -1,17 +1,20 @@ -import { JetBrains_Mono, Lora, Montserrat } from "next/font/google"; +import { Inter, JetBrains_Mono, Space_Grotesk } from "next/font/google"; -export const montserrat = Montserrat({ - variable: "--font-montserrat", +// Variable font (weights 300–700) — for headings/display +export const spaceGrotesk = Space_Grotesk({ + variable: "--font-space-grotesk", subsets: ["latin"], display: "swap", }); -export const lora = Lora({ - variable: "--font-lora", +// Variable font (weights 100–900) — for body text +export const inter = Inter({ + variable: "--font-inter", subsets: ["latin"], display: "swap", }); +// Variable font (weights 100–800) — for code export const jetbrainsMono = JetBrains_Mono({ variable: "--font-jetbrains-mono", subsets: ["latin"], diff --git a/app/globals-old.css b/app/globals-old.css new file mode 100644 index 0000000..5a27b19 --- /dev/null +++ b/app/globals-old.css @@ -0,0 +1,399 @@ +@import "tailwindcss"; + +body { + font-family: "Inter", sans-serif; +} + +:root { + --radius: 8px; +} + +@layer utilities { + .background-light850_dark100 { + @apply bg-light-850 dark:bg-dark-100; + } + + .background-light900_dark200 { + @apply bg-light-900 dark:bg-dark-200; + } + + .background-light900_dark300 { + @apply bg-light-900 dark:bg-dark-300; + } + + .background-light800_darkgradient { + @apply bg-light-800 dark:dark-gradient; + } + + .background-light800_dark400 { + @apply bg-light-800 dark:bg-dark-400 !important; + } + + .background-light700_dark400 { + @apply bg-light-700 dark:bg-dark-400; + } + + .background-light700_dark300 { + @apply bg-light-700 dark:bg-dark-300; + } + + .background-light800_dark400 { + @apply bg-light-800 dark:bg-dark-400; + } + + .background-light800_dark300 { + @apply bg-light-800 dark:bg-dark-300 !important; + } + + .background-light800_dark200 { + @apply bg-light-800 dark:bg-dark-200; + } + + .background-dark400_light900 { + @apply dark:bg-dark-400 bg-light-900 !important; + } + + .text-dark100_light900 { + @apply text-dark-100 dark:text-light-900 !important; + } + + .text-dark200_light900 { + @apply text-dark-200 dark:text-light-900; + } + + .text-dark200_light800 { + @apply text-dark-200 dark:text-light-800 !important; + } + + .text-dark300_light700 { + @apply text-dark-300 dark:text-light-700; + } + + .text-dark400_light700 { + @apply text-dark-400 dark:text-light-700; + } + + .text-dark500_light700 { + @apply text-dark-500 dark:text-light-700 !important; + } + + .text-dark500_light500 { + @apply text-dark-500 dark:text-light-500; + } + + .text-dark500_light400 { + @apply text-dark-500 dark:text-light-400; + } + + .text-dark300_light900 { + @apply text-dark-300 dark:text-light-900 !important; + } + + .text-dark400_light800 { + @apply text-dark-400 dark:text-light-800; + } + + .text-light400_light500 { + @apply text-light-400 dark:text-light-500 !important; + } + + .text-dark400_light500 { + @apply text-dark-400 dark:text-light-500; + } + + .text-dark400_light900 { + @apply text-dark-400 dark:text-light-900 !important; + } + + .text-light400_light500 { + @apply text-light-400 dark:text-light-500 !important; + } + + .light-border { + @apply border-light-800 dark:border-dark-300; + } + + .light-border-2 { + @apply border-light-700 dark:border-dark-400 !important; + } + + .h1-bold { + @apply text-[30px] font-bold leading-[42px] tracking-tighter; + } + + .h2-bold { + @apply text-[24px] font-bold leading-[31.2px]; + } + + .h2-semibold { + @apply text-[24px] font-semibold leading-[31.2px]; + } + + .h3-bold { + @apply text-[20px] font-bold leading-[26px]; + } + + .h3-semibold { + @apply text-[20px] font-semibold leading-[24.8px]; + } + + .base-medium { + @apply text-[18px] font-medium leading-[25.2px]; + } + + .base-semibold { + @apply text-[18px] font-semibold leading-[25.2px]; + } + + .base-bold { + @apply text-[18px] font-bold leading-[140%]; + } + + .paragraph-regular { + @apply text-[16px] font-normal leading-[22.4px]; + } + + .paragraph-medium { + @apply text-[16px] font-medium leading-[22.4px]; + } + + .paragraph-semibold { + @apply text-[16px] font-semibold leading-[20.8px]; + } + + .body-regular { + @apply text-[14px] font-normal leading-[19.6px]; + } + + .body-medium { + @apply text-[14px] font-medium leading-[18.2px]; + } + + .body-semibold { + @apply text-[14px] font-semibold leading-[18.2px]; + } + + .body-bold { + @apply text-[14px] font-bold leading-[18.2px]; + } + + .small-regular { + @apply text-[12px] font-normal leading-[15.6px]; + } + + .small-medium { + @apply text-[12px] font-medium leading-[15.6px]; + } + + .small-semibold { + @apply text-[12px] font-semibold leading-[15.6px]; + } + + .subtle-medium { + @apply text-[10px] font-medium leading-[13px] !important; + } + + .subtle-regular { + @apply text-[10px] font-normal leading-[13px]; + } + + .placeholder { + @apply placeholder:text-light-400 dark:placeholder:text-light-500; + } + + .invert-colors { + @apply invert dark:invert-0; + } + + .shadow-light100_dark100 { + @apply shadow-light-100 dark:shadow-dark-100; + } + + .shadow-light100_darknone { + @apply shadow-light-100 dark:shadow-none; + } + + .primary-gradient { + background: linear-gradient(129deg, #ff7000 0%, #e2995f 100%); + } + + .dark-gradient { + background: linear-gradient( + 232deg, + rgba(23, 28, 35, 0.41) 0%, + rgba(19, 22, 28, 0.7) 100% + ); + } + + .light-gradient { + background: linear-gradient( + 132deg, + rgba(247, 249, 255, 0.5) 0%, + rgba(229, 237, 255, 0.25) 100% + ); + } + + .primary-text-gradient { + background: linear-gradient(129deg, #ff7000 0%, #e2995f 100%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + } + + .flex-center { + @apply flex justify-center items-center; + } + + .flex-between { + @apply flex justify-between items-center; + } + + .flex-start { + @apply flex justify-start items-center; + } + + .card-wrapper { + @apply bg-light-900 dark:dark-gradient shadow-light-100 dark:shadow-dark-100; + } + + .btn { + @apply bg-light-800 dark:bg-dark-300 !important; + } + + .btn-secondary { + @apply bg-light-800 dark:bg-dark-400 !important; + } + + .btn-tertiary { + @apply bg-light-700 dark:bg-dark-300 !important; + } + + .no-focus { + @apply focus-visible:ring-0 focus-visible:ring-transparent focus-visible:ring-offset-0 !important; + } + + .markdown { + @apply max-w-full prose dark:prose-p:text-light-700 dark:prose-ol:text-light-700 dark:prose-ul:text-light-500 dark:prose-strong:text-white dark:prose-headings:text-white prose-headings:text-dark-400 prose-h1:text-dark-300 prose-h2:text-dark-300 prose-p:text-dark-500 prose-ul:text-dark-500 prose-ol:text-dark-500; + } + + .markdown-editor { + @apply prose max-w-full prose-p:m-0 dark:prose-headings:text-white prose-headings:text-dark-400 prose-p:text-dark-500 dark:prose-p:text-light-700 prose-ul:text-dark-500 dark:prose-ul:text-light-700 prose-ol:text-dark-500 dark:prose-ol:text-light-700 dark:prose-strong:text-white prose-blockquote:text-dark-500 dark:prose-blockquote:text-light-700; + } + + .tab { + @apply min-h-full dark:bg-dark-400 bg-light-800 text-light-500 dark:data-[state=active]:bg-dark-300 data-[state=active]:bg-primary-100 data-[state=active]:text-primary-500 !important; + } + + .dark-gradient { + background: linear-gradient( + 232deg, + rgba(23, 28, 35, 0.41) 0%, + rgba(19, 22, 28, 0.7) 100% + ); + } +} + +.custom-scrollbar::-webkit-scrollbar { + width: 3px; + height: 3px; + border-radius: 2px; +} + +.custom-scrollbar::-webkit-scrollbar-track { + background: #ffffff; +} + +.custom-scrollbar::-webkit-scrollbar-thumb { + background: #888; + border-radius: 50px; +} + +.custom-scrollbar::-webkit-scrollbar-thumb:hover { + background: #555; +} + +/* Hide scrollbar for Chrome, Safari and Opera */ +.no-scrollbar::-webkit-scrollbar { + display: none; +} + +/* Hide scrollbar for IE, Edge and Firefox */ +.no-scrollbar { + -ms-overflow-style: none; /* IE and Edge */ + scrollbar-width: none; /* Firefox */ +} + +.active-theme { + filter: invert(53%) sepia(98%) saturate(3332%) hue-rotate(0deg) + brightness(104%) contrast(106%) !important; +} + +.hash-span { + margin-top: -140px; + padding-bottom: 140px; + display: block; +} + +.mdxeditor-toolbar { + background: #ffffff !important; +} + +.dark .mdxeditor-toolbar { + background: #151821 !important; +} + +.dark .mdxeditor-toolbar button svg { + color: #858ead !important; +} + +.dark .mdxeditor-toolbar button:hover svg { + color: #000 !important; +} + +.dark .mdxeditor-toolbar [role="separator"] { + border-color: #555 !important; +} + +.markdown a { + color: #1da1f2; +} + +.markdown a, +code { + /* These are technically the same, but use both */ + overflow-wrap: break-word; + word-wrap: break-word; + + -ms-word-break: break-all; + /* This is the dangerous one in WebKit, as it breaks things wherever */ + word-break: break-all; + /* Instead use this non-standard one: */ + word-break: break-word; + + /* Adds a hyphen where the word breaks, if supported (No Blink) */ + -ms-hyphens: auto; + -moz-hyphens: auto; + -webkit-hyphens: auto; + hyphens: auto; + + padding: 2px; + color: #ff7000 !important; +} + +.markdown pre { + display: grid; + width: 100%; +} + +.markdown pre code { + width: 100%; + display: block; + overflow-x: auto; + + color: inherit !important; +} + +[data-lexical-editor="true"] { + height: 350px !important; + overflow-y: auto !important; +} \ No newline at end of file diff --git a/app/globals.css b/app/globals.css index 3e2dec2..cc9c323 100644 --- a/app/globals.css +++ b/app/globals.css @@ -85,9 +85,10 @@ --color-background: var(--background); --color-foreground: var(--foreground); - --font-display: var(--font-montserrat), Arial, sans-serif; - --font-serif: var(--font-lora), Georgia, serif; - --font-mono: var(--font-jetbrains-mono), "Courier New", monospace; + --font-display: var(--font-space-grotesk), Arial, sans-serif; + --font-sans-serif: var(--font-inter), ui-sans-serif, system-ui, sans-serif; + --font-mono: + var(--font-jetbrains-mono), ui-monospace, Menlo, Consolas, monospace; --color-sidebar-ring: var(--sidebar-ring); --color-sidebar-border: var(--sidebar-border); @@ -142,7 +143,7 @@ /* TYPOGRAPHY */ html { - font-family: var(--font-serif); + font-family: var(--font-sans-serif); } h1 { @apply text-5xl font-bold font-display; diff --git a/app/layout.tsx b/app/layout.tsx index 1cf339e..2b7d4e8 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,13 +1,14 @@ import { Analytics } from "@vercel/analytics/next"; import { SpeedInsights } from "@vercel/speed-insights/next"; import type { Metadata } from "next"; -import "./globals.css"; -import { jetbrainsMono, lora, montserrat } from "@/app/fonts"; +import "@/app/globals.css"; +import { inter, jetbrainsMono, spaceGrotesk } from "@/app/fonts"; import { ThemeProvider } from "@/components/theme-provider"; export const metadata: Metadata = { - title: "Create Next App", - description: "Generated by create next app", + title: "Devflow", + description: + "A community-driven platform for asking and answering programming questions. Get help, share knowledge, and collaborate with developers from around the world. Explore topics in web development, mobile app development, algorithms, data structures, and more.", }; export default function RootLayout({ @@ -19,16 +20,16 @@ export default function RootLayout({ - + -
{children}
+ {children}
diff --git a/app/page.tsx b/app/page.tsx index 09829fc..04764ac 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,121 +1,7 @@ -import Image from "next/image"; -import { Button } from "@/components/button"; -import { ThemeToggle } from "@/components/theme-toggle"; +const Home = () => ( + <> +

Welcome to the world of Next.js

+ +); -export default function Home() { - return ( -
-
- Next.js logo - -
- -
-

- Choose a UI Library -

- -

- This template still needs a UI component library. Choose one like{" "} - - shadcn/ui - {" "} - or{" "} - - Tailwind Plus - - . The theme in globals.css uses shadcn's semantic - colour naming convention. Your choice affects how to proceed. -

- -

shadcn/ui

-

- You're ready to go. The colour variables in{" "} - globals.css already match shadcn's naming ( - --background, --foreground,{" "} - --primary, --muted, etc.), so installed - components inherit your theme automatically. To customise colours, - change the values in :root and .dark - —all components update instantly. -

- -

Tailwind Plus

-

- Tailwind Plus includes UI Kit (Catalyst) and UI Blocks. The UI Kit - typically goes into components/ui, whilst blocks go into{" "} - page.tsx or components/sections/. Both use - Tailwind's default palette (zinc, gray,{" "} - indigo, etc.) rather than semantic names. -

-

- To theme them, either find-replace palette classes with semantic ones - (bg-zinc-900bg-background - ), or override the palette in @theme - —for example, --color-zinc-900: oklch(...). Note that - Blocks are often light-mode only. -

- -

- With either library, colours remain centralised. The{" "} - @theme override method requires no code changes; - find-replace gives full control over naming but requires editing - files. -

- -

Advanced: Palette Remapping

-

- Instead of directly overriding --color-zinc-900, you can - define your brand palette first, then remap Tailwind's colours to - it: -

-
-          {`@theme {
-  --color-brand-600: oklch(0.51 0.24 280);
-}
-:root {
-  --color-indigo-600: var(--color-brand-600);
-  --color-blue-600: var(--color-brand-600);  /* both now use your brand */
-}`}
-        
-

- Benefits: define colours once with meaningful names, remap multiple - Tailwind palettes to the same brand colour, clearer separation between - your palette and Tailwind's naming. -

- -
- - -
-
-
- ); -} +export default Home; diff --git a/biome.json b/biome.json index cf56d75..fcde559 100644 --- a/biome.json +++ b/biome.json @@ -14,7 +14,8 @@ "!.next", "!dist", "!build", - "!x_docs/reference" + "!x_docs/reference", + "!app/globals-old.css" ] }, "css": { diff --git a/e2e/homepage.spec.ts b/e2e/homepage.spec.ts index 8315648..21cc7f1 100644 --- a/e2e/homepage.spec.ts +++ b/e2e/homepage.spec.ts @@ -2,5 +2,5 @@ import { expect, test } from "@playwright/test"; test("homepage loads successfully", async ({ page }) => { await page.goto("/"); - await expect(page).toHaveTitle(/Next/); + await expect(page).toHaveTitle(/Devflow/); }); diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100755 index 0000000..ad21ce7 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/social-card.jpg b/public/social-card.jpg deleted file mode 100755 index 35763f0..0000000 Binary files a/public/social-card.jpg and /dev/null differ diff --git a/x_docs/own/globals-compare.md b/x_docs/own/globals-compare.md new file mode 100644 index 0000000..352a2ad --- /dev/null +++ b/x_docs/own/globals-compare.md @@ -0,0 +1,983 @@ +# Tailwind Theming Comparison: `globals-old.css` vs My Modern Approach `globals.css` + +This document compares the light/dark mode approach from an old file (`globals-old.css`) with the modern Tailwind v4 approach (`globals.css`), and categorises all configuration patterns with explanations. + +--- + +## Light/Dark Mode Comparison + +| Aspect | Old File (globals-old.css) | Your File (globals.css) | +|--------|-------------------------------|-------------------------| +| **Approach** | Composite utility classes with `dark:` variant | CSS custom properties + `@theme inline` | +| **Colour Format** | Named scales (light-850, dark-100) | OKLCH (perceptually uniform) | +| **Theme Switching** | Class-based via Tailwind `dark:` | CSS variables that swap at runtime | +| **Naming** | Arbitrary (light-850, dark-400) | Semantic (primary, muted, accent) | +| **Tailwind Version** | v3 patterns | v4 native patterns | + +### Verdict: The modern globals.css approach is significantly better + +**Why the modern approach wins:** + +1. **Runtime theme switching** — CSS variables can be changed via JavaScript without recompiling CSS. This enables features like system preference detection, user preference persistence, and instant theme toggling. + +2. **OKLCH colour space** — A modern, perceptually uniform colour space. Unlike HSL or RGB, OKLCH ensures that colours with the same lightness value actually *appear* equally light to human eyes. This produces better gradients and more accessible colour combinations. + +3. **Semantic naming** — `bg-primary` communicates intent; `bg-light-850` is meaningless without context. Semantic names make code self-documenting and easier to maintain. + +4. **shadcn/ui compatible** — The CSS variable pattern is the standard for shadcn/ui components, Radix UI, and most modern React component libraries. Your setup works out of the box. + +5. **DRY (Don't Repeat Yourself)** — Change one variable in `:root` or `.dark`, and all usages update automatically. The old file has 50+ hardcoded light/dark combinations that must be updated individually. + +6. **No `!important` spam** — The old file uses `!important` 20+ times, which is a code smell indicating specificity problems. The modern approach avoids this entirely through proper CSS layering. + +--- + +## Old File Categories Explained + +The old file contains various utility patterns. Below, each category is explained with context on what it does, why it exists, and what you would need in a modern setup. + +--- + +### 1. Base Settings + +```css +body { font-family: "Inter", sans-serif; } +:root { --radius: 8px; } +``` + +**What it does:** +Sets the global font family and defines a CSS variable for border radius that can be used throughout the application. + +**Why it exists:** +Centralising design tokens like border radius allows for consistent UI and easy global changes. + +**What you'd need:** +Already handled in your file. Your setup is more sophisticated: + +- `--radius` is defined in `:root` and mapped via `@theme inline` to generate `rounded-sm`, `rounded-md`, `rounded-lg`, `rounded-xl` utilities +- Fonts are defined as CSS variables (`--font-display`, `--font-serif`, `--font-mono`) that integrate with Next.js font optimisation + +--- + +### 2. Background Theme Utilities (17 classes) + +```css +.background-light850_dark100 { @apply bg-light-850 dark:bg-dark-100; } +.background-light900_dark200 { @apply bg-light-900 dark:bg-dark-200; } +.background-light900_dark300 { @apply bg-light-900 dark:bg-dark-300; } +/* ... 14 more variations */ +``` + +**What it does:** +Pre-composed utility classes that apply different background colours depending on whether light or dark mode is active. The naming convention `light850_dark100` indicates "use light-850 in light mode, dark-100 in dark mode". + +**Why it exists:** +In Tailwind v3, this was a common pattern to avoid writing `bg-light-850 dark:bg-dark-100` repeatedly in JSX. It keeps component markup cleaner. + +**Problems with this approach:** + +- Creates dozens of single-purpose classes +- Arbitrary numbers (850, 100) have no semantic meaning +- Must create a new class for every light/dark combination needed +- Tightly couples colour values to class names + +**What you'd need:** +Nothing. Use semantic classes instead: + +```html + +
+ + +
+ +``` + +The modern approach uses semantic names that describe *purpose* (card, muted, secondary) rather than *appearance* (light-900, dark-200). + +--- + +### 3. Text Theme Utilities (16 classes) + +```css +.text-dark100_light900 { @apply text-dark-100 dark:text-light-900 !important; } +.text-dark200_light800 { @apply text-dark-200 dark:text-light-800 !important; } +.text-dark300_light700 { @apply text-dark-300 dark:text-light-700; } +/* ... 13 more variations */ +``` + +**What it does:** +Pre-composed text colour classes for light/dark mode. Note that many use `!important` to force specificity. + +**Why it exists:** +Same rationale as background utilities — reduces repetition in markup. + +**Problems with this approach:** + +- Heavy use of `!important` indicates specificity battles +- Arbitrary colour values make it hard to understand visual hierarchy +- No indication of what each colour combination is *for* + +**What you'd need:** +Nothing. Use semantic text colours: + +```html + +

Secondary text

+ + +

Secondary text

+``` + +Common semantic text colours in your setup: + +- `text-foreground` — Primary text +- `text-muted-foreground` — Secondary/subdued text +- `text-primary` — Brand/accent text +- `text-destructive` — Error/warning text + +--- + +### 4. Border Utilities + +```css +.light-border { @apply border-light-800 dark:border-dark-300; } +.light-border-2 { @apply border-light-700 dark:border-dark-400 !important; } +``` + +**What it does:** +Consistent border colours that adapt to light/dark mode. + +**Why it exists:** +Borders often need different opacity/colour in dark mode to remain visible without being too harsh. + +**What you'd need:** +Already handled. Your `@layer base` includes: + +```css +* { + @apply border-border outline-ring/50; +} +``` + +This sets a default border colour on all elements using the `--border` CSS variable, which automatically changes between light and dark mode. Simply use the `border` class: + +```html +
+``` + +--- + +### 5. Typography Scale (16 classes) + +```css +.h1-bold { @apply text-[30px] font-bold leading-[42px] tracking-tighter; } +.h2-bold { @apply text-[24px] font-bold leading-[31.2px]; } +.h2-semibold { @apply text-[24px] font-semibold leading-[31.2px]; } +.h3-bold { @apply text-[20px] font-bold leading-[26px]; } +.h3-semibold { @apply text-[20px] font-semibold leading-[24.8px]; } +.base-medium { @apply text-[18px] font-medium leading-[25.2px]; } +.base-semibold { @apply text-[18px] font-semibold leading-[25.2px]; } +.base-bold { @apply text-[18px] font-bold leading-[140%]; } +.paragraph-regular { @apply text-[16px] font-normal leading-[22.4px]; } +.paragraph-medium { @apply text-[16px] font-medium leading-[22.4px]; } +.paragraph-semibold { @apply text-[16px] font-semibold leading-[20.8px]; } +.body-regular { @apply text-[14px] font-normal leading-[19.6px]; } +.body-medium { @apply text-[14px] font-medium leading-[18.2px]; } +.body-semibold { @apply text-[14px] font-semibold leading-[18.2px]; } +.body-bold { @apply text-[14px] font-bold leading-[18.2px]; } +.small-regular { @apply text-[12px] font-normal leading-[15.6px]; } +.small-medium { @apply text-[12px] font-medium leading-[15.6px]; } +.small-semibold { @apply text-[12px] font-semibold leading-[15.6px]; } +.subtle-medium { @apply text-[10px] font-medium leading-[13px] !important; } +.subtle-regular { @apply text-[10px] font-normal leading-[13px]; } +``` + +**What it does:** +A comprehensive type scale combining font size, weight, and line height into single utility classes. The naming convention is `{size}-{weight}`. + +**Why it exists:** +Typography often requires coordinated changes to size, weight, and line-height. These utilities ensure consistent combinations across the app. + +**Analysis:** +This is actually a reasonable pattern, though the arbitrary pixel values (`text-[30px]`, `leading-[42px]`) could be replaced with Tailwind's built-in scale for better maintainability. + +**What you'd need:** +Your file already has base styles for `h1`–`h6` in `@layer base`. For additional utility combinations, you could add: + +```css +@layer utilities { + /* Using Tailwind's built-in scale instead of arbitrary values */ + .h1-bold { @apply text-5xl font-bold tracking-tight; } + .h2-bold { @apply text-4xl font-bold; } + .h2-semibold { @apply text-4xl font-semibold; } + .h3-bold { @apply text-3xl font-bold; } + + .body-medium { @apply text-sm font-medium; } + .body-semibold { @apply text-sm font-semibold; } + + .small-medium { @apply text-xs font-medium; } + .caption { @apply text-xs text-muted-foreground; } +} +``` + +Alternatively, just compose Tailwind utilities directly in your JSX — `text-sm font-medium` is clear and doesn't require custom classes. + +--- + +### 6. Placeholder Styles + +```css +.placeholder { @apply placeholder:text-light-400 dark:placeholder:text-light-500; } +``` + +**What it does:** +Sets consistent placeholder text colour in form inputs across light/dark modes. + +**Why it exists:** +Placeholder text should be visually distinct from actual input text, but still readable. The colour often needs adjustment in dark mode. + +**What you'd need:** +Add to your base layer for global application: + +```css +@layer base { + input::placeholder, + textarea::placeholder { + @apply text-muted-foreground; + } +} +``` + +Or apply per-component using Tailwind's placeholder modifier: `placeholder:text-muted-foreground`. + +--- + +### 7. Visual Effects + +```css +.invert-colors { @apply invert dark:invert-0; } +.shadow-light100_dark100 { @apply shadow-light-100 dark:shadow-dark-100; } +.shadow-light100_darknone { @apply shadow-light-100 dark:shadow-none; } +``` + +**What it does:** + +- `invert-colors`: Inverts an element's colours in light mode, restores in dark mode. Useful for black icons that need to be white in dark mode. +- Shadow utilities: Apply different shadow styles per theme. Shadows often look too harsh in dark mode and need to be softer or removed. + +**Why it exists:** +Icons and shadows frequently need theme-specific treatment that simple colour changes don't address. + +**What you'd need:** +If using icons that need inversion (e.g., black SVGs): + +```css +@layer utilities { + .invert-on-light { @apply invert dark:invert-0; } + .invert-on-dark { @apply dark:invert; } +} +``` + +For shadows, consider defining shadow values in your theme that work across modes, or use: + +```css +@layer utilities { + .shadow-theme { + @apply shadow-md dark:shadow-none dark:ring-1 dark:ring-border; + } +} +``` + +--- + +### 8. Gradients + +```css +.primary-gradient { + background: linear-gradient(129deg, #ff7000 0%, #e2995f 100%); +} + +.dark-gradient { + background: linear-gradient( + 232deg, + rgba(23, 28, 35, 0.41) 0%, + rgba(19, 22, 28, 0.7) 100% + ); +} + +.light-gradient { + background: linear-gradient( + 132deg, + rgba(247, 249, 255, 0.5) 0%, + rgba(229, 237, 255, 0.25) 100% + ); +} + +.primary-text-gradient { + background: linear-gradient(129deg, #ff7000 0%, #e2995f 100%); + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; +} +``` + +**What it does:** + +- `primary-gradient`: Brand gradient for buttons, CTAs, highlights (orange tones) +- `dark-gradient`: Subtle overlay gradient for dark mode cards/surfaces +- `light-gradient`: Subtle overlay gradient for light mode +- `primary-text-gradient`: Applies gradient as text colour (the background shows through transparent text) + +**Why it exists:** +Gradients add visual interest and depth. Brand gradients reinforce identity. The text gradient technique creates eye-catching headings. + +**What you'd need:** +First, define brand colours as CSS variables, then create gradient utilities: + +```css +:root { + --brand-orange: #ff7000; + --brand-gold: #e2995f; +} + +@layer utilities { + .bg-gradient-primary { + background: linear-gradient(135deg, var(--brand-orange) 0%, var(--brand-gold) 100%); + } + + .bg-gradient-surface { + @apply bg-gradient-to-br from-background to-muted/50; + } + + .text-gradient-primary { + background: linear-gradient(135deg, var(--brand-orange) 0%, var(--brand-gold) 100%); + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + background-clip: text; + } +} +``` + +--- + +### 9. Layout Utilities + +```css +.flex-center { @apply flex justify-center items-center; } +.flex-between { @apply flex justify-between items-center; } +.flex-start { @apply flex justify-start items-center; } +``` + +**What it does:** +Shorthand utilities for common flexbox patterns: + +- `flex-center`: Centre children both horizontally and vertically +- `flex-between`: Space children to opposite ends, vertically centred +- `flex-start`: Align children to start, vertically centred + +**Why it exists:** +These three-class combinations (`flex justify-center items-center`) are extremely common. Single utilities reduce markup verbosity. + +**What you'd need:** +These are genuinely useful shortcuts. Add if you find yourself writing these combinations frequently: + +```css +@layer utilities { + .flex-center { @apply flex items-center justify-center; } + .flex-between { @apply flex items-center justify-between; } + .flex-start { @apply flex items-center justify-start; } + .flex-end { @apply flex items-center justify-end; } + + /* Inline variant (horizontal only) */ + .inline-center { @apply inline-flex items-center justify-center; } +} +``` + +--- + +### 10. Component Utilities + +```css +.card-wrapper { + @apply bg-light-900 dark:dark-gradient shadow-light-100 dark:shadow-dark-100; +} + +.btn { @apply bg-light-800 dark:bg-dark-300 !important; } +.btn-secondary { @apply bg-light-800 dark:bg-dark-400 !important; } +.btn-tertiary { @apply bg-light-700 dark:bg-dark-300 !important; } + +.tab { + @apply min-h-full dark:bg-dark-400 bg-light-800 text-light-500 + dark:data-[state=active]:bg-dark-300 data-[state=active]:bg-primary-100 + data-[state=active]:text-primary-500 !important; +} +``` + +**What it does:** +Pre-styled component utilities for cards, buttons, and tabs. + +**Why it exists:** +When not using a component library, these utilities provide consistent component styling without writing full CSS classes for each component. + +**Problems with this approach:** + +- Heavy use of `!important` to override other styles +- Tightly coupled to specific colour values +- Doesn't scale well — you end up with `.btn`, `.btn-secondary`, `.btn-tertiary`, `.btn-ghost`, `.btn-outline`... +- State management (`:hover`, `:active`, `:disabled`) becomes complex + +**What you'd need:** +With shadcn/ui or similar component libraries, you get actual React components (`