Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
feat: right sidebar base with three-column layout by michellepace · Pull Request #23 · michellepace/devflow · GitHub
Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: right sidebar base with three-column layout by michellepace · Pull Request #23 · michellepace/devflow · GitHub
Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: right sidebar base with three-column layout by michellepace · Pull Request #23 · michellepace/devflow · GitHub
Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' feat: right sidebar base with three-column layout by michellepace · Pull Request #23 · michellepace/devflow · GitHub
Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: right sidebar base with three-column layout by michellepace · Pull Request #23 · michellepace/devflow · GitHub
Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' feat: right sidebar base with three-column layout by michellepace · Pull Request #23 · michellepace/devflow · GitHub
Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); feat: right sidebar base with three-column layout by michellepace · Pull Request #23 · michellepace/devflow · GitHub
Skip to content
Merged
1 change: 1 addition & 0 deletions .markdownlint.yaml
Original file line numberDiff line numberDiff line change
Expand Up@@ -9,3 +9,4 @@ MD036: false # Allow emphasis (bold/italic) without treating it as heading
MD040: false # Fenced code blocks don't need language specified
MD041: false # First line doesn't need to be h1
MD029: false # Allow any ordered list style (1/2/3 or continuous numbering)
MD060: false # Allow compact table style (no space before pipe)
2 changes: 1 addition & 1 deletion app/(root)/ask-question/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function AskQuestionPage() {
return <h1>Ask a Question</h1>;
return <h1 className="text-heading-2xl">Ask a Question</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/collections/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CollectionsPage() {
return <h1>Collections</h1>;
return <h1 className="text-heading-2xl">Collections</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/community/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function CommunityPage() {
return <h1>Community</h1>;
return <h1 className="text-heading-2xl">Community</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/jobs/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function JobsPage() {
return <h1>Find Jobs</h1>;
return <h1 className="text-heading-2xl">Find Jobs</h1>;
}
10 changes: 9 additions & 1 deletion app/(root)/layout.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -2,6 +2,7 @@ import { cookies } from "next/headers";
import { DesktopTopBar } from "@/components/navigation/desktop-top-bar";
import { LeftSidebar } from "@/components/navigation/left-sidebar";
import { MobileTopBar } from "@/components/navigation/mobile-top-bar";
import { RightSidebar } from "@/components/navigation/right-sidebar";
import { SidebarProvider } from "@/components/ui/sidebar";

const RootLayout = async ({ children }: { children: React.ReactNode }) => {
Expand All@@ -17,7 +18,14 @@ const RootLayout = async ({ children }: { children: React.ReactNode }) => {
<div className="flex min-h-screen flex-1 flex-col">
<MobileTopBar />
<DesktopTopBar />
<main className="flex-1 pb-10 pt-10 layout-padding-x">{children}</main>

{/* Content + Right Sidebar row */}
<div className="flex flex-1">
<main className="flex-1 pb-10 pt-10 layout-padding-x">
{children}
</main>
<RightSidebar />
</div>
</div>
</SidebarProvider>
);
Expand Down
4 changes: 2 additions & 2 deletions app/(root)/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
const HomePage = () => (
<>
<h1>Hello Root page with heading H1</h1>
<h1 className="text-heading-2xl">Hello Root page with heading H1</h1>

{/* Demo: top overflow test */}
<div className="mt-8 bg-primary/30 p-4">{"words ".repeat(50)}</div>
Expand DownExpand Up@@ -42,7 +42,7 @@ const HomePage = () => (
</span>
</div>

<h2 className="mb-2 text-xl font-semibold">
<h2 className="text-heading-sm mb-2">
How to implement a sticky sidebar in Next.js with Tailwind CSS?
</h2>

Expand Down
2 changes: 1 addition & 1 deletion app/(root)/profile/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function ProfilePage() {
return <h1>Profile</h1>;
return <h1 className="text-heading-2xl">Profile</h1>;
}
2 changes: 1 addition & 1 deletion app/(root)/tags/page.tsx
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
export default function TagsPage() {
return <h1>Tags</h1>;
return <h1 className="text-heading-2xl">Tags</h1>;
}
172 changes: 113 additions & 59 deletions app/globals.css
Original file line numberDiff line numberDiff line change
@@ -1,66 +1,81 @@
@layer theme, base, clerk, components, utilities; /* Must be at top */
@layer theme,
base,
clerk,
components,
utilities; /* Declare layer order before imports (adds "clerk" layer) */
@import "tailwindcss";
@import "tw-animate-css";
@import "@clerk/themes/shadcn.css";

/* Applies dark: styles when element is .dark or inside .dark */
@custom-variant dark (&:where(.dark, .dark *));

/* Registers theme tokens → generates utilities (bg-*, text-*, border-*, etc.).
"inline" embeds values directly, required when referencing other vars to avoid CSS scope issues. */
@theme inline {
/* Register theme tokens with Tailwind so it generates utilities (bg-*, text-*, etc.) */
/* E.g. --color-background → bg-background, text-background, border-background, etc. */
/* "inline" (v4) means values are embedded here, not in a separate CSS layer */
/* Fonts (no mode variants) */
--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, monospace;

/* Radius (derived from --radius) */
--radius-sm: calc(var(--radius) - 4px);
--radius-md: calc(var(--radius) - 2px);
--radius-lg: var(--radius);
--radius-xl: calc(var(--radius) + 4px);

/* shadcn/ui: Core */
--color-background: var(--background);
--color-foreground: var(--foreground);
--color-sidebar-ring: var(--sidebar-ring);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar: var(--sidebar);
--color-chart-5: var(--chart-5);
--color-chart-4: var(--chart-4);
--color-chart-3: var(--chart-3);
--color-chart-2: var(--chart-2);
--color-chart-1: var(--chart-1);
--color-ring: var(--ring);
--color-input: var(--input);
--color-border: var(--border);
--color-destructive: var(--destructive);
--color-accent-foreground: var(--accent-foreground);
--color-accent: var(--accent);
--color-muted-foreground: var(--muted-foreground);
--color-muted: var(--muted);
--color-secondary-foreground: var(--secondary-foreground);
--color-secondary: var(--secondary);
--color-primary-foreground: var(--primary-foreground);
--color-primary: var(--primary);
--color-popover-foreground: var(--popover-foreground);
--color-popover: var(--popover);
--color-card-foreground: var(--card-foreground);
--color-card: var(--card);
--color-card-foreground: var(--card-foreground);
--color-popover: var(--popover);
--color-popover-foreground: var(--popover-foreground);
--color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground);
--color-destructive: var(--destructive);
--color-border: var(--border);
--color-input: var(--input);
--color-ring: var(--ring);

/* shadcn/ui: Charts */
--color-chart-1: var(--chart-1);
--color-chart-2: var(--chart-2);
--color-chart-3: var(--chart-3);
--color-chart-4: var(--chart-4);
--color-chart-5: var(--chart-5);

/* NOT SHADCN VARIABLES */
/* shadcn/ui: Sidebar */
--color-sidebar: var(--sidebar);
--color-sidebar-foreground: var(--sidebar-foreground);
--color-sidebar-primary: var(--sidebar-primary);
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
--color-sidebar-accent: var(--sidebar-accent);
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
--color-sidebar-border: var(--sidebar-border);
--color-sidebar-ring: var(--sidebar-ring);

/* Custom */
--color-mobile-nav: var(--mobile-nav);
--color-overlay: var(--overlay);
}

/* Raw CSS variables (no Tailwind utilities generated). Access via var(--background), etc. */
:root {
/* Creates CSS variables available to all elements (no utility generation) */
/* Layout */
--top-bar-height: 4rem; /* 64px — desktop & mobile top navigation */

/* Base */
--radius: 0.625rem;

--background: oklch(0.994 0 0); /* #FDFDFD */
/* shadcn/ui: Core */
--background: oklch(0.99 0 0); /* #FDFDFD */
--foreground: oklch(0.129 0.042 264.695);
--card: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--card-foreground: oklch(0.129 0.042 264.695);
Expand All@@ -78,35 +93,47 @@
--border: oklch(0.929 0.013 255.508);
--input: oklch(0.929 0.013 255.508);
--ring: oklch(0.704 0.04 256.788);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.828 0.189 84.429);
--chart-5: oklch(0.769 0.188 70.08);

/* shadcn/ui: Sidebar */
--sidebar: oklch(1 0 0); /* Figma #ffffff */
--sidebar-foreground: oklch(0.2102 0.0185 270.39); /* #151821 */
--sidebar-primary: oklch(0.208 0.042 265.755);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.968 0.007 247.896);
--sidebar-accent-foreground: oklch(0.208 0.042 265.755);
--sidebar-border: oklch(0.929 0.013 255.508);
--sidebar-border: oklch(0.9722 0.0034 247.86); /* #F4F6F8 */
--sidebar-ring: oklch(0.704 0.04 256.788);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* #E2995F */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
);

/* Custom: Navigation */
--mobile-nav: oklch(1 0 0); /* #FFFFFF */
--overlay: oklch(0 0 0); /* black, both modes */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-light.svg");
--auth-bg: url("/images/auth-bg-light.webp");
}

/* Dark mode overrides — these values take precedence over :root when .dark is active */
.dark {
/* Overrides CSS variables when .dark class is present */
/* Base */
--radius: 0.625rem; /* same as root */

Comment thread
michellepace marked this conversation as resolved.
/* shadcn/ui: Core */
--background: oklch(0 0 0); /* #000000 */
--foreground: oklch(0.984 0.003 247.858);
--card: oklch(0.1783 0.0128 270.6); /* #0F1117 */
Expand All@@ -125,23 +152,37 @@
--border: oklch(1 0 0 / 10%);
--input: oklch(1 0 0 / 15%);
--ring: oklch(0.551 0.027 264.364);

/* shadcn/ui: Charts */
--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);
--chart-4: oklch(0.627 0.265 303.9);
--chart-5: oklch(0.645 0.246 16.439);

/* shadcn/ui: Sidebar */
--sidebar: oklch(0.1783 0.0128 270.6); /* Figma #0F1117 */
--sidebar-foreground: oklch(1 0 0); /* #FFFFFF */
--sidebar-primary: oklch(0.488 0.243 264.376);
--sidebar-primary-foreground: oklch(0.984 0.003 247.858);
--sidebar-accent: oklch(0.279 0.041 260.031);
--sidebar-accent-foreground: oklch(0.984 0.003 247.858);
--sidebar-border: oklch(1 0 0 / 10%);
--sidebar-border: transparent;
--sidebar-ring: oklch(0.551 0.027 264.364);

/* NOT SHADCN VARIABLES */
/* Custom: Gradients */
--primary-gradient-to: oklch(0.7434 0.115 58.23); /* same as root */
--gradient-primary: linear-gradient(
93.22deg,
var(--primary) -13.95%,
var(--primary-gradient-to) 99.54%
); /* same as root */

/* Custom: Navigation */
--mobile-nav: oklch(0.1288 0.0406 264.7); /* #07080b */
--overlay: oklch(0 0 0); /* black, both modes */
--overlay: oklch(0 0 0); /* same as root */

/* Custom: Assets (theme-aware URLs) */
--logo-full-themed: url("/images/logo-dark.svg");
--auth-bg: url("/images/auth-bg-dark.webp");
}
Expand All@@ -163,24 +204,6 @@
html {
font-family: var(--font-sans-serif);
}
h1 {
@apply text-5xl font-bold font-display;
}
h2 {
@apply text-4xl font-semibold font-display;
}
h3 {
@apply text-3xl font-semibold font-display;
}
h4 {
@apply text-2xl font-medium font-display;
}
h5 {
@apply text-xl font-medium font-display;
}
h6 {
@apply text-lg font-medium font-display;
}

:is(code, kbd, samp, pre) {
/* Code font family */
Expand All@@ -198,6 +221,29 @@
}
}

@layer components {
/* Heading utilities — apply visual heading styles independent of h1-h6 level.
Usage: <h2 class="text-heading-sm"> renders an h2 with small heading styling. */
.text-heading-2xl {
@apply text-5xl font-bold font-display; /* 48px */
}
.text-heading-xl {
@apply text-4xl font-semibold font-display; /* 36px */
}
.text-heading-lg {
@apply text-3xl font-semibold font-display; /* 30px */
}
.text-heading-md {
@apply text-2xl font-medium font-display; /* 24px */
}
.text-heading-sm {
@apply text-xl font-medium font-display; /* 20px */
}
.text-heading-xs {
@apply text-lg font-medium font-display; /* 18px */
}
}

@layer utilities {
/* Custom SVG icons use hardcoded white fill. Since <Image> loads them as external
files, CSS currentColor won't work. Invert to black in light mode instead. */
Expand All@@ -209,4 +255,12 @@
.layout-padding-x {
@apply px-6 md:px-8 lg:px-12;
}

/* Subtle directional shadow for navigation edges (light mode only) */
.shadow-light {
box-shadow: -10px 10px 20px 0px oklch(0.877 0.006 17.26 / 10%);
}
.dark .shadow-light {
box-shadow: none;
}
}
8 changes: 1 addition & 7 deletions components/navigation/desktop-top-bar.tsx
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,10 @@
import { SignedOut, SignInButton, SignUpButton } from "@clerk/nextjs";
import { ThemeToggle } from "@/components/navigation/theme-toggle";
import { Button } from "@/components/ui/button";
import { cn, TOP_BAR_HEIGHT } from "@/lib/utils";

export function DesktopTopBar() {
return (
<header
className={cn(
"sticky top-0 z-40 hidden items-center gap-4 border-b bg-background sm:flex layout-padding-x",
TOP_BAR_HEIGHT,
)}
>
<header className="sticky top-0 z-40 hidden h-(--top-bar-height) items-center gap-4 shadow-light bg-sidebar sm:flex layout-padding-x">
<div className="flex flex-1 justify-center">
<p className="text-muted-foreground">Global Search</p>
</div>
Expand Down
12 changes: 7 additions & 5 deletions components/navigation/left-sidebar.tsx
Original file line numberDiff line numberDiff line change
Expand Up@@ -25,17 +25,19 @@ import {
isRouteActive,
NAV_LINK_ACTIVE_CLASSES,
NAV_LINK_INACTIVE_CLASSES,
TOP_BAR_HEIGHT,
} from "@/lib/utils";

export function LeftSidebar() {
const pathname = usePathname();

return (
<Sidebar id="left-sidebar" collapsible="icon">
<SidebarHeader
className={cn(TOP_BAR_HEIGHT, "flex-row items-center px-3")}
>
<Sidebar
id="left-sidebar"
collapsible="icon"
width="13rem"
className="shadow-light"
>
<SidebarHeader className="h-(--top-bar-height) flex-row items-center px-3">
<Link
href="/"
aria-label="DevFlow sidebar logo"
Expand Down
Loading
Loading