Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
137 changes: 102 additions & 35 deletions morya-techcoder/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,12 @@ morya-techcoder/
│ ├── layout.tsx # Root layout (fonts + theme provider)
│ ├── (site)/ # 🆕 Route group for public site
│ │ ├── layout.tsx # Navbar + Footer + ambient background
│ │ ├── page.tsx # Home page (Hero + featured content)
│ │ └── blog/
│ │ ├── page.tsx # Blog list with search & filters
│ │ └── [slug]/page.tsx # Dynamic blog detail pages
│ │ ├── page.tsx # Home page (Hero + topic grid + rails)
│ │ ├── blog/
│ │ │ ├── page.tsx # Blog list with search & filters
│ │ │ └── [slug]/page.tsx # Dynamic blog detail pages
│ │ └── topics/
│ │ └── [category]/page.tsx # 🆕 Per-topic landing pages (SSG)
│ ├── keystatic/ # 🆕 Keystatic admin UI
│ │ └── [[...params]]/page.tsx # Visual CMS editor at /keystatic
│ └── api/keystatic/ # 🆕 Keystatic file operations API
Expand All @@ -63,16 +65,22 @@ morya-techcoder/
│ │ └── Footer.tsx # Multi-column footer with social links
│ ├── sections/
│ │ ├── HeroSection.tsx # Animated hero with 3D code preview
│ │ ├── TopicGrid.tsx # 🆕 On-page topic discovery (reusable category treatment)
│ │ ├── HomeContent.tsx # Category-grouped post grids
│ │ ├── BlogListContent.tsx # Client-side search & filtering
│ │ └── TrustedBy, Capabilities, Testimonials, FAQ, etc.
│ ├── ui/
│ │ ├── MagicBorderCard.tsx # Blog card with hover animations
│ │ ├── MagicBorderCard.tsx # Compact, elegant article card
│ │ ├── CategoryBadge.tsx # 🆕 Reusable color-coded category pill
│ │ ├── CardCarousel.tsx # 🆕 Responsive snapping slider (featured rail)
│ │ ├── CodeBlock.tsx # Copy-to-clipboard code blocks
│ │ ├── TableOfContents.tsx # Auto-generated TOC (uses useActiveHeading hook)
│ │ ├── ReadingProgress.tsx # Scroll progress bar (uses useReadingProgress hook)
│ │ ├── TableOfContents.tsx # Desktop TOC — sliding indicator + completion (useReadingState)
│ │ ├── ReadingProgress.tsx # Compositor-only top progress bar (uses useScrollProgress)
│ │ ├── FadeInImage.tsx # next/image drop-in: eases from opacity/scale on load
│ │ ├── ScrollToTop.tsx # 🆕 Global fade-in scroll-to-top affordance
│ │ ├── SpotlightCursor.tsx # 🆕 Subtle warm cursor glow (desktop, reduced-motion aware)
│ │ ├── NewsletterBox.tsx # Email subscription form
│ │ ├── ThemeToggle.tsx # Light/dark mode switcher
│ │ ├── ThemeToggle.tsx # Light/dark switch (View Transitions cross-fade)
│ │ ├── DifficultyBadge.tsx # Visual difficulty indicators
│ │ ├── HeadingLink.tsx # Anchor links for headings
│ │ └── SectionHeading, Reveal, etc.
Expand All @@ -96,16 +104,30 @@ morya-techcoder/
│ └── posts/*.mdx # Blog posts (MDX only, no .md duplicates)
├── lib/ # 🔄 Pure logic (framework-agnostic)
│ ├── categories.ts # 🔄 SINGLE SOURCE OF TRUTH for categories
│ ├── categories.ts # 🔄 SINGLE SOURCE OF TRUTH for categories (6 topics)
│ ├── category-icons.ts # 🆕 Maps category icon names → lucide components
│ ├── featureFlags.ts # 🆕 Progressive section rollout (Testimonials, FAQ, …)
│ ├── posts.ts # 🆕 Business logic (filtering, featured, related)
│ └── utils.ts # Helpers (cn, formatDate, calcReadingTime)
├── types/ # 🆕 Dedicated types directory
│ └── blog.ts # 🔄 Moved from content/blogs.ts
├── components/layout/
│ ├── Navbar.tsx # Glass navbar → mobile reading toolbar on articles
│ └── NavLinks.tsx # 🆕 Desktop nav: shared pill (layoutId) + magnetic + underline
├── components/reading/ # 🆕 Immersive reading experience
│ ├── ReadingChromeProvider.tsx # Context: article chrome + Contents sheet state
│ ├── SetReadingChrome.tsx # Registers the current article (client, on the page)
│ ├── ArticleActions.tsx # 🆕 Sidebar companion: share/copy/bookmark/print/focus + progress
│ └── ReadingLayer.tsx # Top progress bar + floating circular indicator + TOC bottom sheet
├── hooks/ # 🆕 Reusable client hooks
│ ├── useReadingProgress.ts # Extracted scroll percentage logic
│ └── useActiveHeading.ts # Extracted intersection observer logic
│ ├── useReadingProgress.ts # Scroll percentage (state-based)
│ ├── useScrollProgress.ts # 🆕 Imperative rAF scroll progress (no re-renders)
│ ├── useReadingState.ts # 🆕 Active heading + completed sections scroll-spy
│ └── useActiveHeading.ts # Intersection observer active-heading (desktop TOC)
├── docs/ # 🆕 Comprehensive documentation (8 files)
│ ├── README.md # Documentation index
Expand All @@ -126,11 +148,14 @@ morya-techcoder/

### Routes

- `/` - Home page with hero, featured posts, category sections
- `/blog` - Blog listing with search and category filters
- `/` - Home page (hero, topic grid, featured carousel + editorial rails)
- `/blog` - Full article library with search and category filters
- `/blog/[slug]` - Individual blog post pages (SSG via generateStaticParams)
- `/blog?category=AI` - Filtered views via URL params
- `/keystatic` - 🆕 Visual CMS admin interface (full-screen, no site chrome)
- `/topics/[category]` - 🆕 Per-topic landing pages (SSG for all 6 topics; empty
and "coming soon" topics render a graceful state). Slug via `categorySlug()`,
e.g. `/topics/ai`, `/topics/dev-tools`. This is the primary category route the
nav/footer/topic-grid link to; `/blog?category=` still works for direct links.
- `/keystatic` - Visual CMS admin interface (full-screen, no site chrome)

### Route Group Pattern

Expand Down Expand Up @@ -193,16 +218,16 @@ export interface BlogPost {
slug: string;
excerpt: string;
date: string; // ISO format (YYYY-MM-DD)
category: BlogCategory; // "AI" | "WebDev" | "Tricks"
category: BlogCategory; // "Programming" | "AI" | "Technology" | "Reviews" | "Guides" | "DevTools"
tags: string[];
readingTime: string; // Auto-calculated (238 wpm)
coverImage: string;
thumbnail: string;
ogImage: string;
difficulty?: Difficulty;
updated?: string; // Optional last updated date
prerequisites?: string[];
draft?: boolean; // 🆕 Visibility toggle (dev vs prod)
featured?: boolean; // 🆕 Homepage “Featured articles” eligibility
seo?: { // 🆕 SEO overrides
title?: string;
description?: string;
Expand All @@ -213,6 +238,7 @@ export interface BlogPost {

**New Fields:**
- `draft: boolean` - Filters posts in production (visible in dev only)
- `featured: boolean` - Show in homepage Featured section (Keystatic checkbox or frontmatter)
- `seo: { title?, description? }` - Override default title/description for SEO

### Content Loader Architecture
Expand Down Expand Up @@ -272,10 +298,13 @@ compileMDX({
- Primary: Orange gradient (`#F97316` → `#FDBA74`)
- Accent: Rose/pink for "New" badges (`#F43F5E`)

**Category Colors (from `lib/categories.ts`):**
- AI: Orange tones
- WebDev: Blue tones
- Tricks: Green/emerald tones
**Category Colors (from `lib/categories.ts`), one accent per topic:**
- Programming: Blue tones
- AI (Artificial Intelligence): Orange tones (the brand accent)
- Technology: Cyan/teal tones
- Reviews: Violet tones
- Guides (Buying Guides): Emerald tones
- DevTools (Developer Tools): Slate tones — flagged `comingSoon`

**Theme:** 40+ CSS custom properties (`--tc-*` tokens) with light/dark mode support

Expand All @@ -287,20 +316,32 @@ compileMDX({
**After Refactor:** Everything derives from ONE object:

```typescript
export const CATEGORIES = {
AI: {
label: "AI & Machine Learning",
description: "Latest breakthroughs and practical AI engineering",
badge: "bg-tc-cat-ai-bg text-tc-cat-ai-text border border-tc-cat-ai-border",
dot: "bg-tc-cat-ai-text",
// Rich metadata per topic. Icons are string keys (lucide names) so this module
// stays framework-agnostic; lib/category-icons.ts resolves them to components.
const CATEGORY_DEFS = {
Programming: {
label: "Programming",
short: "Programming",
description: "Languages, frameworks, and the craft of writing great software.",
icon: "Code2",
badge: "bg-tc-cat-programming-bg text-tc-cat-programming-text border border-tc-cat-programming-border",
dot: "bg-tc-cat-programming-text",
accent: "text-tc-cat-programming-text",
},
WebDev: { /* ... */ },
Tricks: { /* ... */ },
} as const;

// Derived exports (automatic from CATEGORIES)
export type BlogCategory = keyof typeof CATEGORIES;
AI: { /* Artificial Intelligence */ },
Technology: { /* ... */ },
Reviews: { /* ... */ },
Guides: { /* Buying Guides */ },
DevTools: { /* Developer Tools, comingSoon: true */ },
} as const satisfies Record<string, CategoryMeta>;

// Derived exports (automatic from the definitions)
export type BlogCategory = keyof typeof CATEGORY_DEFS;
// Widened view so optional fields (e.g. comingSoon) are accessible everywhere
export const CATEGORIES: Record<BlogCategory, CategoryMeta> = CATEGORY_DEFS;
export const CATEGORY_KEYS = Object.keys(CATEGORIES) as BlogCategory[];
// Keys that accept content today (excludes "coming soon" topics)
export const ACTIVE_CATEGORY_KEYS = CATEGORY_KEYS.filter((k) => !CATEGORIES[k].comingSoon);
export const categoryColors = /* map from CATEGORIES */;
export const categoryOptions = /* for Keystatic dropdown */;
```
Expand Down Expand Up @@ -468,13 +509,14 @@ import type { BlogPost } from "@/types/blog";
title: "Your Post Title"
excerpt: "Brief description for SEO and cards"
date: "2026-07-12"
category: "AI" # or "WebDev" or "Tricks"
category: "Programming" # or "AI" | "Technology" | "Reviews" | "Guides"
tags: ["nextjs", "typescript"]
thumbnail: "/content/blog/post-thumbnail.jpg"
ogImage: "/content/blog/post-og.jpg"
difficulty: "Intermediate" # optional
prerequisites: ["Basic React", "TypeScript"] # optional
draft: false # optional (defaults false)
featured: true # optional — show in homepage Featured articles
---
```
3. Write content in markdown/MDX
Expand All @@ -495,6 +537,20 @@ import type { BlogPost } from "@/types/blog";

**Never mix:** Server-only imports in client components will fail

### Feature flags

Toggle homepage (and later site-wide) sections from `lib/featureFlags.ts`:

```ts
import { isFeatureEnabled } from "@/lib/featureFlags";

{isFeatureEnabled("showTestimonials") && <Testimonials />}
{isFeatureEnabled("showFAQ") && <FAQ />}
```

Current defaults: `showTestimonials: false`, `showFAQ: false`. Components stay in
the tree — they simply don't render when disabled.

### Adding New Categories

1. Edit `lib/categories.ts` - add to `CATEGORIES` object
Expand All @@ -509,6 +565,14 @@ No need to edit types, filter pills, section headers, or color mappings separate
- Conditional classes with `cn()` helper (`clsx` + `tailwind-merge`)
- Avoid inline styles except for dynamic values (e.g., `style={{ width: percent }}`)
- Animation classes: `.animate-fade-up`, `.animate-float`, `.animate-pulse-slow`
- **Motion system** — use the shared tokens `--tc-dur-fast|--tc-dur|--tc-dur-slow`
and `--tc-ease*` for all transitions (never hardcode ms/easing). Reusable
interaction utilities: `.moving-border` (animated gradient edge on hover),
`.press` (tactile scale), `.link-underline`, layered `--tc-shadow-md|lg`.
Public routes paint immediately without a client route-transition wrapper.
Article prose CSS and JetBrains Mono are scoped to `/blog/[slug]`; deferred
homepage sections use `content-visibility: auto`. See
[docs/motion-system.md](docs/motion-system.md).

---

Expand All @@ -524,14 +588,17 @@ No need to edit types, filter pills, section headers, or color mappings separate
- [keystatic.config.ts](keystatic.config.ts) - Keystatic schema & collections
- [types/blog.ts](types/blog.ts) - TypeScript type definitions
- [lib/categories.ts](lib/categories.ts) - SINGLE SOURCE OF TRUTH for categories
- [lib/featureFlags.ts](lib/featureFlags.ts) - Progressive feature flags (`isFeatureEnabled`)
- [lib/posts.ts](lib/posts.ts) - Business logic (filtering, featured, related)
- [lib/utils.ts](lib/utils.ts) - Helper functions

### Key Components
- [components/layout/Navbar.tsx](components/layout/Navbar.tsx) - Glassmorphic navbar
- [components/sections/HeroSection.tsx](components/sections/HeroSection.tsx) - Animated hero
- [components/sections/HomeContent.tsx](components/sections/HomeContent.tsx) - Magazine-style editorial rails
- [components/sections/BlogListContent.tsx](components/sections/BlogListContent.tsx) - Search & filters
- [components/ui/MagicBorderCard.tsx](components/ui/MagicBorderCard.tsx) - Blog cards
- [components/ui/MagicBorderCard.tsx](components/ui/MagicBorderCard.tsx) - Blog cards (`.card-premium` hover)
- [components/ui/CardCarousel.tsx](components/ui/CardCarousel.tsx) - Infinite premium article carousel
- [components/ui/TableOfContents.tsx](components/ui/TableOfContents.tsx) - Auto TOC
- [components/ui/CodeBlock.tsx](components/ui/CodeBlock.tsx) - Code blocks with copy
- [components/mdx/](components/mdx/) - MDX component library (11 components)
Expand Down
134 changes: 134 additions & 0 deletions morya-techcoder/app/(site)/blog/[slug]/article.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
.prose-tc {
max-width: 48rem;
font-family: var(--font-body);
}

.prose-tc h1,
.prose-tc h2,
.prose-tc h3,
.prose-tc h4 {
color: var(--tc-text);
font-family: var(--font-heading);
scroll-margin-top: 5.5rem;
}

.prose-tc h1 {
margin-block: 2.5rem 1rem;
font-size: 1.75rem;
font-weight: 700;
line-height: 1.2;
letter-spacing: -0.02em;
}

.prose-tc h2 {
margin-block: 2rem 0.75rem;
font-size: 1.5rem;
font-weight: 700;
line-height: 1.25;
letter-spacing: -0.015em;
}

.prose-tc h3 {
margin-block: 1.75rem 0.5rem;
font-size: 1.25rem;
font-weight: 600;
line-height: 1.3;
letter-spacing: -0.01em;
}

.prose-tc h4 {
margin-block: 1.5rem 0.5rem;
font-size: 1.125rem;
font-weight: 600;
line-height: 1.4;
}

@media (min-width: 768px) {
.prose-tc h1 { font-size: 2.25rem; }
.prose-tc h2 { font-size: 1.75rem; }
.prose-tc h3 { font-size: 1.375rem; }
.prose-tc h4 { font-size: 1.25rem; }
}

.prose-tc p,
.prose-tc li {
color: var(--tc-text-muted);
font-family: var(--font-body);
font-size: 1.0625rem;
line-height: 1.8;
}

.prose-tc p { margin-bottom: 1.5rem; }
.prose-tc ul,
.prose-tc ol { margin-bottom: 1.5rem; padding-left: 1.5rem; }
.prose-tc li { margin-bottom: 0.5rem; }
.prose-tc ul li { list-style-type: disc; }
.prose-tc ol li { list-style-type: decimal; }

.prose-tc blockquote {
margin: 1.5rem 0;
padding: 0.75rem 1.25rem;
border-left: 3px solid var(--tc-primary);
border-radius: 0 12px 12px 0;
background: var(--tc-bg-elevated);
}
.prose-tc blockquote p { margin-bottom: 0; color: var(--tc-text); font-style: italic; }

.prose-tc code {
padding: 0.15rem 0.45rem;
border: 1px solid var(--tc-border);
border-radius: 6px;
background: var(--tc-bg-elevated);
color: var(--tc-text);
font-family: var(--font-mono);
font-size: 0.875rem;
}
.prose-tc pre {
overflow-x: auto;
margin-bottom: 1.5rem;
padding: 1.25rem;
border: 1px solid var(--tc-border-code);
border-radius: 14px;
background: var(--tc-bg-code);
color: var(--tc-text-code);
font-family: var(--font-mono);
}
.prose-tc pre code {
padding: 0;
border: 0;
background: none;
color: inherit;
font-size: 0.875rem;
line-height: 1.7;
}

.prose-tc a {
background-image: linear-gradient(var(--tc-primary), var(--tc-primary));
background-position: 0 100%;
background-repeat: no-repeat;
background-size: 0 1.5px;
color: var(--tc-primary-dark);
text-decoration: none;
transition: background-size var(--tc-dur) var(--tc-ease), color var(--tc-dur) var(--tc-ease);
}
.prose-tc a:hover { background-size: 100% 1.5px; color: var(--tc-primary); }
.prose-tc img { margin: 1.5rem 0; border-radius: 14px; }
.prose-tc hr { margin: 2.5rem 0; border: 0; border-top: 1px solid var(--tc-border); }
.prose-tc strong { color: var(--tc-text); font-weight: 600; }
.prose-tc table {
display: block;
width: 100%;
overflow-x: auto;
margin: 1.5rem 0;
border-collapse: collapse;
font-size: 0.9375rem;
}
.prose-tc th,
.prose-tc td {
padding: 0.6rem 0.9rem;
border: 1px solid var(--tc-border);
text-align: left;
vertical-align: top;
}
.prose-tc th { background: var(--tc-bg-elevated); color: var(--tc-text); font-weight: 600; }
.prose-tc td { color: var(--tc-text-muted); }
Loading