From b55a4b6dd26a5aa4776da3e798ec6ae0d5ebf39c Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 2 Dec 2025 03:34:48 +0400 Subject: [PATCH 1/7] docs: tidy docs structure - Remove project-setup.md (template-specific, not needed in derived projects) - Move quick_notes.md up from own/ subdirectory Simplifies documentation hierarchy for projects created from this template. --- x_docs/project-setup.md | 135 -------------------------------- x_docs/{own => }/quick_notes.md | 0 2 files changed, 135 deletions(-) delete mode 100644 x_docs/project-setup.md rename x_docs/{own => }/quick_notes.md (100%) diff --git a/x_docs/project-setup.md b/x_docs/project-setup.md deleted file mode 100644 index af20dbb..0000000 --- a/x_docs/project-setup.md +++ /dev/null @@ -1,135 +0,0 @@ -# πŸ› οΈ Project Setup Steps - -This guide documents the manual configuration required after creating a repo from the `nextjs-base` template. GitHub settings, secrets, and Vercel connections don't transfer with templates. - -## πŸ“¦ Part 1: Clone `nextjs-base` repo - -Assuming the new project is called "my_proj", run: - -```bash -gh repo create my_proj --template michellepace/nextjs-base --clone --public -``` - -What does this do? - -- Creates `my_proj` repo on GitHub (from the template) -- Clones the repo locally to your machine in folder my_proj -- Initial commit with template in it - -## πŸš€ Part 2: Vercel Setup - -### 2.1 Create Vercel Project - -1. Go to [vercel.com/new](https://vercel.com/new) -2. Import the `my_proj` repository (default settings) -3. Click **Deploy** β†’ wait for deploy -4. Click **Continue to Dashboard**: enable Analytics and Speed Insights - -### 2.2 Set Up E2E Tests on Vercel Previews - -This allows Playwright tests to run against Vercel preview deployments. - -**Step A: Create bypass secret in Vercel** - -1. Vercel β†’ Project β†’ **Settings** β†’ **Deployment Protection** -2. Scroll to **Protection Bypass for Automation** -3. Click **Add Secret** and copy the secret name and value - -**Step B: Add secret to GitHub** - -1. GitHub Repo β†’ **Settings** β†’ **Secrets and variables** β†’ **Actions** -2. Click **New repository secret** β†’ Add secret name and value from Vercel - -### 2.3 Link Vercel CLI (optional) - -Install the [Vercel CLI](https://vercel.com/docs/cli) globally and link it to your project: - -```bash - -npm i -g vercel # Install vercel globally -vercel --version # See installed version - -vercel link # Link project (creates .vercel/) -vercel list # See deployments for linked project -``` - ---- - -## πŸ“¦ Part 3: GitHub Repository Settings - -Go to **Settings** β†’ **General** on your GitHub repo. - -- [ ] **Description**: Add a project description -- [ ] **Delete head branches**: βœ… Enable "Automatically delete head branches" -- [ ] **Features** (optional): Disable Wikis/Issues/Projects if not needed - -The template includes `.github/dependabot.yml` which automatically enables **version updates** (weekly PRs for npm and GitHub Actions). However, **security features** require manual enablement. - -Go to **Settings** β†’ **Advanced Security** β†’ **Dependabot** - -- [ ] **Grouped security updates**: βœ… Enable (auto-PRs to fix vulnerabilities) - ---- - -## πŸ§ͺ Part 4: Trigger Initial Workflows - -Create a test PR to trigger all workflows. This ensures status check names exist in GitHub before configuring branch protection. - -1. Create a branch, make a small change (e.g., edit README), push, open PR -2. Wait for all workflows to complete: - - `Run Lint & Type Checks` - - `Run Unit Tests` - - `Run E2E Tests` - - `Vercel` (preview deployment) - - `Run E2E Tests on Preview` -3. Vercel β†’ project overview β†’ click "Deployment" URL to verify preview -4. GitHub β†’ Merge the pull request -5. Vercel β†’ project overview β†’ click "Domains" URL to verify production - ---- - -## πŸ”’ Part 5: Branch Protection Ruleset - -Now that all workflows have run, their check names are available in GitHub. - -Go to **Settings** β†’ **Rules** β†’ **Rulesets** β†’ **New ruleset** β†’ **New branch ruleset** - -Configure as follows: - -| Setting | Value | -|---------|-------| -| Ruleset name | `Protect main branch` | -| Enforcement status | `Active` | -| Target branches | Add target β†’ Include default branch (i.e. main) | - -**Rules to enable:** - -- [ ] βœ… **Restrict deletions** -- [ ] βœ… **Require a pull request before merging** - - Allowed merge methods: `Merge` only (uncheck Squash/Rebase if preferred) -- [ ] βœ… **Require status checks to pass** - - βœ… Require branches to be up-to-date before merging - - βœ… Do not require status checks on creation - - Add these required checks (search by name): - - `Run Lint & Type Checks` - - `Run Unit Tests` - - `Run E2E Tests` - - `Vercel` (Preview deployment must succeed) - - `Run E2E Tests on Preview` -- [ ] βœ… **Block force pushes** - -Click **Create** to save. - ---- - -## 🐰 Part 6: CodeRabbit AI Review (Optional) - -CodeRabbit provides AI-powered code review on pull requests. - -1. Go to [coderabbit.ai](https://coderabbit.ai) -2. Connect your GitHub account -3. Enable for this repository - -Run the `/coderabbit` slash command to evaluate and action specific comments. - ---- diff --git a/x_docs/own/quick_notes.md b/x_docs/quick_notes.md similarity index 100% rename from x_docs/own/quick_notes.md rename to x_docs/quick_notes.md From 461f66e8baba5fafd3bea23888b7c2661578586f Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 2 Dec 2025 03:35:30 +0400 Subject: [PATCH 2/7] rules: add merge-cleanup command and update docs prefix MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Claude Commands: - Add /merge-cleanup slash command for post-PR branch housekeeping - Include x_docs/**/*.md in docs prefix examples The merge-cleanup command automates the repetitive workflow after merging a PR: switching to main, pulling, deleting the merged branch, and pruning remotes. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- .claude/commands/commit.md | 2 +- .claude/commands/merge-cleanup.md | 22 ++++++++++++++++++++++ 2 files changed, 23 insertions(+), 1 deletion(-) create mode 100644 .claude/commands/merge-cleanup.md 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. From 440462572783eddaf353f7491cc6d765b3345a04 Mon Sep 17 00:00:00 2001 From: Michelle Date: Tue, 2 Dec 2025 03:43:41 +0400 Subject: [PATCH 3/7] chore: add legacy CSS file for theming comparison MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reference Files: - Add globals-old.css showing Tailwind v3 utility class patterns - Add globals-compare.md documenting differences between old and modern approaches - Exclude old CSS from Biome linting (intentionally contains legacy patterns) Documents the contrast between composite utility classes and modern CSS variable patterns for Tailwind v4 theming. Useful for understanding migration paths. πŸ€– Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- app/globals-old.css | 399 ++++++++++++++ biome.json | 3 +- x_docs/own/globals-compare.md | 983 ++++++++++++++++++++++++++++++++++ 3 files changed, 1384 insertions(+), 1 deletion(-) create mode 100644 app/globals-old.css create mode 100644 x_docs/own/globals-compare.md 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/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/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 (` - -
- - - ); -} +export default Home; 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/); });