mirror of
https://github.com/waynesutton/markdown-site.git
synced 2026-01-11 20:08:57 +00:00
feat: add GitHub contributions graph with theme-aware colors
- Add GitHubContributions component with year navigation - Display contribution activity from github-contributions-api.jogruber.de - Theme-specific colors for dark, light, tan, and cloud themes - Phosphor icons for year navigation (CaretLeft, CaretRight) - Click graph to visit GitHub profile - Configurable via siteConfig.gitHubContributions - Mobile responsive with scaled cells and hidden day labels - Add documentation to setup-guide, docs, README, and changelog
This commit is contained in:
@@ -4,7 +4,7 @@ Instructions for AI coding agents working on this codebase.
|
||||
|
||||
## Project overview
|
||||
|
||||
An open-source markdown sync site for developers and AI agents. Publish from the terminal with `npm run sync`. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.
|
||||
An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.
|
||||
|
||||
**Key features:**
|
||||
- Markdown posts with frontmatter
|
||||
|
||||
76
README.md
76
README.md
@@ -1,8 +1,8 @@
|
||||
# markdown "sync" site
|
||||
# markdown "sync" framework
|
||||
|
||||
An open-source markdown sync site for developers and AI agents. Publish from the terminal with `npm run sync`. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.
|
||||
An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.
|
||||
|
||||
Write markdown locally, run `npm run sync` (dev) or `npm run sync:prod` (production), and content appears instantly across all connected browsers. Built with React, Convex, and Vite. Optimized for SEO, AI agents, and LLM discovery.
|
||||
Write markdown locally, run `npm run sync` (dev) or `npm run sync:prod` (production), and content appears instantly across all connected browsers. Built with React, Convex, and Vite. Optimized for AEO, GEO, and LLM discovery.
|
||||
|
||||
**How publishing works:** Write posts in markdown, run `npm run sync` for development or `npm run sync:prod` for production, and they appear on your live site immediately. No rebuild or redeploy needed. Convex handles real-time data sync, so all connected browsers update automatically.
|
||||
|
||||
@@ -27,6 +27,7 @@ npm run sync:prod # production
|
||||
- Full text search with Command+K shortcut
|
||||
- Featured section with list/card view toggle
|
||||
- Logo gallery with continuous marquee scroll
|
||||
- GitHub contributions graph with year navigation
|
||||
- Static raw markdown files at `/raw/{slug}.md`
|
||||
- Dedicated blog page with configurable navigation order
|
||||
- Markdown writing page at `/write` with frontmatter reference
|
||||
@@ -63,15 +64,16 @@ When you fork this project, update these files with your site information:
|
||||
|
||||
| File | What to update |
|
||||
| ----------------------------------- | ----------------------------------------------------------- |
|
||||
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery |
|
||||
| `convex/http.ts` | `SITE_URL`, `SITE_NAME` (API responses, sitemap) |
|
||||
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery, GitHub contributions |
|
||||
| `src/pages/Home.tsx` | Intro paragraph text (hardcoded JSX) |
|
||||
| `convex/http.ts` | `SITE_URL`, `SITE_NAME`, description strings (3 locations) |
|
||||
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
|
||||
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
|
||||
| `index.html` | Title, meta description, OG tags, JSON-LD |
|
||||
| `public/llms.txt` | Site URL and description |
|
||||
| `public/robots.txt` | Sitemap URL |
|
||||
| `public/.well-known/ai-plugin.json` | Site name and description |
|
||||
| `public/openapi.yaml` | API title and site name |
|
||||
| `public/llms.txt` | Site name, URL, description, topics |
|
||||
| `public/robots.txt` | Sitemap URL and header comment |
|
||||
| `public/openapi.yaml` | API title, server URL, site name in examples |
|
||||
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
|
||||
|
||||
See the [Setup Guide](/setup-guide) for detailed configuration examples.
|
||||
|
||||
@@ -165,6 +167,17 @@ Add images in markdown content:
|
||||
|
||||
Place image files in `public/images/`. The alt text displays as a caption.
|
||||
|
||||
### Image deployment
|
||||
|
||||
Images are served as static files from your git repository, not synced to Convex. After adding images:
|
||||
|
||||
1. Add image files to `public/images/`
|
||||
2. Reference in frontmatter (`image: "/images/my-image.png"`) or markdown (``)
|
||||
3. Commit and push to git
|
||||
4. Netlify rebuilds and serves the images
|
||||
|
||||
The `npm run sync` command only syncs markdown text content. Images require a full deploy via git push.
|
||||
|
||||
### Site Logo
|
||||
|
||||
Edit `src/pages/Home.tsx` to set your site logo:
|
||||
@@ -238,13 +251,13 @@ blogPage: {
|
||||
displayOnHomepage: true, // Show posts on homepage
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| `enabled` | Enable the `/blog` route |
|
||||
| `showInNav` | Show Blog link in navigation |
|
||||
| `title` | Text for nav link and page heading |
|
||||
| `order` | Position in navigation (lower = first) |
|
||||
| `displayOnHomepage` | Show post list on homepage |
|
||||
| Option | Description |
|
||||
| ------------------- | -------------------------------------- |
|
||||
| `enabled` | Enable the `/blog` route |
|
||||
| `showInNav` | Show Blog link in navigation |
|
||||
| `title` | Text for nav link and page heading |
|
||||
| `order` | Position in navigation (lower = first) |
|
||||
| `displayOnHomepage` | Show post list on homepage |
|
||||
|
||||
**Display options:**
|
||||
|
||||
@@ -296,6 +309,37 @@ Delete sample files from `public/images/logos/` and replace the images array wit
|
||||
|
||||
The gallery uses CSS animations for smooth infinite scrolling. Logos appear grayscale and colorize on hover.
|
||||
|
||||
## GitHub Contributions Graph
|
||||
|
||||
Display your GitHub contribution activity on the homepage. Configure in `src/config/siteConfig.ts`:
|
||||
|
||||
```typescript
|
||||
gitHubContributions: {
|
||||
enabled: true, // Set to false to hide
|
||||
username: "yourusername", // Your GitHub username
|
||||
showYearNavigation: true, // Show arrows to navigate between years
|
||||
linkToProfile: true, // Click graph to open GitHub profile
|
||||
title: "GitHub Activity", // Optional title above the graph
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | --------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `username` | Your GitHub username |
|
||||
| `showYearNavigation` | Show prev/next year navigation buttons |
|
||||
| `linkToProfile` | Click graph to visit GitHub profile |
|
||||
| `title` | Text above graph (set to `undefined` to hide) |
|
||||
|
||||
The graph displays with theme-aware colors that match each site theme:
|
||||
|
||||
- **Dark**: GitHub green on dark background
|
||||
- **Light**: Standard GitHub green
|
||||
- **Tan**: Warm brown tones
|
||||
- **Cloud**: Gray-blue tones
|
||||
|
||||
Uses the public `github-contributions-api.jogruber.de` API (no GitHub token required).
|
||||
|
||||
### Favicon
|
||||
|
||||
Replace `public/favicon.svg` with your own icon. The default is a rounded square with the letter "m". Edit the SVG to change the letter or style.
|
||||
|
||||
10
TASK.md
10
TASK.md
@@ -2,16 +2,22 @@
|
||||
|
||||
## To Do
|
||||
|
||||
- [ ] add github code block
|
||||
- [ ] create a ui site config page
|
||||
- [ ] create a prompt formator or checklidst or skill or agent to change everything at once after forking
|
||||
|
||||
## Current Status
|
||||
|
||||
v1.16.0 deployed. Added public /write page with three-column Cursor docs-style layout, font switcher, theme toggle, and localStorage persistence for markdown writing.
|
||||
v1.17.0 deployed. Added GitHub contributions graph on homepage with theme-aware colors, year navigation, and configurable display options.
|
||||
|
||||
## Completed
|
||||
|
||||
- [x] GitHub contributions graph on homepage with theme-aware colors
|
||||
- [x] Year navigation with Phosphor icons (CaretLeft, CaretRight)
|
||||
- [x] Click graph to visit GitHub profile
|
||||
- [x] Configurable via siteConfig.gitHubContributions
|
||||
- [x] Theme-specific contribution colors for all 4 themes
|
||||
- [x] Mobile responsive design with scaled cells
|
||||
|
||||
- [x] Public /write page with three-column layout (not linked in nav)
|
||||
- [x] Left sidebar: Home link, content type selector, actions (Clear, Theme, Font)
|
||||
- [x] Center: Writing area with Copy All button and borderless textarea
|
||||
|
||||
28
changelog.md
28
changelog.md
@@ -4,6 +4,34 @@ All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [1.17.0] - 2025-12-20
|
||||
|
||||
### Added
|
||||
|
||||
- GitHub contributions graph on homepage
|
||||
- Displays yearly contribution activity with theme-aware colors
|
||||
- Fetches data from public API (no GitHub token required)
|
||||
- Year navigation with Phosphor icons (CaretLeft, CaretRight)
|
||||
- Click graph to visit GitHub profile
|
||||
- Configurable via `siteConfig.gitHubContributions`
|
||||
- Theme-specific contribution colors
|
||||
- Dark theme: GitHub green on dark background
|
||||
- Light theme: Standard GitHub green
|
||||
- Tan theme: Warm brown tones matching site palette
|
||||
- Cloud theme: Gray-blue tones
|
||||
- Mobile responsive design
|
||||
- Scales down on tablets and phones
|
||||
- Day labels hidden on small screens for space
|
||||
- Touch-friendly navigation buttons
|
||||
|
||||
### Technical
|
||||
|
||||
- New component: `src/components/GitHubContributions.tsx`
|
||||
- Uses `github-contributions-api.jogruber.de` public API
|
||||
- CSS variables for contribution level colors per theme
|
||||
- Configuration interface: `GitHubContributionsConfig`
|
||||
- Set `enabled: false` in siteConfig to disable
|
||||
|
||||
## [1.16.0] - 2025-12-21
|
||||
|
||||
### Added
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
---
|
||||
title: "About This Markdown Site"
|
||||
description: "How this open source site works with Convex for real-time sync and Netlify for deployment."
|
||||
title: "About This Markdown Framework"
|
||||
description: "How this open source framework works with Convex for real-time sync and Netlify for deployment."
|
||||
date: "2025-01-16"
|
||||
slug: "about-this-blog"
|
||||
published: true
|
||||
tags: ["convex", "netlify", "open-source", "markdown"]
|
||||
tags: ["convex", "netlify", "open-source", "markdown", "ai", "llm"]
|
||||
readTime: "4 min read"
|
||||
featured: false
|
||||
featuredOrder: 3
|
||||
excerpt: "Learn how this open source site works with real-time sync and instant updates."
|
||||
excerpt: "Learn how this open source framework works with real-time sync and instant updates."
|
||||
---
|
||||
|
||||
# About This Markdown Site
|
||||
# About This Markdown Framework
|
||||
|
||||
An open-source markdown sync site for developers and AI agents. Publish from the terminal with `npm run sync`. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.
|
||||
An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.
|
||||
|
||||
## How It Works
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "How to Publish a Blog Post"
|
||||
description: "A quick guide to writing and publishing markdown blog posts using Cursor after your blog is set up."
|
||||
description: "A quick guide to writing and publishing markdown posts using Cursor after your framework is set up."
|
||||
date: "2025-01-17"
|
||||
slug: "how-to-publish"
|
||||
published: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "New features: search, featured section, and logo gallery"
|
||||
description: "Three updates that make your markdown site more useful: Command+K search, frontmatter-controlled featured items, and a scrolling logo gallery."
|
||||
description: "Three updates that make your markdown framework more useful: Command+K search, frontmatter-controlled featured items, and a scrolling logo gallery."
|
||||
date: "2025-12-17"
|
||||
slug: "new-features-search-featured-logos"
|
||||
published: true
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Setup Guide - Fork and Deploy Your Own Markdown Site"
|
||||
description: "Step-by-step guide to fork this markdown sync site, set up Convex backend, and deploy to Netlify in under 10 minutes."
|
||||
title: "Setup Guide - Fork and Deploy Your Own Markdown Framework"
|
||||
description: "Step-by-step guide to fork this markdown sync framework, set up Convex backend, and deploy to Netlify in under 10 minutes."
|
||||
date: "2025-01-14"
|
||||
slug: "setup-guide"
|
||||
published: true
|
||||
@@ -9,18 +9,18 @@ readTime: "8 min read"
|
||||
featured: true
|
||||
featuredOrder: 3
|
||||
image: "/images/setupguide.png"
|
||||
excerpt: "Complete guide to fork, set up, and deploy your own markdown blog in under 10 minutes."
|
||||
excerpt: "Complete guide to fork, set up, and deploy your own markdown framework in under 10 minutes."
|
||||
---
|
||||
|
||||
# Fork and Deploy Your Own Markdown Blog
|
||||
# Fork and Deploy Your Own Markdown Framework
|
||||
|
||||
This guide walks you through forking [this markdown site](https://github.com/waynesutton/markdown-site), setting up your Convex backend, and deploying to Netlify. The entire process takes about 10 minutes.
|
||||
This guide walks you through forking [this markdown framework](https://github.com/waynesutton/markdown-site), setting up your Convex backend, and deploying to Netlify. The entire process takes about 10 minutes.
|
||||
|
||||
**How publishing works:** Once deployed, you write posts in markdown, run `npm run sync` for development or `npm run sync:prod` for production, and they appear on your live site immediately. No rebuild or redeploy needed. Convex handles real-time data sync, so all connected browsers update automatically.
|
||||
|
||||
## Table of Contents
|
||||
|
||||
- [Fork and Deploy Your Own Markdown Blog](#fork-and-deploy-your-own-markdown-blog)
|
||||
- [Fork and Deploy Your Own Markdown Framework](#fork-and-deploy-your-own-markdown-framework)
|
||||
- [Table of Contents](#table-of-contents)
|
||||
- [Prerequisites](#prerequisites)
|
||||
- [Step 1: Fork the Repository](#step-1-fork-the-repository)
|
||||
@@ -42,18 +42,22 @@ This guide walks you through forking [this markdown site](https://github.com/way
|
||||
- [Sync After Adding Posts](#sync-after-adding-posts)
|
||||
- [Environment Files](#environment-files)
|
||||
- [When to Sync vs Deploy](#when-to-sync-vs-deploy)
|
||||
- [Customizing Your Blog](#customizing-your-blog)
|
||||
- [Customizing Your Framework](#customizing-your-framework)
|
||||
- [Files to Update When Forking](#files-to-update-when-forking)
|
||||
- [Site title and description metadata](#site-title-and-description-metadata)
|
||||
- [Update Backend Configuration](#update-backend-configuration)
|
||||
- [Change the Favicon](#change-the-favicon)
|
||||
- [Change the Site Logo](#change-the-site-logo)
|
||||
- [Change the Default Open Graph Image](#change-the-default-open-graph-image)
|
||||
- [Update Site Configuration](#update-site-configuration)
|
||||
- [Featured Section](#featured-section)
|
||||
- [GitHub Contributions Graph](#github-contributions-graph)
|
||||
- [Logo Gallery](#logo-gallery)
|
||||
- [Blog page](#blog-page)
|
||||
- [Scroll-to-top button](#scroll-to-top-button)
|
||||
- [Change the Default Theme](#change-the-default-theme)
|
||||
- [Change the Font](#change-the-font)
|
||||
- [Change Font Sizes](#change-font-sizes)
|
||||
- [Add Static Pages (Optional)](#add-static-pages-optional)
|
||||
- [Update SEO Meta Tags](#update-seo-meta-tags)
|
||||
- [Update llms.txt and robots.txt](#update-llmstxt-and-robotstxt)
|
||||
@@ -70,6 +74,7 @@ This guide walks you through forking [this markdown site](https://github.com/way
|
||||
- [RSS/Sitemap not working](#rsssitemap-not-working)
|
||||
- [Build failures on Netlify](#build-failures-on-netlify)
|
||||
- [Project Structure](#project-structure)
|
||||
- [Write Page](#write-page)
|
||||
- [Next Steps](#next-steps)
|
||||
|
||||
## Prerequisites
|
||||
@@ -341,6 +346,14 @@ This image appears when sharing on social media. Recommended: 1200x630 pixels.
|
||||

|
||||
```
|
||||
|
||||
**Images require git deploy.** Images are served as static files from your repository, not synced to Convex. After adding images to `public/images/`:
|
||||
|
||||
1. Commit the image files to git
|
||||
2. Push to GitHub
|
||||
3. Wait for Netlify to rebuild
|
||||
|
||||
The `npm run sync` command only syncs markdown text content. Images are deployed when Netlify builds your site.
|
||||
|
||||
### Sync After Adding Posts
|
||||
|
||||
After adding or editing posts, sync to Convex.
|
||||
@@ -384,31 +397,33 @@ Both files are gitignored. Each developer creates their own local environment fi
|
||||
| Pages in `content/pages/` | `npm run sync` | Instant (no rebuild) |
|
||||
| Featured items (via frontmatter) | `npm run sync` | Instant (no rebuild) |
|
||||
| Import external URL | `npm run import` then sync | Instant (no rebuild) |
|
||||
| Images in `public/images/` | Git commit + push | Requires rebuild |
|
||||
| `siteConfig` in `Home.tsx` | Redeploy | Requires rebuild |
|
||||
| Logo gallery config | Redeploy | Requires rebuild |
|
||||
| React components/styles | Redeploy | Requires rebuild |
|
||||
|
||||
**Markdown content** syncs instantly via Convex. **Source code changes** require pushing to GitHub for Netlify to rebuild.
|
||||
**Markdown content** syncs instantly via Convex. **Images and source code** require pushing to GitHub for Netlify to rebuild.
|
||||
|
||||
**Featured items** can now be controlled via markdown frontmatter. Add `featured: true` and `featuredOrder: 1` to any post or page, then run `npm run sync`.
|
||||
|
||||
## Customizing Your Blog
|
||||
## Customizing Your Framework
|
||||
|
||||
### Files to Update When Forking
|
||||
|
||||
When you fork this project, update these files with your site information:
|
||||
|
||||
| File | What to update |
|
||||
| ----------------------------------- | ----------------------------------------------------------- |
|
||||
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery |
|
||||
| `convex/http.ts` | `SITE_URL`, `SITE_NAME` (API responses, sitemap) |
|
||||
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
|
||||
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
|
||||
| `index.html` | Title, meta description, OG tags, JSON-LD |
|
||||
| `public/llms.txt` | Site name, URL, description |
|
||||
| `public/robots.txt` | Sitemap URL |
|
||||
| `public/openapi.yaml` | Server URL, site name in examples |
|
||||
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
|
||||
| File | What to update |
|
||||
| ----------------------------------- | --------------------------------------------------------------------------- |
|
||||
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery, GitHub contributions |
|
||||
| `src/pages/Home.tsx` | Intro paragraph text (hardcoded JSX) |
|
||||
| `convex/http.ts` | `SITE_URL`, `SITE_NAME`, description strings (3 locations) |
|
||||
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
|
||||
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
|
||||
| `index.html` | Title, meta description, OG tags, JSON-LD |
|
||||
| `public/llms.txt` | Site name, URL, description, topics |
|
||||
| `public/robots.txt` | Sitemap URL and header comment |
|
||||
| `public/openapi.yaml` | API title, server URL, site name in examples |
|
||||
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
|
||||
|
||||
### Site title and description metadata
|
||||
|
||||
@@ -418,13 +433,16 @@ These files contain the main site description text. Update them with your own ta
|
||||
| --------------------------------- | -------------------------------------------------------------- |
|
||||
| `index.html` | meta description, og:description, twitter:description, JSON-LD |
|
||||
| `README.md` | Main description at top of file |
|
||||
| `src/pages/Home.tsx` | intro and bio text in siteConfig |
|
||||
| `convex/http.ts` | description field in API responses (2 locations) |
|
||||
| `convex/rss.ts` | SITE_DESCRIPTION constant |
|
||||
| `public/llms.txt` | Header quote and Description field |
|
||||
| `src/config/siteConfig.ts` | name, title, and bio fields |
|
||||
| `src/pages/Home.tsx` | Intro paragraph (hardcoded JSX with links) |
|
||||
| `convex/http.ts` | SITE_NAME constant and description strings (3 locations) |
|
||||
| `convex/rss.ts` | SITE_TITLE and SITE_DESCRIPTION constants |
|
||||
| `public/llms.txt` | Header quote, Name, and Description fields |
|
||||
| `public/openapi.yaml` | API title and example site name |
|
||||
| `AGENTS.md` | Project overview section |
|
||||
| `content/blog/about-this-blog.md` | Opening paragraph |
|
||||
| `content/blog/about-this-blog.md` | Title, description, excerpt, and opening paragraph |
|
||||
| `content/pages/about.md` | excerpt field and opening paragraph |
|
||||
| `content/pages/docs.md` | Opening description paragraph |
|
||||
|
||||
### Update Backend Configuration
|
||||
|
||||
@@ -504,10 +522,10 @@ export default {
|
||||
|
||||
// Blog page configuration
|
||||
blogPage: {
|
||||
enabled: true, // Enable /blog route
|
||||
showInNav: true, // Show in navigation
|
||||
title: "Blog", // Nav link and page title
|
||||
order: 0, // Nav order (lower = first)
|
||||
enabled: true, // Enable /blog route
|
||||
showInNav: true, // Show in navigation
|
||||
title: "Blog", // Nav link and page title
|
||||
order: 0, // Nav order (lower = first)
|
||||
},
|
||||
displayOnHomepage: true, // Show posts on homepage
|
||||
|
||||
@@ -515,7 +533,7 @@ export default {
|
||||
featuredViewMode: "list", // 'list' or 'cards'
|
||||
showViewToggle: true, // Let users switch between views
|
||||
|
||||
// Logo gallery (marquee scroll with clickable links)
|
||||
// Logo gallery (static grid or scrolling marquee with clickable links)
|
||||
logoGallery: {
|
||||
enabled: true, // Set false to hide
|
||||
images: [
|
||||
@@ -524,7 +542,9 @@ export default {
|
||||
],
|
||||
position: "above-footer", // or 'below-featured'
|
||||
speed: 30, // Seconds for one scroll cycle
|
||||
title: "Trusted by",
|
||||
title: "Built with",
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
|
||||
links: {
|
||||
@@ -573,9 +593,33 @@ Use `featuredOrder` to control display order. Lower numbers appear first. Posts
|
||||
|
||||
Users can toggle between list and card views using the icon button next to "Get started:". To change the default view, set `featuredViewMode: "cards"` in siteConfig.
|
||||
|
||||
### GitHub Contributions Graph
|
||||
|
||||
Display your GitHub contribution activity on the homepage. Configure in `siteConfig`:
|
||||
|
||||
```typescript
|
||||
gitHubContributions: {
|
||||
enabled: true, // Set to false to hide
|
||||
username: "yourusername", // Your GitHub username
|
||||
showYearNavigation: true, // Show arrows to navigate between years
|
||||
linkToProfile: true, // Click graph to open GitHub profile
|
||||
title: "GitHub Activity", // Optional title above the graph
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| -------------------- | --------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `username` | Your GitHub username |
|
||||
| `showYearNavigation` | Show prev/next year buttons |
|
||||
| `linkToProfile` | Click graph to visit GitHub profile |
|
||||
| `title` | Text above graph (set to `undefined` to hide) |
|
||||
|
||||
The graph displays with theme-aware colors that match each site theme (dark, light, tan, cloud). Uses the public `github-contributions-api.jogruber.de` API (no GitHub token required).
|
||||
|
||||
### Logo Gallery
|
||||
|
||||
The homepage includes a scrolling logo gallery with 5 sample logos. Customize or disable it in siteConfig:
|
||||
The homepage includes a logo gallery that can scroll infinitely or display as a static grid. Customize or disable it in siteConfig:
|
||||
|
||||
**Disable the gallery:**
|
||||
|
||||
@@ -600,7 +644,9 @@ logoGallery: {
|
||||
],
|
||||
position: "above-footer",
|
||||
speed: 30,
|
||||
title: "Trusted by",
|
||||
title: "Built with",
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos to show when scrolling is false
|
||||
},
|
||||
```
|
||||
|
||||
@@ -615,15 +661,22 @@ Delete the sample files from `public/images/logos/` and clear the images array,
|
||||
|
||||
**Configuration options:**
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | ---------------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of logo objects with `src` and optional `href` |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (set to `undefined` to hide) |
|
||||
| Option | Description |
|
||||
| ----------- | ---------------------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of logo objects with `src` and optional `href` |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (set to `undefined` to hide) |
|
||||
| `scrolling` | `true` for infinite scroll, `false` for static grid |
|
||||
| `maxItems` | Max logos to show when `scrolling` is `false` (default: 4) |
|
||||
|
||||
The gallery uses CSS animations for smooth infinite scrolling. Logos display in grayscale and colorize on hover.
|
||||
**Display modes:**
|
||||
|
||||
- **Scrolling marquee** (`scrolling: true`): Infinite horizontal scroll animation. All logos display in a continuous loop.
|
||||
- **Static grid** (`scrolling: false`): Centered grid showing the first `maxItems` logos without animation.
|
||||
|
||||
Logos display in grayscale and colorize on hover.
|
||||
|
||||
### Blog page
|
||||
|
||||
@@ -639,13 +692,13 @@ blogPage: {
|
||||
displayOnHomepage: true, // Show posts on homepage
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| `enabled` | Enable the `/blog` route |
|
||||
| `showInNav` | Show Blog link in navigation |
|
||||
| `title` | Text for nav link and page heading |
|
||||
| `order` | Position in navigation (lower = first) |
|
||||
| `displayOnHomepage` | Show post list on homepage |
|
||||
| Option | Description |
|
||||
| ------------------- | -------------------------------------- |
|
||||
| `enabled` | Enable the `/blog` route |
|
||||
| `showInNav` | Show Blog link in navigation |
|
||||
| `title` | Text for nav link and page heading |
|
||||
| `order` | Position in navigation (lower = first) |
|
||||
| `displayOnHomepage` | Show post list on homepage |
|
||||
|
||||
**Display options:**
|
||||
|
||||
@@ -937,7 +990,7 @@ See [netlify-deploy-fix.md](https://github.com/waynesutton/markdown-site/blob/ma
|
||||
```
|
||||
markdown-site/
|
||||
├── content/
|
||||
│ ├── blog/ # Markdown blog posts
|
||||
│ ├── blog/ # Markdown posts
|
||||
│ └── pages/ # Static pages (About, Docs, etc.)
|
||||
├── convex/ # Convex backend functions
|
||||
│ ├── http.ts # HTTP endpoints
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
---
|
||||
title: "Using Images in Blog Posts"
|
||||
description: "Learn how to add header images, inline images, and Open Graph images to your markdown blog posts."
|
||||
description: "Learn how to add header images, inline images, and Open Graph images to your markdown posts."
|
||||
date: "2025-01-18"
|
||||
slug: "using-images-in-posts"
|
||||
published: true
|
||||
|
||||
@@ -3,16 +3,16 @@ title: "About"
|
||||
slug: "about"
|
||||
published: true
|
||||
order: 2
|
||||
excerpt: "An open-source markdown sync site for developers and AI agents."
|
||||
excerpt: "An open-source publishing framework for AI agents and developers."
|
||||
---
|
||||
|
||||
An open-source markdown sync site for developers and AI agents. Publish from the terminal with `npm run sync`. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.
|
||||
An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.
|
||||
|
||||
## What makes it a dev sync system
|
||||
|
||||
**File-based content.** All posts and pages live in `content/blog/` and `content/pages/` as markdown files with frontmatter. No database UI. No admin panel. Just files in your repo.
|
||||
|
||||
**CLI publishing workflow.** Write markdown locally, then run `npm run sync` (dev) or `npm run sync:prod` (production). Content appears instantly via Convex real-time sync.
|
||||
**CLI publishing workflow.** Write markdown locally, then run `npm run sync` (dev) or `npm run sync:prod` (production). Content appears instantly via Convex real-time sync. Images require git commit and push since they are served as static files from Netlify.
|
||||
|
||||
**Version controlled.** Markdown source files live in your repo alongside code. Commit changes, review diffs, roll back like any codebase. The sync command pushes content to the database.
|
||||
|
||||
@@ -55,6 +55,7 @@ It's a hybrid: developer workflow for publishing + real-time delivery like a dyn
|
||||
- Full text search with Command+K shortcut
|
||||
- Featured section with list/card view toggle and excerpts
|
||||
- Logo gallery with clickable links and marquee scroll
|
||||
- GitHub contributions graph with year navigation
|
||||
- Dedicated blog page with configurable navigation order
|
||||
- Real-time analytics at `/stats`
|
||||
- RSS feeds and sitemap for SEO
|
||||
|
||||
@@ -7,6 +7,29 @@ order: 5
|
||||
|
||||
All notable changes to this project.
|
||||
|
||||
## v1.17.0
|
||||
|
||||
Released December 20, 2025
|
||||
|
||||
**GitHub contributions graph**
|
||||
|
||||
- GitHub activity graph on homepage with theme-aware colors
|
||||
- Year navigation with Phosphor CaretLeft/CaretRight icons
|
||||
- Click graph to visit GitHub profile
|
||||
- Configurable via `siteConfig.gitHubContributions`
|
||||
- Uses public API (no GitHub token required)
|
||||
|
||||
Theme-specific contribution colors:
|
||||
|
||||
- Dark theme: GitHub green on dark background
|
||||
- Light theme: Standard GitHub green
|
||||
- Tan theme: Warm brown tones
|
||||
- Cloud theme: Gray-blue tones
|
||||
|
||||
New component: `src/components/GitHubContributions.tsx`
|
||||
|
||||
Set `enabled: false` in siteConfig to disable.
|
||||
|
||||
## v1.15.2
|
||||
|
||||
Released December 20, 2025
|
||||
@@ -356,7 +379,7 @@ Released December 14, 2025
|
||||
|
||||
**Initial release**
|
||||
|
||||
- Markdown blog posts with frontmatter parsing
|
||||
- Markdown posts with frontmatter parsing
|
||||
- Static pages support (About, Projects, Contact)
|
||||
- Four theme options: Dark, Light, Tan (default), Cloud
|
||||
- Syntax highlighting for code blocks
|
||||
|
||||
@@ -5,7 +5,7 @@ published: true
|
||||
order: 0
|
||||
---
|
||||
|
||||
Reference documentation for setting up, customizing, and deploying this markdown site.
|
||||
Reference documentation for setting up, customizing, and deploying this markdown framework.
|
||||
|
||||
**How publishing works:** Write posts in markdown, run `npm run sync` for development or `npm run sync:prod` for production, and they appear on your live site immediately. No rebuild or redeploy needed. Convex handles real-time data sync, so connected browsers update automatically.
|
||||
|
||||
@@ -140,11 +140,12 @@ npm run sync:prod
|
||||
| Pages in `content/pages/` | `npm run sync` | Instant (no rebuild) |
|
||||
| Featured items (via frontmatter) | `npm run sync` | Instant (no rebuild) |
|
||||
| Import external URL | `npm run import` then sync | Instant (no rebuild) |
|
||||
| Images in `public/images/` | Git commit + push | Requires rebuild |
|
||||
| `siteConfig` in `Home.tsx` | Redeploy | Requires rebuild |
|
||||
| Logo gallery config | Redeploy | Requires rebuild |
|
||||
| React components/styles | Redeploy | Requires rebuild |
|
||||
|
||||
**Markdown content** syncs instantly. **Source code** requires pushing to GitHub for Netlify to rebuild.
|
||||
**Markdown content** syncs instantly to Convex. **Images and source code** require pushing to GitHub for Netlify to rebuild.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -154,14 +155,15 @@ When you fork this project, update these files with your site information:
|
||||
|
||||
| File | What to update |
|
||||
|------|----------------|
|
||||
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery |
|
||||
| `convex/http.ts` | `SITE_URL`, `SITE_NAME` (API responses, sitemap) |
|
||||
| `src/config/siteConfig.ts` | Site name, title, intro, bio, blog page, logo gallery, GitHub contributions |
|
||||
| `src/pages/Home.tsx` | Intro paragraph text (hardcoded JSX) |
|
||||
| `convex/http.ts` | `SITE_URL`, `SITE_NAME`, description strings (3 locations) |
|
||||
| `convex/rss.ts` | `SITE_URL`, `SITE_TITLE`, `SITE_DESCRIPTION` (RSS feeds) |
|
||||
| `src/pages/Post.tsx` | `SITE_URL`, `SITE_NAME`, `DEFAULT_OG_IMAGE` (OG tags) |
|
||||
| `index.html` | Title, meta description, OG tags, JSON-LD |
|
||||
| `public/llms.txt` | Site name, URL, description |
|
||||
| `public/robots.txt` | Sitemap URL |
|
||||
| `public/openapi.yaml` | Server URL, site name in examples |
|
||||
| `public/llms.txt` | Site name, URL, description, topics |
|
||||
| `public/robots.txt` | Sitemap URL and header comment |
|
||||
| `public/openapi.yaml` | API title, server URL, site name in examples |
|
||||
| `public/.well-known/ai-plugin.json` | Site name, descriptions |
|
||||
|
||||
### Site title and description metadata
|
||||
@@ -172,13 +174,16 @@ These files contain the main site description text. Update them with your own ta
|
||||
|------|----------------|
|
||||
| `index.html` | meta description, og:description, twitter:description, JSON-LD |
|
||||
| `README.md` | Main description at top of file |
|
||||
| `src/pages/Home.tsx` | intro and bio text in siteConfig |
|
||||
| `convex/http.ts` | description field in API responses (2 locations) |
|
||||
| `convex/rss.ts` | SITE_DESCRIPTION constant |
|
||||
| `public/llms.txt` | Header quote and Description field |
|
||||
| `src/config/siteConfig.ts` | name, title, and bio fields |
|
||||
| `src/pages/Home.tsx` | Intro paragraph (hardcoded JSX with links) |
|
||||
| `convex/http.ts` | SITE_NAME constant and description strings (3 locations) |
|
||||
| `convex/rss.ts` | SITE_TITLE and SITE_DESCRIPTION constants |
|
||||
| `public/llms.txt` | Header quote, Name, and Description fields |
|
||||
| `public/openapi.yaml` | API title and example site name |
|
||||
| `AGENTS.md` | Project overview section |
|
||||
| `content/blog/about-this-blog.md` | Opening paragraph |
|
||||
| `content/blog/about-this-blog.md` | Title, description, excerpt, and opening paragraph |
|
||||
| `content/pages/about.md` | excerpt field and opening paragraph |
|
||||
| `content/pages/docs.md` | Opening description paragraph |
|
||||
|
||||
**Backend constants** (`convex/http.ts` and `convex/rss.ts`):
|
||||
|
||||
@@ -228,13 +233,15 @@ export default {
|
||||
featuredViewMode: "list", // 'list' or 'cards'
|
||||
showViewToggle: true,
|
||||
|
||||
// Logo gallery (with clickable links)
|
||||
// Logo gallery (static grid or scrolling marquee)
|
||||
logoGallery: {
|
||||
enabled: true, // false to hide
|
||||
images: [{ src: "/images/logos/logo.svg", href: "https://example.com" }],
|
||||
position: "above-footer",
|
||||
speed: 30,
|
||||
title: "Trusted by",
|
||||
title: "Built with",
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
|
||||
links: {
|
||||
@@ -283,12 +290,36 @@ const siteConfig = {
|
||||
};
|
||||
```
|
||||
|
||||
### Logo gallery
|
||||
### GitHub contributions graph
|
||||
|
||||
The homepage includes a scrolling logo marquee with sample logos. Each logo can link to a URL.
|
||||
Display your GitHub contribution activity on the homepage. Configure in `siteConfig`:
|
||||
|
||||
```typescript
|
||||
// In src/pages/Home.tsx
|
||||
gitHubContributions: {
|
||||
enabled: true, // Set to false to hide
|
||||
username: "yourusername", // Your GitHub username
|
||||
showYearNavigation: true, // Show arrows to navigate between years
|
||||
linkToProfile: true, // Click graph to open GitHub profile
|
||||
title: "GitHub Activity", // Optional title above the graph
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ------ | ----------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `username` | Your GitHub username |
|
||||
| `showYearNavigation` | Show prev/next year navigation |
|
||||
| `linkToProfile` | Click graph to visit GitHub profile |
|
||||
| `title` | Text above graph (`undefined` to hide) |
|
||||
|
||||
Theme-aware colors match each site theme. Uses public API (no GitHub token required).
|
||||
|
||||
### Logo gallery
|
||||
|
||||
The homepage includes a logo gallery that can scroll infinitely or display as a static grid. Each logo can link to a URL.
|
||||
|
||||
```typescript
|
||||
// In src/config/siteConfig.ts
|
||||
logoGallery: {
|
||||
enabled: true, // false to hide
|
||||
images: [
|
||||
@@ -297,17 +328,26 @@ logoGallery: {
|
||||
],
|
||||
position: "above-footer", // or 'below-featured'
|
||||
speed: 30, // Seconds for one scroll cycle
|
||||
title: "Trusted by", // undefined to hide
|
||||
title: "Built with", // undefined to hide
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | --------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of `{ src, href }` objects |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (`undefined` to hide) |
|
||||
| Option | Description |
|
||||
| ----------- | -------------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of `{ src, href }` objects |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (`undefined` to hide) |
|
||||
| `scrolling` | `true` for infinite scroll, `false` for static grid |
|
||||
| `maxItems` | Max logos to show when `scrolling` is `false` (default: 4) |
|
||||
|
||||
**Display modes:**
|
||||
|
||||
- `scrolling: true`: Infinite horizontal scroll with all logos
|
||||
- `scrolling: false`: Static centered grid showing first `maxItems` logos
|
||||
|
||||
**To add logos:**
|
||||
|
||||
@@ -419,6 +459,14 @@ Mobile sizes defined in `@media (max-width: 768px)` block.
|
||||
| Default OG image | `public/images/og-default.svg` | 1200x630 |
|
||||
| Post images | `public/images/` | Any |
|
||||
|
||||
**Images require git deploy.** Images are served as static files from your repository, not synced to Convex. After adding images to `public/images/`:
|
||||
|
||||
1. Commit the image files to git
|
||||
2. Push to GitHub
|
||||
3. Wait for Netlify to rebuild
|
||||
|
||||
The `npm run sync` command only syncs markdown text content. Images are deployed when Netlify builds your site.
|
||||
|
||||
## Search
|
||||
|
||||
Press `Command+K` (Mac) or `Ctrl+K` (Windows/Linux) to open the search modal. Click the search icon in the nav or use the keyboard shortcut.
|
||||
|
||||
@@ -5,7 +5,7 @@ published: true
|
||||
order: 3
|
||||
---
|
||||
|
||||
This markdown site is open source and built to be extended. Here is what ships out of the box.
|
||||
This markdown framework is open source and built to be extended. Here is what ships out of the box.
|
||||
|
||||
## Core Features
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const http = httpRouter();
|
||||
|
||||
// Site configuration
|
||||
const SITE_URL = process.env.SITE_URL || "https://markdowncms.netlify.app";
|
||||
const SITE_NAME = "markdown sync site";
|
||||
const SITE_NAME = "markdown sync framework";
|
||||
|
||||
// RSS feed endpoint (descriptions only)
|
||||
http.route({
|
||||
@@ -72,7 +72,7 @@ http.route({
|
||||
const response = {
|
||||
site: SITE_NAME,
|
||||
url: SITE_URL,
|
||||
description: "An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.",
|
||||
description: "An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.",
|
||||
posts: posts.map((post) => ({
|
||||
title: post.title,
|
||||
slug: post.slug,
|
||||
@@ -194,7 +194,7 @@ http.route({
|
||||
const response = {
|
||||
site: SITE_NAME,
|
||||
url: SITE_URL,
|
||||
description: "An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.",
|
||||
description: "An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.",
|
||||
exportedAt: new Date().toISOString(),
|
||||
totalPosts: fullPosts.length,
|
||||
posts: fullPosts,
|
||||
@@ -231,7 +231,7 @@ function generateMetaHtml(content: {
|
||||
type?: "post" | "page";
|
||||
}): string {
|
||||
const siteUrl = process.env.SITE_URL || "https://markdowncms.netlify.app";
|
||||
const siteName = "markdown sync site";
|
||||
const siteName = "markdown sync framework";
|
||||
const defaultImage = `${siteUrl}/images/og-default.svg`;
|
||||
const canonicalUrl = `${siteUrl}/${content.slug}`;
|
||||
|
||||
|
||||
@@ -3,9 +3,9 @@ import { api } from "./_generated/api";
|
||||
|
||||
// Site configuration for RSS feed
|
||||
const SITE_URL = process.env.SITE_URL || "https://markdowncms.netlify.app";
|
||||
const SITE_TITLE = "markdown sync site";
|
||||
const SITE_TITLE = "markdown sync framework";
|
||||
const SITE_DESCRIPTION =
|
||||
"An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.";
|
||||
"An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.";
|
||||
|
||||
// Escape XML special characters
|
||||
function escapeXml(text: string): string {
|
||||
|
||||
31
files.md
31
files.md
@@ -29,9 +29,9 @@ A brief description of each file in the codebase.
|
||||
|
||||
### Config (`src/config/`)
|
||||
|
||||
| File | Description |
|
||||
| --------------- | -------------------------------------------------------------------------------- |
|
||||
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display, nav order) |
|
||||
| File | Description |
|
||||
| --------------- | --------------------------------------------------------------------------------------------------------- |
|
||||
| `siteConfig.ts` | Centralized site configuration (name, logo, blog page, posts display, GitHub contributions, nav order) |
|
||||
|
||||
### Pages (`src/pages/`)
|
||||
|
||||
@@ -45,18 +45,19 @@ A brief description of each file in the codebase.
|
||||
|
||||
### Components (`src/components/`)
|
||||
|
||||
| File | Description |
|
||||
| ---------------------- | ---------------------------------------------------------- |
|
||||
| `Layout.tsx` | Page wrapper with search button, theme toggle, mobile menu, and scroll-to-top |
|
||||
| `ThemeToggle.tsx` | Theme switcher (dark/light/tan/cloud) |
|
||||
| `PostList.tsx` | Year-grouped blog post list |
|
||||
| `BlogPost.tsx` | Markdown renderer with syntax highlighting |
|
||||
| `CopyPageDropdown.tsx` | Share dropdown for LLMs (ChatGPT, Claude, Perplexity) with View as Markdown and Generate Skill options |
|
||||
| `SearchModal.tsx` | Full text search modal with keyboard navigation |
|
||||
| `FeaturedCards.tsx` | Card grid for featured posts/pages with excerpts |
|
||||
| `LogoMarquee.tsx` | Scrolling logo gallery with clickable links |
|
||||
| `MobileMenu.tsx` | Slide-out drawer menu for mobile navigation with hamburger button |
|
||||
| `ScrollToTop.tsx` | Configurable scroll-to-top button with Phosphor ArrowUp icon |
|
||||
| File | Description |
|
||||
| ------------------------- | ---------------------------------------------------------- |
|
||||
| `Layout.tsx` | Page wrapper with search button, theme toggle, mobile menu, and scroll-to-top |
|
||||
| `ThemeToggle.tsx` | Theme switcher (dark/light/tan/cloud) |
|
||||
| `PostList.tsx` | Year-grouped blog post list |
|
||||
| `BlogPost.tsx` | Markdown renderer with syntax highlighting |
|
||||
| `CopyPageDropdown.tsx` | Share dropdown for LLMs (ChatGPT, Claude, Perplexity) with View as Markdown and Generate Skill options |
|
||||
| `SearchModal.tsx` | Full text search modal with keyboard navigation |
|
||||
| `FeaturedCards.tsx` | Card grid for featured posts/pages with excerpts |
|
||||
| `LogoMarquee.tsx` | Scrolling logo gallery with clickable links |
|
||||
| `MobileMenu.tsx` | Slide-out drawer menu for mobile navigation with hamburger button |
|
||||
| `ScrollToTop.tsx` | Configurable scroll-to-top button with Phosphor ArrowUp icon |
|
||||
| `GitHubContributions.tsx` | GitHub activity graph with theme-aware colors and year navigation |
|
||||
|
||||
### Context (`src/context/`)
|
||||
|
||||
|
||||
22
index.html
22
index.html
@@ -8,9 +8,9 @@
|
||||
<!-- SEO Meta Tags -->
|
||||
<meta
|
||||
name="description"
|
||||
content="An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify."
|
||||
content="An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify."
|
||||
/>
|
||||
<meta name="author" content="markdown sync site" />
|
||||
<meta name="author" content="markdown sync framework" />
|
||||
<meta
|
||||
name="keywords"
|
||||
content="markdown site, Convex, Netlify, React, TypeScript, open source, real-time, sync"
|
||||
@@ -21,14 +21,14 @@
|
||||
<meta name="theme-color" content="#faf8f5" />
|
||||
|
||||
<!-- Open Graph -->
|
||||
<meta property="og:title" content="markdown sync site" />
|
||||
<meta property="og:title" content="markdown sync framework" />
|
||||
<meta
|
||||
property="og:description"
|
||||
content="An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify."
|
||||
content="An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify."
|
||||
/>
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content="https://markdowncms.netlify.app/" />
|
||||
<meta property="og:site_name" content="markdown sync site" />
|
||||
<meta property="og:site_name" content="markdown sync framework" />
|
||||
<meta
|
||||
property="og:image"
|
||||
content="https://markdowncms.netlify.app/images/og-default.svg"
|
||||
@@ -38,10 +38,10 @@
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
<meta property="twitter:domain" content="markdowncms.netlify.app" />
|
||||
<meta property="twitter:url" content="https://markdowncms.netlify.app/" />
|
||||
<meta name="twitter:title" content="markdown sync site" />
|
||||
<meta name="twitter:title" content="markdown sync framework" />
|
||||
<meta
|
||||
name="twitter:description"
|
||||
content="An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify."
|
||||
content="An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify."
|
||||
/>
|
||||
<meta
|
||||
name="twitter:image"
|
||||
@@ -70,12 +70,12 @@
|
||||
{
|
||||
"@context": "https://schema.org",
|
||||
"@type": "WebSite",
|
||||
"name": "markdown sync site",
|
||||
"name": "markdown sync framework",
|
||||
"url": "https://markdowncms.netlify.app",
|
||||
"description": "An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.",
|
||||
"description": "An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.",
|
||||
"author": {
|
||||
"@type": "Organization",
|
||||
"name": "markdown sync site",
|
||||
"name": "markdown sync framework",
|
||||
"url": "https://markdowncms.netlify.app"
|
||||
},
|
||||
"potentialAction": {
|
||||
@@ -86,7 +86,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<title>markdown "sync" site</title>
|
||||
<title>markdown "sync" framework</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
||||
|
Before Width: | Height: | Size: 505 B After Width: | Height: | Size: 505 B |
11
public/images/logos/markdown.svg
Normal file
11
public/images/logos/markdown.svg
Normal file
@@ -0,0 +1,11 @@
|
||||
<svg width="113" height="70" viewBox="0 0 113 70" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1027_8)">
|
||||
<path d="M104.849 2.71631H8.14754C5.14714 2.71631 2.71484 5.14861 2.71484 8.149V61.3894C2.71484 64.3898 5.14714 66.8221 8.14754 66.8221H104.849C107.85 66.8221 110.282 64.3898 110.282 61.3894V8.149C110.282 5.14861 107.85 2.71631 104.849 2.71631Z" stroke="black" stroke-width="5.43269"/>
|
||||
<path d="M16.2969 53.2404V16.2981H27.1623L38.0276 29.8798L48.893 16.2981H59.7584V53.2404H48.893V32.0529L38.0276 45.6346L27.1623 32.0529V53.2404H16.2969ZM84.2055 53.2404L67.9075 35.3125H78.7728V16.2981H89.6382V35.3125H100.504L84.2055 53.2404Z" fill="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1027_8">
|
||||
<rect width="113" height="69.5385" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 795 B |
27
public/images/logos/netlify.svg
Normal file
27
public/images/logos/netlify.svg
Normal file
@@ -0,0 +1,27 @@
|
||||
<svg width="108" height="44" viewBox="0 0 108 44" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1027_29)">
|
||||
<mask id="mask0_1027_29" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="0" y="0" width="108" height="44">
|
||||
<path d="M107.789 0H0V43.816H107.789V0Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_1027_29)">
|
||||
<path d="M24.7227 43.5865V32.5482L24.9528 32.3179H27.2523L27.4824 32.5482V43.5865L27.2523 43.8168H24.9528L24.7227 43.5865Z" fill="black"/>
|
||||
<path d="M24.7227 11.2679V0.230187L24.9528 0H27.2523L27.4824 0.230187V11.2679L27.2523 11.4981H24.9528L24.7227 11.2679Z" fill="black"/>
|
||||
<path d="M14.7283 35.629H14.4031L12.7773 34.0024V33.6772L16.5738 29.8816L18.2954 29.8824L18.5264 30.1117V31.8334L14.7283 35.629Z" fill="black"/>
|
||||
<path d="M14.7283 8.1875H14.4031L12.7773 9.81411V10.1393L16.5738 13.9349L18.2954 13.9341L18.5264 13.7047V11.9832L14.7283 8.1875Z" fill="black"/>
|
||||
<path d="M0.230187 20.5283H15.8676L16.0978 20.7585V23.0579L15.8676 23.2882H0.230187L0 23.0579V20.7585L0.230187 20.5283Z" fill="black"/>
|
||||
<path d="M92.8409 20.5283H107.559L107.789 20.7585V23.0579L107.559 23.2882H91.9217L91.6914 23.0579L92.6106 20.7585L92.8409 20.5283Z" fill="black"/>
|
||||
<path d="M44.6433 22.8899L44.4132 23.1202H37.2798L37.0497 23.3503C37.0497 23.8107 37.5101 25.191 39.3507 25.191C40.0412 25.191 40.7309 24.9609 40.9612 24.5004L41.1913 24.2703H43.9528L44.1829 24.5004C43.9528 25.8809 42.8027 27.9524 39.3507 27.9524C35.4391 27.9524 33.5977 25.191 33.5977 21.9699C33.5977 18.749 35.4383 15.9875 39.1204 15.9875C42.8027 15.9875 44.6433 18.749 44.6433 21.9699V22.8907V22.8899ZM41.1913 20.5889C41.1913 20.3587 40.9612 18.7482 39.1204 18.7482C37.2798 18.7482 37.0497 20.3587 37.0497 20.5889L37.2798 20.8191H40.9612L41.1913 20.5889Z" fill="black"/>
|
||||
<path d="M51.0889 24.2705C51.0889 24.7307 51.319 24.961 51.7794 24.961H53.8501L54.0805 25.1911V27.4922L53.8501 27.7225H51.7794C49.7085 27.7225 47.8678 26.8017 47.8678 24.2705V19.2079L47.6375 18.9778H46.0272L45.7969 18.7476V16.4465L46.0272 16.2163H47.6375L47.8678 15.9861V13.9152L48.0979 13.6851H50.8594L51.0895 13.9152V15.9861L51.3198 16.2163H53.851L54.0813 16.4465V18.7476L53.851 18.9778H51.3198L51.0895 19.2079V24.2705H51.0889Z" fill="black"/>
|
||||
<path d="M59.5973 27.7223H56.8358L56.6055 27.492V11.8449L56.8358 11.6147H59.5973L59.8274 11.8449V27.492L59.5973 27.7223Z" fill="black"/>
|
||||
<path d="M65.816 14.3762H63.0545L62.8242 14.146V11.8449L63.0545 11.6147H65.816L66.0461 11.8449V14.146L65.816 14.3762ZM65.816 27.7223H63.0545L62.8242 27.492V16.4471L63.0545 16.2169H65.816L66.0461 16.4471V27.492L65.816 27.7223Z" fill="black"/>
|
||||
<path d="M76.6282 11.8449V14.146L76.3979 14.3762H74.3271C73.8667 14.3762 73.6364 14.6064 73.6364 15.0667V15.9875L73.8667 16.2177H76.1678L76.3979 16.4479V18.7489L76.1678 18.9791H73.8667L73.6364 19.2093V27.4928L73.4063 27.7229H70.6448L70.4147 27.4928V19.2093L70.1844 18.9791H68.5741L68.3438 18.7489V16.4479L68.5741 16.2177H70.1844L70.4147 15.9875V15.0667C70.4147 12.5355 72.2553 11.6147 74.3263 11.6147H76.397L76.6273 11.8449H76.6282Z" fill="black"/>
|
||||
<path d="M85.1413 27.9526C84.2204 30.2537 83.3006 31.6341 80.0788 31.6341H78.9278L78.6977 31.4038V29.1027L78.9278 28.8726H80.0788C81.2289 28.8726 81.459 28.6423 81.6893 27.9518V27.7217L78.0078 18.7476V16.4465L78.2381 16.2163H80.3089L80.5392 16.4465L83.3007 24.2705H83.5308L86.2922 16.4465L86.5223 16.2163H88.5933L88.8234 16.4465V18.7476L85.1421 27.9518L85.1413 27.9526Z" fill="black"/>
|
||||
<path d="M28.5216 27.7222L28.2914 27.4919L28.2929 20.8246C28.2929 19.6745 27.8407 18.7827 26.4523 18.7537C25.7384 18.7352 24.9216 18.7521 24.0489 18.7891L23.9186 18.9227L23.9203 27.4919L23.69 27.7222H20.9294L20.6992 27.4919V16.3239L20.9294 16.0937L27.142 16.0374C30.2544 16.0374 31.514 18.1758 31.514 20.5896V27.4919L31.2839 27.7222H28.5216Z" fill="black"/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1027_29">
|
||||
<rect width="107.789" height="44" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
13
public/images/logos/react.svg
Normal file
13
public/images/logos/react.svg
Normal file
@@ -0,0 +1,13 @@
|
||||
<svg width="23" height="21" viewBox="0 0 23 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_1027_2)">
|
||||
<path d="M11.4992 12.2816C12.6314 12.2816 13.5492 11.3638 13.5492 10.2316C13.5492 9.09946 12.6314 8.18164 11.4992 8.18164C10.367 8.18164 9.44922 9.09946 9.44922 10.2316C9.44922 11.3638 10.367 12.2816 11.4992 12.2816Z" fill="black"/>
|
||||
<path d="M11.5 14.4317C17.5751 14.4317 22.5 12.5513 22.5 10.2317C22.5 7.91214 17.5751 6.03174 11.5 6.03174C5.42487 6.03174 0.5 7.91214 0.5 10.2317C0.5 12.5513 5.42487 14.4317 11.5 14.4317Z" stroke="black"/>
|
||||
<path d="M7.86211 12.3317C10.8997 17.593 14.9906 20.9178 16.9994 19.758C19.0082 18.5982 18.1743 13.393 15.1367 8.13175C12.0992 2.87053 8.00824 -0.454329 5.99941 0.705469C3.99058 1.86527 4.82454 7.07053 7.86211 12.3317Z" stroke="black"/>
|
||||
<path d="M7.86211 8.1317C4.82454 13.3929 3.99058 18.5982 5.99941 19.758C8.00824 20.9178 12.0992 17.5929 15.1367 12.3317C18.1743 7.07048 19.0082 1.86522 16.9994 0.70542C14.9906 -0.454378 10.8997 2.87048 7.86211 8.1317Z" stroke="black"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_1027_2">
|
||||
<rect width="23" height="20.4635" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -1,13 +1,13 @@
|
||||
# llms.txt - Information for AI assistants and LLMs
|
||||
# Learn more: https://llmstxt.org/
|
||||
|
||||
> An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync.
|
||||
> An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal.
|
||||
|
||||
# Site Information
|
||||
- Name: markdown sync site
|
||||
- Name: markdown sync framework
|
||||
- URL: https://markdowncms.netlify.app
|
||||
- Description: An open-source markdown sync site for developers and AI agents. Publish from the terminal with npm run sync. Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.
|
||||
- Topics: Markdown, Convex, React, TypeScript, Netlify, Open Source
|
||||
- Description: An open-source publishing framework for AI agents and developers. Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents. Built on Convex and Netlify.
|
||||
- Topics: Markdown, Convex, React, TypeScript, Netlify, Open Source, AI, LLM, AEO, GEO
|
||||
|
||||
# API Endpoints
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: markdown sync site API
|
||||
title: markdown sync framework API
|
||||
description: |
|
||||
API for accessing blog posts and pages as markdown content.
|
||||
All endpoints return JSON by default. Use format=md for raw markdown.
|
||||
@@ -28,7 +28,7 @@ paths:
|
||||
properties:
|
||||
site:
|
||||
type: string
|
||||
example: markdown sync site
|
||||
example: markdown sync framework
|
||||
url:
|
||||
type: string
|
||||
example: https://markdowncms.netlify.app
|
||||
|
||||
@@ -11,7 +11,7 @@ An open-source markdown sync site for developers and AI agents. Publish from the
|
||||
|
||||
**File-based content.** All posts and pages live in `content/blog/` and `content/pages/` as markdown files with frontmatter. No database UI. No admin panel. Just files in your repo.
|
||||
|
||||
**CLI publishing workflow.** Write markdown locally, then run `npm run sync` (dev) or `npm run sync:prod` (production). Content appears instantly via Convex real-time sync.
|
||||
**CLI publishing workflow.** Write markdown locally, then run `npm run sync` (dev) or `npm run sync:prod` (production). Content appears instantly via Convex real-time sync. Images require git commit and push since they are served as static files from Netlify.
|
||||
|
||||
**Version controlled.** Markdown source files live in your repo alongside code. Commit changes, review diffs, roll back like any codebase. The sync command pushes content to the database.
|
||||
|
||||
|
||||
@@ -140,11 +140,12 @@ npm run sync:prod
|
||||
| Pages in `content/pages/` | `npm run sync` | Instant (no rebuild) |
|
||||
| Featured items (via frontmatter) | `npm run sync` | Instant (no rebuild) |
|
||||
| Import external URL | `npm run import` then sync | Instant (no rebuild) |
|
||||
| Images in `public/images/` | Git commit + push | Requires rebuild |
|
||||
| `siteConfig` in `Home.tsx` | Redeploy | Requires rebuild |
|
||||
| Logo gallery config | Redeploy | Requires rebuild |
|
||||
| React components/styles | Redeploy | Requires rebuild |
|
||||
|
||||
**Markdown content** syncs instantly. **Source code** requires pushing to GitHub for Netlify to rebuild.
|
||||
**Markdown content** syncs instantly to Convex. **Images and source code** require pushing to GitHub for Netlify to rebuild.
|
||||
|
||||
## Configuration
|
||||
|
||||
@@ -228,13 +229,15 @@ export default {
|
||||
featuredViewMode: "list", // 'list' or 'cards'
|
||||
showViewToggle: true,
|
||||
|
||||
// Logo gallery (with clickable links)
|
||||
// Logo gallery (static grid or scrolling marquee)
|
||||
logoGallery: {
|
||||
enabled: true, // false to hide
|
||||
images: [{ src: "/images/logos/logo.svg", href: "https://example.com" }],
|
||||
position: "above-footer",
|
||||
speed: 30,
|
||||
title: "Trusted by",
|
||||
title: "Built with",
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
|
||||
links: {
|
||||
@@ -285,10 +288,10 @@ const siteConfig = {
|
||||
|
||||
### Logo gallery
|
||||
|
||||
The homepage includes a scrolling logo marquee with sample logos. Each logo can link to a URL.
|
||||
The homepage includes a logo gallery that can scroll infinitely or display as a static grid. Each logo can link to a URL.
|
||||
|
||||
```typescript
|
||||
// In src/pages/Home.tsx
|
||||
// In src/config/siteConfig.ts
|
||||
logoGallery: {
|
||||
enabled: true, // false to hide
|
||||
images: [
|
||||
@@ -297,17 +300,26 @@ logoGallery: {
|
||||
],
|
||||
position: "above-footer", // or 'below-featured'
|
||||
speed: 30, // Seconds for one scroll cycle
|
||||
title: "Trusted by", // undefined to hide
|
||||
title: "Built with", // undefined to hide
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
```
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | --------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of `{ src, href }` objects |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (`undefined` to hide) |
|
||||
| Option | Description |
|
||||
| ----------- | -------------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of `{ src, href }` objects |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (`undefined` to hide) |
|
||||
| `scrolling` | `true` for infinite scroll, `false` for static grid |
|
||||
| `maxItems` | Max logos to show when `scrolling` is `false` (default: 4) |
|
||||
|
||||
**Display modes:**
|
||||
|
||||
- `scrolling: true`: Infinite horizontal scroll with all logos
|
||||
- `scrolling: false`: Static centered grid showing first `maxItems` logos
|
||||
|
||||
**To add logos:**
|
||||
|
||||
@@ -419,6 +431,14 @@ Mobile sizes defined in `@media (max-width: 768px)` block.
|
||||
| Default OG image | `public/images/og-default.svg` | 1200x630 |
|
||||
| Post images | `public/images/` | Any |
|
||||
|
||||
**Images require git deploy.** Images are served as static files from your repository, not synced to Convex. After adding images to `public/images/`:
|
||||
|
||||
1. Commit the image files to git
|
||||
2. Push to GitHub
|
||||
3. Wait for Netlify to rebuild
|
||||
|
||||
The `npm run sync` command only syncs markdown text content. Images are deployed when Netlify builds your site.
|
||||
|
||||
## Search
|
||||
|
||||
Press `Command+K` (Mac) or `Ctrl+K` (Windows/Linux) to open the search modal. Click the search icon in the nav or use the keyboard shortcut.
|
||||
|
||||
@@ -338,6 +338,14 @@ This image appears when sharing on social media. Recommended: 1200x630 pixels.
|
||||

|
||||
```
|
||||
|
||||
**Images require git deploy.** Images are served as static files from your repository, not synced to Convex. After adding images to `public/images/`:
|
||||
|
||||
1. Commit the image files to git
|
||||
2. Push to GitHub
|
||||
3. Wait for Netlify to rebuild
|
||||
|
||||
The `npm run sync` command only syncs markdown text content. Images are deployed when Netlify builds your site.
|
||||
|
||||
### Sync After Adding Posts
|
||||
|
||||
After adding or editing posts, sync to Convex.
|
||||
@@ -381,11 +389,12 @@ Both files are gitignored. Each developer creates their own local environment fi
|
||||
| Pages in `content/pages/` | `npm run sync` | Instant (no rebuild) |
|
||||
| Featured items (via frontmatter) | `npm run sync` | Instant (no rebuild) |
|
||||
| Import external URL | `npm run import` then sync | Instant (no rebuild) |
|
||||
| Images in `public/images/` | Git commit + push | Requires rebuild |
|
||||
| `siteConfig` in `Home.tsx` | Redeploy | Requires rebuild |
|
||||
| Logo gallery config | Redeploy | Requires rebuild |
|
||||
| React components/styles | Redeploy | Requires rebuild |
|
||||
|
||||
**Markdown content** syncs instantly via Convex. **Source code changes** require pushing to GitHub for Netlify to rebuild.
|
||||
**Markdown content** syncs instantly via Convex. **Images and source code** require pushing to GitHub for Netlify to rebuild.
|
||||
|
||||
**Featured items** can now be controlled via markdown frontmatter. Add `featured: true` and `featuredOrder: 1` to any post or page, then run `npm run sync`.
|
||||
|
||||
@@ -512,7 +521,7 @@ export default {
|
||||
featuredViewMode: "list", // 'list' or 'cards'
|
||||
showViewToggle: true, // Let users switch between views
|
||||
|
||||
// Logo gallery (marquee scroll with clickable links)
|
||||
// Logo gallery (static grid or scrolling marquee with clickable links)
|
||||
logoGallery: {
|
||||
enabled: true, // Set false to hide
|
||||
images: [
|
||||
@@ -521,7 +530,9 @@ export default {
|
||||
],
|
||||
position: "above-footer", // or 'below-featured'
|
||||
speed: 30, // Seconds for one scroll cycle
|
||||
title: "Trusted by",
|
||||
title: "Built with",
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos when scrolling is false
|
||||
},
|
||||
|
||||
links: {
|
||||
@@ -572,7 +583,7 @@ Users can toggle between list and card views using the icon button next to "Get
|
||||
|
||||
### Logo Gallery
|
||||
|
||||
The homepage includes a scrolling logo gallery with 5 sample logos. Customize or disable it in siteConfig:
|
||||
The homepage includes a logo gallery that can scroll infinitely or display as a static grid. Customize or disable it in siteConfig:
|
||||
|
||||
**Disable the gallery:**
|
||||
|
||||
@@ -597,7 +608,9 @@ logoGallery: {
|
||||
],
|
||||
position: "above-footer",
|
||||
speed: 30,
|
||||
title: "Trusted by",
|
||||
title: "Built with",
|
||||
scrolling: false, // false = static grid, true = scrolling marquee
|
||||
maxItems: 4, // Number of logos to show when scrolling is false
|
||||
},
|
||||
```
|
||||
|
||||
@@ -612,15 +625,22 @@ Delete the sample files from `public/images/logos/` and clear the images array,
|
||||
|
||||
**Configuration options:**
|
||||
|
||||
| Option | Description |
|
||||
| ---------- | ---------------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of logo objects with `src` and optional `href` |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (set to `undefined` to hide) |
|
||||
| Option | Description |
|
||||
| ----------- | ---------------------------------------------------- |
|
||||
| `enabled` | `true` to show, `false` to hide |
|
||||
| `images` | Array of logo objects with `src` and optional `href` |
|
||||
| `position` | `'above-footer'` or `'below-featured'` |
|
||||
| `speed` | Seconds for one scroll cycle (lower = faster) |
|
||||
| `title` | Text above gallery (set to `undefined` to hide) |
|
||||
| `scrolling` | `true` for infinite scroll, `false` for static grid |
|
||||
| `maxItems` | Max logos to show when `scrolling` is `false` (default: 4) |
|
||||
|
||||
The gallery uses CSS animations for smooth infinite scrolling. Logos display in grayscale and colorize on hover.
|
||||
**Display modes:**
|
||||
|
||||
- **Scrolling marquee** (`scrolling: true`): Infinite horizontal scroll animation. All logos display in a continuous loop.
|
||||
- **Static grid** (`scrolling: false`): Centered grid showing the first `maxItems` logos without animation.
|
||||
|
||||
Logos display in grayscale and colorize on hover.
|
||||
|
||||
### Blog page
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# robots.txt for markdown sync site
|
||||
# robots.txt for markdown sync framework
|
||||
# https://www.robotstxt.org/
|
||||
|
||||
User-agent: *
|
||||
|
||||
296
src/components/GitHubContributions.tsx
Normal file
296
src/components/GitHubContributions.tsx
Normal file
@@ -0,0 +1,296 @@
|
||||
// GitHub contributions graph component
|
||||
// Fetches and displays contribution data with theme-aware colors
|
||||
|
||||
import { useState, useEffect, useCallback } from "react";
|
||||
import { CaretLeft, CaretRight } from "@phosphor-icons/react";
|
||||
import type { GitHubContributionsConfig } from "../config/siteConfig";
|
||||
|
||||
// Contribution data structure from the API
|
||||
interface ContributionDay {
|
||||
date: string;
|
||||
count: number;
|
||||
level: 0 | 1 | 2 | 3 | 4;
|
||||
}
|
||||
|
||||
interface ContributionWeek {
|
||||
days: ContributionDay[];
|
||||
}
|
||||
|
||||
interface APIResponse {
|
||||
total: Record<string, number>;
|
||||
contributions: ContributionDay[];
|
||||
}
|
||||
|
||||
interface GitHubContributionsProps {
|
||||
config: GitHubContributionsConfig;
|
||||
}
|
||||
|
||||
// Month labels for the graph header
|
||||
const MONTHS = [
|
||||
"Jan",
|
||||
"Feb",
|
||||
"Mar",
|
||||
"Apr",
|
||||
"May",
|
||||
"Jun",
|
||||
"Jul",
|
||||
"Aug",
|
||||
"Sep",
|
||||
"Oct",
|
||||
"Nov",
|
||||
"Dec",
|
||||
];
|
||||
|
||||
// Day labels for the left side
|
||||
const DAYS = ["", "Mon", "", "Wed", "", "Fri", ""];
|
||||
|
||||
export default function GitHubContributions({
|
||||
config,
|
||||
}: GitHubContributionsProps) {
|
||||
const currentYear = new Date().getFullYear();
|
||||
const [year, setYear] = useState(currentYear);
|
||||
const [contributions, setContributions] = useState<ContributionDay[]>([]);
|
||||
const [totalContributions, setTotalContributions] = useState(0);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// Fetch contributions from the API
|
||||
const fetchContributions = useCallback(async () => {
|
||||
if (!config.enabled || !config.username) return;
|
||||
|
||||
setLoading(true);
|
||||
setError(null);
|
||||
|
||||
try {
|
||||
const response = await fetch(
|
||||
`https://github-contributions-api.jogruber.de/v4/${config.username}?y=${year}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error("Failed to fetch contributions");
|
||||
}
|
||||
|
||||
const data: APIResponse = await response.json();
|
||||
setContributions(data.contributions || []);
|
||||
setTotalContributions(data.total?.[year.toString()] || 0);
|
||||
} catch {
|
||||
setError("Unable to load GitHub contributions");
|
||||
setContributions([]);
|
||||
setTotalContributions(0);
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}, [config.enabled, config.username, year]);
|
||||
|
||||
// Fetch on mount and when year changes
|
||||
useEffect(() => {
|
||||
fetchContributions();
|
||||
}, [fetchContributions]);
|
||||
|
||||
// Don't render if disabled
|
||||
if (!config.enabled) return null;
|
||||
|
||||
// Navigate to previous year
|
||||
const goToPreviousYear = () => {
|
||||
if (year > currentYear - 10) {
|
||||
setYear((y) => y - 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Navigate to next year
|
||||
const goToNextYear = () => {
|
||||
if (year < currentYear) {
|
||||
setYear((y) => y + 1);
|
||||
}
|
||||
};
|
||||
|
||||
// Group contributions into weeks for grid display
|
||||
const getWeeks = (): ContributionWeek[] => {
|
||||
if (contributions.length === 0) return [];
|
||||
|
||||
const weeks: ContributionWeek[] = [];
|
||||
let currentWeek: ContributionDay[] = [];
|
||||
|
||||
// Get the first day of the year to determine padding
|
||||
const firstDay = new Date(year, 0, 1);
|
||||
const startDayOfWeek = firstDay.getDay(); // 0 = Sunday
|
||||
|
||||
// Add empty days for alignment
|
||||
for (let i = 0; i < startDayOfWeek; i++) {
|
||||
currentWeek.push({ date: "", count: 0, level: 0 });
|
||||
}
|
||||
|
||||
// Add all contribution days
|
||||
for (const day of contributions) {
|
||||
currentWeek.push(day);
|
||||
if (currentWeek.length === 7) {
|
||||
weeks.push({ days: currentWeek });
|
||||
currentWeek = [];
|
||||
}
|
||||
}
|
||||
|
||||
// Add remaining days to last week
|
||||
if (currentWeek.length > 0) {
|
||||
while (currentWeek.length < 7) {
|
||||
currentWeek.push({ date: "", count: 0, level: 0 });
|
||||
}
|
||||
weeks.push({ days: currentWeek });
|
||||
}
|
||||
|
||||
return weeks;
|
||||
};
|
||||
|
||||
// Calculate month label positions
|
||||
const getMonthLabels = () => {
|
||||
const weeks = getWeeks();
|
||||
const labels: { month: string; position: number }[] = [];
|
||||
let lastMonth = -1;
|
||||
|
||||
weeks.forEach((week, weekIndex) => {
|
||||
const firstValidDay = week.days.find((d) => d.date);
|
||||
if (firstValidDay) {
|
||||
const date = new Date(firstValidDay.date);
|
||||
const month = date.getMonth();
|
||||
if (month !== lastMonth) {
|
||||
labels.push({ month: MONTHS[month], position: weekIndex });
|
||||
lastMonth = month;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return labels;
|
||||
};
|
||||
|
||||
const weeks = getWeeks();
|
||||
const monthLabels = getMonthLabels();
|
||||
const profileUrl = `https://github.com/${config.username}`;
|
||||
|
||||
// Render the contribution grid
|
||||
const renderGrid = () => (
|
||||
<div className="github-contributions-graph">
|
||||
{/* Month labels */}
|
||||
<div className="github-contributions-months">
|
||||
<div className="github-contributions-day-spacer" />
|
||||
<div className="github-contributions-month-labels">
|
||||
{monthLabels.map(({ month, position }, index) => (
|
||||
<span
|
||||
key={`${month}-${index}`}
|
||||
className="github-contributions-month"
|
||||
style={{
|
||||
gridColumnStart: position + 1,
|
||||
}}
|
||||
>
|
||||
{month}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Grid with day labels */}
|
||||
<div className="github-contributions-body">
|
||||
{/* Day labels column */}
|
||||
<div className="github-contributions-days">
|
||||
{DAYS.map((day, index) => (
|
||||
<span key={index} className="github-contributions-day-label">
|
||||
{day}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Contribution cells grid */}
|
||||
<div
|
||||
className="github-contributions-grid"
|
||||
style={{
|
||||
gridTemplateColumns: `repeat(${weeks.length}, 1fr)`,
|
||||
}}
|
||||
>
|
||||
{weeks.map((week, weekIndex) =>
|
||||
week.days.map((day, dayIndex) => (
|
||||
<div
|
||||
key={`${weekIndex}-${dayIndex}`}
|
||||
className="github-contribution-cell"
|
||||
data-level={day.date ? day.level : "empty"}
|
||||
title={
|
||||
day.date
|
||||
? `${day.count} contribution${day.count !== 1 ? "s" : ""} on ${day.date}`
|
||||
: ""
|
||||
}
|
||||
/>
|
||||
)),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Legend */}
|
||||
<div className="github-contributions-legend">
|
||||
<span className="github-contributions-legend-label">Less</span>
|
||||
<div className="github-contribution-cell" data-level="0" />
|
||||
<div className="github-contribution-cell" data-level="1" />
|
||||
<div className="github-contribution-cell" data-level="2" />
|
||||
<div className="github-contribution-cell" data-level="3" />
|
||||
<div className="github-contribution-cell" data-level="4" />
|
||||
<span className="github-contributions-legend-label">More</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="github-contributions-container">
|
||||
{/* Header with title and year navigation */}
|
||||
<div className="github-contributions-header">
|
||||
<div className="github-contributions-header-left">
|
||||
{config.title && (
|
||||
<span className="github-contributions-title">{config.title}</span>
|
||||
)}
|
||||
<span className="github-contributions-count">
|
||||
{loading
|
||||
? "Loading..."
|
||||
: error
|
||||
? ""
|
||||
: `${totalContributions.toLocaleString()} contributions in ${year}`}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{config.showYearNavigation && (
|
||||
<div className="github-contributions-nav">
|
||||
<button
|
||||
onClick={goToPreviousYear}
|
||||
disabled={year <= currentYear - 10}
|
||||
className="github-nav-button"
|
||||
aria-label="Previous year"
|
||||
>
|
||||
<CaretLeft size={16} weight="bold" />
|
||||
</button>
|
||||
<span className="github-year">{year}</span>
|
||||
<button
|
||||
onClick={goToNextYear}
|
||||
disabled={year >= currentYear}
|
||||
className="github-nav-button"
|
||||
aria-label="Next year"
|
||||
>
|
||||
<CaretRight size={16} weight="bold" />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Contribution grid - optionally wrapped in a link */}
|
||||
{error ? (
|
||||
<div className="github-contributions-error">{error}</div>
|
||||
) : config.linkToProfile ? (
|
||||
<a
|
||||
href={profileUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="github-contributions-link"
|
||||
aria-label={`View ${config.username}'s GitHub profile`}
|
||||
>
|
||||
{renderGrid()}
|
||||
</a>
|
||||
) : (
|
||||
renderGrid()
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -13,6 +13,8 @@ export interface LogoGalleryConfig {
|
||||
position: "above-footer" | "below-featured";
|
||||
speed: number; // Seconds for one complete scroll cycle
|
||||
title?: string; // Optional title above the marquee
|
||||
scrolling?: boolean; // When false, shows static grid instead of scrolling marquee
|
||||
maxItems?: number; // Max items to show in static mode (default: 4)
|
||||
}
|
||||
|
||||
interface LogoMarqueeProps {
|
||||
@@ -33,52 +35,71 @@ export default function LogoMarquee({ config }: LogoMarqueeProps) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Normalize and duplicate images for seamless infinite scroll
|
||||
// Normalize images
|
||||
const normalizedImages = config.images.map(normalizeImage);
|
||||
const duplicatedImages = [...normalizedImages, ...normalizedImages];
|
||||
|
||||
// Check if scrolling mode (default true for backwards compatibility)
|
||||
const isScrolling = config.scrolling !== false;
|
||||
|
||||
// For static mode, limit to maxItems (default 4)
|
||||
const maxItems = config.maxItems ?? 4;
|
||||
const displayImages = isScrolling
|
||||
? [...normalizedImages, ...normalizedImages] // Duplicate for seamless scroll
|
||||
: normalizedImages.slice(0, maxItems); // Limit for static grid
|
||||
|
||||
// Render logo item (shared between modes)
|
||||
const renderLogo = (logo: LogoItem, index: number) => (
|
||||
<div key={`${logo.src}-${index}`} className={isScrolling ? "logo-marquee-item" : "logo-static-item"}>
|
||||
{logo.href ? (
|
||||
<a
|
||||
href={logo.href}
|
||||
target={logo.href.startsWith("http") ? "_blank" : undefined}
|
||||
rel={logo.href.startsWith("http") ? "noopener noreferrer" : undefined}
|
||||
className="logo-marquee-link"
|
||||
>
|
||||
<img
|
||||
src={logo.src}
|
||||
alt=""
|
||||
className="logo-marquee-image"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
) : (
|
||||
<img
|
||||
src={logo.src}
|
||||
alt=""
|
||||
className="logo-marquee-image"
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<div className="logo-marquee-container">
|
||||
{config.title && (
|
||||
<p className="logo-marquee-title">{config.title}</p>
|
||||
)}
|
||||
<div
|
||||
className="logo-marquee"
|
||||
style={
|
||||
{
|
||||
"--marquee-speed": `${config.speed}s`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<div className="logo-marquee-track">
|
||||
{duplicatedImages.map((logo, index) => (
|
||||
<div key={`${logo.src}-${index}`} className="logo-marquee-item">
|
||||
{logo.href ? (
|
||||
<a
|
||||
href={logo.href}
|
||||
target={logo.href.startsWith("http") ? "_blank" : undefined}
|
||||
rel={logo.href.startsWith("http") ? "noopener noreferrer" : undefined}
|
||||
className="logo-marquee-link"
|
||||
>
|
||||
<img
|
||||
src={logo.src}
|
||||
alt=""
|
||||
className="logo-marquee-image"
|
||||
loading="lazy"
|
||||
/>
|
||||
</a>
|
||||
) : (
|
||||
<img
|
||||
src={logo.src}
|
||||
alt=""
|
||||
className="logo-marquee-image"
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
{isScrolling ? (
|
||||
// Scrolling marquee mode
|
||||
<div
|
||||
className="logo-marquee"
|
||||
style={
|
||||
{
|
||||
"--marquee-speed": `${config.speed}s`,
|
||||
} as React.CSSProperties
|
||||
}
|
||||
>
|
||||
<div className="logo-marquee-track">
|
||||
{displayImages.map(renderLogo)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
// Static grid mode
|
||||
<div className="logo-static-grid">
|
||||
{displayImages.map(renderLogo)}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,16 @@ import { ReactNode } from "react";
|
||||
export type { LogoItem, LogoGalleryConfig } from "../components/LogoMarquee";
|
||||
import type { LogoGalleryConfig } from "../components/LogoMarquee";
|
||||
|
||||
// GitHub contributions graph configuration
|
||||
// Displays your GitHub activity on the homepage
|
||||
export interface GitHubContributionsConfig {
|
||||
enabled: boolean; // Enable/disable the contributions graph
|
||||
username: string; // GitHub username to fetch contributions for
|
||||
showYearNavigation: boolean; // Show prev/next year arrows
|
||||
linkToProfile: boolean; // Click graph to go to GitHub profile
|
||||
title?: string; // Optional title above the graph
|
||||
}
|
||||
|
||||
// Blog page configuration
|
||||
// Controls whether posts appear on homepage, dedicated blog page, or both
|
||||
export interface BlogPageConfig {
|
||||
@@ -36,6 +46,9 @@ export interface SiteConfig {
|
||||
// Logo gallery configuration
|
||||
logoGallery: LogoGalleryConfig;
|
||||
|
||||
// GitHub contributions graph configuration
|
||||
gitHubContributions: GitHubContributionsConfig;
|
||||
|
||||
// Blog page configuration
|
||||
blogPage: BlogPageConfig;
|
||||
|
||||
@@ -54,12 +67,12 @@ export interface SiteConfig {
|
||||
// Customize this for your site
|
||||
export const siteConfig: SiteConfig = {
|
||||
// Basic site info
|
||||
name: 'markdown "sync" site',
|
||||
title: "markdown sync site",
|
||||
name: 'markdown "sync" framework',
|
||||
title: "markdown sync framework",
|
||||
// Optional logo/header image (place in public/images/, set to null to hide)
|
||||
logo: "/images/logo.svg",
|
||||
intro: null, // Set in Home.tsx to allow JSX with links
|
||||
bio: `Write locally, sync instantly with real-time updates. Powered by Convex and Netlify.`,
|
||||
bio: `Write markdown, sync from the terminal. Your content is instantly available to browsers, LLMs, and AI agents.`,
|
||||
|
||||
// Featured section configuration
|
||||
// viewMode: 'list' shows bullet list, 'cards' shows card grid with excerpts
|
||||
@@ -69,23 +82,25 @@ export const siteConfig: SiteConfig = {
|
||||
|
||||
// Logo gallery configuration
|
||||
// Set enabled to false to hide, or remove/replace sample images with your own
|
||||
// scrolling: true = infinite scroll marquee, false = static centered grid
|
||||
// maxItems: only used when scrolling is false (default: 4)
|
||||
logoGallery: {
|
||||
enabled: true,
|
||||
images: [
|
||||
{
|
||||
src: "/images/logos/sample-logo-1.svg",
|
||||
src: "/images/logos/convex-wordmark-black.svg",
|
||||
href: "https://markdowncms.netlify.app/",
|
||||
},
|
||||
{
|
||||
src: "/images/logos/convex-wordmark-black.svg",
|
||||
src: "/images/logos/netlify.svg",
|
||||
href: "/about#the-real-time-twist",
|
||||
},
|
||||
{
|
||||
src: "/images/logos/sample-logo-3.svg",
|
||||
src: "/images/logos/markdown.svg",
|
||||
href: "https://markdowncms.netlify.app/",
|
||||
},
|
||||
{
|
||||
src: "/images/logos/sample-logo-4.svg",
|
||||
src: "/images/logos/react.svg",
|
||||
href: "https://markdowncms.netlify.app/",
|
||||
},
|
||||
{
|
||||
@@ -95,7 +110,19 @@ export const siteConfig: SiteConfig = {
|
||||
],
|
||||
position: "above-footer",
|
||||
speed: 30,
|
||||
title: "Trusted by (sample logos)",
|
||||
title: "Built with",
|
||||
scrolling: false, // Set to false for static grid showing first maxItems logos
|
||||
maxItems: 4, // Number of logos to show when scrolling is false
|
||||
},
|
||||
|
||||
// GitHub contributions graph configuration
|
||||
// Set enabled to false to hide, or change username to your GitHub username
|
||||
gitHubContributions: {
|
||||
enabled: true, // Set to false to hide the contributions graph
|
||||
username: "waynesutton", // Your GitHub username
|
||||
showYearNavigation: true, // Show arrows to navigate between years
|
||||
linkToProfile: true, // Click graph to open GitHub profile
|
||||
title: "GitHub Activity", // Optional title above the graph
|
||||
},
|
||||
|
||||
// Blog page configuration
|
||||
|
||||
@@ -4,6 +4,7 @@ import { api } from "../../convex/_generated/api";
|
||||
import PostList from "../components/PostList";
|
||||
import FeaturedCards from "../components/FeaturedCards";
|
||||
import LogoMarquee from "../components/LogoMarquee";
|
||||
import GitHubContributions from "../components/GitHubContributions";
|
||||
import siteConfig from "../config/siteConfig";
|
||||
|
||||
// Local storage key for view mode preference
|
||||
@@ -93,8 +94,8 @@ export default function Home() {
|
||||
|
||||
{/* Intro with JSX support for links */}
|
||||
<p className="home-intro">
|
||||
An open-source markdown sync site for developers and AI agents.
|
||||
Publish from the terminal with npm run sync.{" "}
|
||||
An open-source publishing framework for AI agents and developers.
|
||||
Write markdown, sync from the terminal.{" "}
|
||||
<a
|
||||
href="https://github.com/waynesutton/markdown-site"
|
||||
target="_blank"
|
||||
@@ -190,6 +191,11 @@ export default function Home() {
|
||||
</section>
|
||||
)}
|
||||
|
||||
{/* GitHub contributions graph - above logo gallery */}
|
||||
{siteConfig.gitHubContributions?.enabled && (
|
||||
<GitHubContributions config={siteConfig.gitHubContributions} />
|
||||
)}
|
||||
|
||||
{/* Logo gallery (above-footer position) */}
|
||||
{renderLogoGallery("above-footer")}
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ import { useState, useEffect } from "react";
|
||||
|
||||
// Site configuration
|
||||
const SITE_URL = "https://markdowncms.netlify.app";
|
||||
const SITE_NAME = "markdown sync site";
|
||||
const SITE_NAME = "markdown sync framework";
|
||||
const DEFAULT_OG_IMAGE = "/images/og-default.svg";
|
||||
|
||||
export default function Post() {
|
||||
|
||||
@@ -1939,6 +1939,341 @@ body {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* ============================================
|
||||
GITHUB CONTRIBUTIONS GRAPH
|
||||
Theme-aware contribution calendar
|
||||
============================================ */
|
||||
.github-contributions-container {
|
||||
margin: 32px 0 48px;
|
||||
padding: 16px 20px;
|
||||
background-color: var(--bg-secondary);
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.github-contributions-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.github-contributions-header-left {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
}
|
||||
|
||||
.github-contributions-title {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
}
|
||||
|
||||
.github-contributions-count {
|
||||
font-size: var(--font-size-xs);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.github-contributions-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.github-nav-button {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
padding: 0;
|
||||
background: transparent;
|
||||
border: 1px solid var(--border-color);
|
||||
border-radius: 6px;
|
||||
color: var(--text-secondary);
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.github-nav-button:hover:not(:disabled) {
|
||||
background-color: var(--bg-hover);
|
||||
color: var(--text-primary);
|
||||
border-color: var(--text-muted);
|
||||
}
|
||||
|
||||
.github-nav-button:disabled {
|
||||
opacity: 0.4;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
.github-year {
|
||||
font-size: var(--font-size-md);
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
min-width: 48px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* Graph layout */
|
||||
.github-contributions-graph {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.github-contributions-months {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.github-contributions-day-spacer {
|
||||
width: 28px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.github-contributions-month-labels {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(53, 1fr);
|
||||
flex: 1;
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
.github-contributions-month {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.github-contributions-body {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.github-contributions-days {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 2px;
|
||||
width: 28px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.github-contributions-day-label {
|
||||
height: 10px;
|
||||
font-size: 9px;
|
||||
color: var(--text-muted);
|
||||
line-height: 10px;
|
||||
text-align: right;
|
||||
padding-right: 4px;
|
||||
}
|
||||
|
||||
.github-contributions-grid {
|
||||
display: grid;
|
||||
grid-template-rows: repeat(7, 1fr);
|
||||
grid-auto-flow: column;
|
||||
gap: 2px;
|
||||
flex: 1;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
/* Contribution cells */
|
||||
.github-contribution-cell {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
border-radius: 2px;
|
||||
transition: filter 0.15s ease;
|
||||
}
|
||||
|
||||
.github-contributions-link {
|
||||
display: block;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.github-contributions-link:hover .github-contribution-cell {
|
||||
filter: brightness(1.15);
|
||||
}
|
||||
|
||||
/* Legend */
|
||||
.github-contributions-legend {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
margin-top: 8px;
|
||||
}
|
||||
|
||||
.github-contributions-legend-label {
|
||||
font-size: 10px;
|
||||
color: var(--text-muted);
|
||||
margin: 0 4px;
|
||||
}
|
||||
|
||||
.github-contributions-legend .github-contribution-cell {
|
||||
width: 10px;
|
||||
height: 10px;
|
||||
}
|
||||
|
||||
/* Error state */
|
||||
.github-contributions-error {
|
||||
padding: 24px;
|
||||
text-align: center;
|
||||
font-size: var(--font-size-sm);
|
||||
color: var(--text-muted);
|
||||
}
|
||||
|
||||
/* Theme-specific contribution colors */
|
||||
/* Dark theme - GitHub green on dark */
|
||||
:root[data-theme="dark"] {
|
||||
--contrib-empty: #161b22;
|
||||
--contrib-level-1: #0e4429;
|
||||
--contrib-level-2: #006d32;
|
||||
--contrib-level-3: #26a641;
|
||||
--contrib-level-4: #39d353;
|
||||
}
|
||||
|
||||
/* Light theme - GitHub green */
|
||||
:root[data-theme="light"] {
|
||||
--contrib-empty: #ebedf0;
|
||||
--contrib-level-1: #9be9a8;
|
||||
--contrib-level-2: #40c463;
|
||||
--contrib-level-3: #30a14e;
|
||||
--contrib-level-4: #216e39;
|
||||
}
|
||||
|
||||
/* Tan theme - warm brown tones */
|
||||
:root[data-theme="tan"] {
|
||||
--contrib-empty: #ebe7df;
|
||||
--contrib-level-1: #d4c4a8;
|
||||
--contrib-level-2: #b5986d;
|
||||
--contrib-level-3: #8b7355;
|
||||
--contrib-level-4: #5d4e37;
|
||||
}
|
||||
|
||||
/* Cloud theme - gray-blue tones */
|
||||
:root[data-theme="cloud"] {
|
||||
--contrib-empty: #e0e0e0;
|
||||
--contrib-level-1: #b0c4de;
|
||||
--contrib-level-2: #7ba3c9;
|
||||
--contrib-level-3: #4682b4;
|
||||
--contrib-level-4: #2f5a7c;
|
||||
}
|
||||
|
||||
/* Apply colors to cells */
|
||||
.github-contribution-cell[data-level="empty"] {
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.github-contribution-cell[data-level="0"] {
|
||||
background-color: var(--contrib-empty);
|
||||
}
|
||||
|
||||
.github-contribution-cell[data-level="1"] {
|
||||
background-color: var(--contrib-level-1);
|
||||
}
|
||||
|
||||
.github-contribution-cell[data-level="2"] {
|
||||
background-color: var(--contrib-level-2);
|
||||
}
|
||||
|
||||
.github-contribution-cell[data-level="3"] {
|
||||
background-color: var(--contrib-level-3);
|
||||
}
|
||||
|
||||
.github-contribution-cell[data-level="4"] {
|
||||
background-color: var(--contrib-level-4);
|
||||
}
|
||||
|
||||
/* Theme-specific container shadows */
|
||||
:root[data-theme="dark"] .github-contributions-container {
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||
}
|
||||
|
||||
:root[data-theme="tan"] .github-contributions-container {
|
||||
box-shadow: 0 2px 8px rgba(139, 115, 85, 0.1);
|
||||
}
|
||||
|
||||
:root[data-theme="cloud"] .github-contributions-container {
|
||||
box-shadow: 0 2px 8px rgba(100, 116, 139, 0.1);
|
||||
}
|
||||
|
||||
/* Responsive adjustments */
|
||||
@media (max-width: 768px) {
|
||||
.github-contributions-container {
|
||||
margin: 24px 0 32px;
|
||||
padding: 12px 14px;
|
||||
}
|
||||
|
||||
.github-contributions-header {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.github-contributions-nav {
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.github-contributions-day-spacer,
|
||||
.github-contributions-days {
|
||||
width: 20px;
|
||||
}
|
||||
|
||||
.github-contribution-cell {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.github-contributions-legend .github-contribution-cell {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
|
||||
.github-contributions-grid {
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.github-contributions-day-label {
|
||||
font-size: 8px;
|
||||
height: 8px;
|
||||
line-height: 8px;
|
||||
}
|
||||
|
||||
.github-contributions-month-labels {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.github-contributions-container {
|
||||
margin: 20px 0 28px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.github-contribution-cell {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.github-contributions-legend .github-contribution-cell {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
.github-contributions-day-spacer,
|
||||
.github-contributions-days {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.github-contributions-month-labels {
|
||||
font-size: 8px;
|
||||
}
|
||||
|
||||
.github-contributions-legend-label {
|
||||
font-size: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Logo marquee container */
|
||||
.logo-marquee-container {
|
||||
margin: 48px 0;
|
||||
@@ -2047,6 +2382,34 @@ body {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Logo static grid mode (non-scrolling) */
|
||||
.logo-static-grid {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
gap: 48px;
|
||||
flex-wrap: wrap;
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.logo-static-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.logo-static-grid {
|
||||
gap: 32px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.logo-static-grid {
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tan theme adjustments for featured cards */
|
||||
:root[data-theme="tan"] .featured-card {
|
||||
box-shadow: 0 2px 8px rgba(139, 115, 85, 0.08);
|
||||
|
||||
Reference in New Issue
Block a user