diff --git a/seed/seed.json b/seed/seed.json index b6c7845..caa37bc 100644 --- a/seed/seed.json +++ b/seed/seed.json @@ -3,7 +3,7 @@ "version": "1", "meta": { "name": "Blog Starter", - "description": "A blog with posts and pages", + "description": "A blog with posts and pages - fully editable via EmDash admin", "author": "EmDash" }, @@ -13,6 +13,186 @@ }, "collections": [ + { + "slug": "homepage", + "label": "Homepage", + "labelSingular": "Homepage", + "supports": ["drafts", "revisions", "search"], + "singleton": true, + "fields": [ + { + "slug": "hero_headline", + "label": "Hero Headline", + "type": "string", + "required": true + }, + { + "slug": "hero_subtitle", + "label": "Hero Subtitle", + "type": "text" + }, + { + "slug": "hero_button_text", + "label": "Hero Button Text", + "type": "string" + }, + { + "slug": "hero_button_url", + "label": "Hero Button URL", + "type": "string" + }, + { + "slug": "hero_image", + "label": "Hero Image", + "type": "image" + }, + { + "slug": "features_section_title", + "label": "Features Section Title", + "type": "string" + }, + { + "slug": "features_section_subtitle", + "label": "Features Section Subtitle", + "type": "text" + }, + { + "slug": "feature_1_title", + "label": "Feature 1 Title", + "type": "string" + }, + { + "slug": "feature_1_description", + "label": "Feature 1 Description", + "type": "text" + }, + { + "slug": "feature_1_icon", + "label": "Feature 1 Icon (SVG path)", + "type": "text" + }, + { + "slug": "feature_2_title", + "label": "Feature 2 Title", + "type": "string" + }, + { + "slug": "feature_2_description", + "label": "Feature 2 Description", + "type": "text" + }, + { + "slug": "feature_2_icon", + "label": "Feature 2 Icon (SVG path)", + "type": "text" + }, + { + "slug": "feature_3_title", + "label": "Feature 3 Title", + "type": "string" + }, + { + "slug": "feature_3_description", + "label": "Feature 3 Description", + "type": "text" + }, + { + "slug": "feature_3_icon", + "label": "Feature 3 Icon (SVG path)", + "type": "text" + }, + { + "slug": "feature_4_title", + "label": "Feature 4 Title", + "type": "string" + }, + { + "slug": "feature_4_description", + "label": "Feature 4 Description", + "type": "text" + }, + { + "slug": "feature_4_icon", + "label": "Feature 4 Icon (SVG path)", + "type": "text" + }, + { + "slug": "feature_5_title", + "label": "Feature 5 Title", + "type": "string" + }, + { + "slug": "feature_5_description", + "label": "Feature 5 Description", + "type": "text" + }, + { + "slug": "feature_5_icon", + "label": "Feature 5 Icon (SVG path)", + "type": "text" + }, + { + "slug": "feature_6_title", + "label": "Feature 6 Title", + "type": "string" + }, + { + "slug": "feature_6_description", + "label": "Feature 6 Description", + "type": "text" + }, + { + "slug": "feature_6_icon", + "label": "Feature 6 Icon (SVG path)", + "type": "text" + }, + { + "slug": "comparison_title", + "label": "Comparison Section Title", + "type": "string" + }, + { + "slug": "comparison_subtitle", + "label": "Comparison Section Subtitle", + "type": "text" + }, + { + "slug": "cta_title", + "label": "CTA Title", + "type": "string" + }, + { + "slug": "cta_subtitle", + "label": "CTA Subtitle", + "type": "text" + }, + { + "slug": "cta_button_text", + "label": "CTA Button Text", + "type": "string" + }, + { + "slug": "cta_button_url", + "label": "CTA Button URL", + "type": "string" + }, + { + "slug": "footer_tagline", + "label": "Footer Tagline", + "type": "text" + }, + { + "slug": "footer_about_text", + "label": "Footer About Text", + "type": "text" + }, + { + "slug": "footer_copyright", + "label": "Footer Copyright", + "type": "string" + } + ] + }, { "slug": "posts", "label": "Posts", @@ -63,6 +243,11 @@ "label": "Content", "type": "portableText", "searchable": true + }, + { + "slug": "featured_image", + "label": "Featured Image", + "type": "image" } ] } @@ -235,6 +420,54 @@ ], "content": { + "homepage": [ + { + "id": "homepage-main", + "status": "published", + "data": { + "hero_headline": "The CMS that\nruns on your server", + "hero_subtitle": "EmDash is a full-stack TypeScript CMS built on Astro. No cloud account required, no external dependencies. Just a complete admin panel and your content.", + "hero_button_text": "Open Admin Panel", + "hero_button_url": "/_emdash/admin", + "hero_image": { + "$media": { + "url": "https://images.unsplash.com/photo-1461749280684-dccba630e2f6?w=1200&h=800&fit=crop", + "alt": "Code on a monitor", + "filename": "hero.jpg" + } + }, + "features_section_title": "Everything you need", + "features_section_subtitle": "A complete CMS without the vendor lock-in", + "feature_1_title": "Fully Self-Hosted", + "feature_1_description": "SQLite, D1, Turso, or PostgreSQL. Your data stays on your servers. No cloud account required.", + "feature_1_icon": "M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5", + "feature_2_title": "Admin Panel", + "feature_2_description": "Visual schema builder, media library, navigation menus. Full admin at /_emdash/admin", + "feature_2_icon": "M3 3h18v18H3zM3 9h18M9 21V9", + "feature_3_title": "Passkey Auth", + "feature_3_description": "WebAuthn passkey-first authentication with OAuth and magic link fallbacks. Role-based access control.", + "feature_3_icon": "M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z", + "feature_4_title": "Built-in MCP", + "feature_4_description": "Model Context Protocol server for AI tools. Claude and ChatGPT can interact with your site directly.", + "feature_4_icon": "M12 6v6l4 2", + "feature_5_title": "Plugin System", + "feature_5_description": "Sandboxed plugins on Cloudflare Workers. Define capabilities, run safely in isolation.", + "feature_5_icon": "M16 18l6-6-6-6M8 6l-6 6 6 6", + "feature_6_title": "WordPress Import", + "feature_6_description": "Import posts, pages, media, and taxonomies from WXR exports, REST API, or WordPress.com.", + "feature_6_icon": "M14 2H6a2 2 0 00-2 2v16a2 2 0 002 2h12a2 2 0 002-2V8zM14 2v6h6M16 13H8M16 17H8M16 9H8", + "comparison_title": "EmDash vs Tina CMS", + "comparison_subtitle": "Both work with Astro, but differ in approach", + "cta_title": "Ready to get started?", + "cta_subtitle": "Clone the template, run bootstrap, and you're ready to build.", + "cta_button_text": "Try Admin Panel", + "cta_button_url": "/_emdash/admin", + "footer_tagline": "Thoughts on building for the web", + "footer_about_text": "A blog about software, design, and the occasional stray thought.", + "footer_copyright": "Powered by EmDash CMS" + } + } + ], "pages": [ { "id": "about", @@ -334,7 +567,7 @@ "children": [ { "_type": "span", - "text": "The best code I've written is boring. It reads like prose, does one thing well, and doesn't require a PhD in category theory to understand. The worst code I've written was technically impressive at the time." + "text": "The best code I've written is boring. It reads like prose, does one thing well, and doesn't require a PhD in category theory to understand. The worst code I wrote was technically impressive at the time." } ] } @@ -374,16 +607,6 @@ } ] }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "The pitch for server-rendered everything was compelling: dynamic content, personalization, real-time data. But most sites don't need most of that most of the time. A blog post doesn't need to be rendered on every request. A product page doesn't change every second." - } - ] - }, { "_type": "block", "style": "h2", @@ -398,16 +621,6 @@ "text": "A static file served from a CDN is as fast as the web gets. No cold starts, no database queries, no server-side rendering overhead. The Time to First Byte is essentially the network latency to your nearest edge node. You can't beat physics." } ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "And when you do need dynamic behavior, you can add it surgically. An island of interactivity in a sea of static HTML. The best of both worlds, without paying the cost of either at all times." - } - ] } ] }, @@ -442,16 +655,6 @@ } ] }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "What I didn't expect was how much the writing itself would accelerate the learning. There's a particular kind of clarity that comes from trying to explain something to someone else. The gaps in your understanding, which you can happily ignore when the knowledge lives only in your head, become painfully obvious when you try to put it into sentences." - } - ] - }, { "_type": "block", "style": "h2", @@ -466,284 +669,6 @@ "text": "The biggest barrier isn't time or writing skill. It's the fear of publishing something that turns out to be wrong. But here's the thing: being wrong publicly is one of the most efficient ways to learn. Someone will correct you, often kindly, and you'll remember that correction forever." } ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "The posts that helped me most weren't written by experts. They were written by people one step ahead of me on the same path, in language that hadn't yet been polished into abstraction. There's a place for that kind of writing, and it's more valuable than most people realize." - } - ] - } - ] - }, - "taxonomies": { - "category": ["notes"], - "tag": ["opinion"] - } - }, - { - "id": "post-4", - "slug": "small-tools-big-impact", - "status": "published", - "data": { - "title": "Small Tools, Big Impact", - "excerpt": "The best developer tools do one thing well and get out of your way. A love letter to focused software.", - "featured_image": { - "$media": { - "url": "https://images.unsplash.com/photo-1575026615908-666710ae5e47?w=1200&h=800&fit=crop", - "alt": "Wrenches and hand tools hanging on a workshop wall", - "filename": "small-tools.jpg" - } - }, - "content": [ - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "There's a class of software that doesn't get enough appreciation. Not the frameworks or the platforms or the IDEs, but the small, sharp tools that solve one problem so well you stop thinking about them. They become invisible, which is the highest compliment you can pay a tool." - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "I'm talking about things like ripgrep, which searches code so fast it changed how I think about searching. Or jq, which makes JSON feel like a first-class data format in the terminal. Or curl, which has been quietly powering the internet's plumbing for decades." - } - ] - }, - { - "_type": "block", - "style": "h2", - "children": [{ "_type": "span", "text": "The Unix philosophy, revisited" }] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Do one thing well. The advice is old enough to be a cliche, but the best modern tools still follow it. They don't try to be platforms. They don't have plugin ecosystems or configuration languages or startup wizards. They do their job and they compose with other tools that do theirs." - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "The temptation is always to add more. One more feature, one more option, one more integration. But every addition is a decision someone has to make, a path through the code that has to be maintained, a thing that can break. The best tools resist this. They stay small, and in staying small, they stay reliable." - } - ] - } - ] - }, - "taxonomies": { - "category": ["development"], - "tag": ["tools"] - } - }, - { - "id": "post-5", - "slug": "designing-with-constraints", - "status": "published", - "data": { - "title": "Designing with Constraints", - "excerpt": "Limitations aren't obstacles to creativity. They're the structure that makes creativity possible.", - "featured_image": { - "$media": { - "url": "https://images.unsplash.com/photo-1513542789411-b6a5d4f31634?w=1200&h=800&fit=crop", - "alt": "Pencils and design tools on a desk", - "filename": "designing-with-constraints.jpg" - } - }, - "content": [ - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Give a designer a blank canvas and unlimited time, and they'll often produce something mediocre. Give them a tight brief, a small screen, and a deadline, and they'll surprise you. This isn't a paradox - it's how creativity actually works." - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Constraints force decisions. When you can't use more than two typefaces, you have to choose carefully. When the page has to load in under a second, every element earns its place. When the interface has to work on a 320px screen, you discover what's truly essential." - } - ] - }, - { - "_type": "block", - "style": "h2", - "children": [{ "_type": "span", "text": "Embracing the box" }] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "The web itself is a constraint. HTML flows in one direction. CSS has a box model. Browsers have viewport sizes and font rendering quirks. You can fight these constraints or you can work with them, and the results are dramatically different." - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "The designs I admire most don't look like they were forced through a framework. They look like they grew naturally from the medium, respecting its grain rather than working against it. That only happens when you treat constraints as creative partners rather than enemies." - } - ] - } - ] - }, - "taxonomies": { - "category": ["design"], - "tag": ["creativity"] - } - }, - { - "id": "post-6", - "slug": "a-weekend-with-a-side-project", - "status": "published", - "data": { - "title": "A Weekend with a Side Project", - "excerpt": "No stakeholders, no deadlines, no Jira tickets. Just you and a dumb idea that might turn into something.", - "featured_image": { - "$media": { - "url": "https://images.unsplash.com/photo-1542831371-29b0f74f9713?w=1200&h=800&fit=crop", - "alt": "Code on a screen with a dark theme", - "filename": "weekend-side-project.jpg" - } - }, - "content": [ - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Saturday morning. Coffee's made, the house is quiet, and I've got an idea that's been nagging at me all week. Not a good idea, necessarily - just a persistent one. A small tool that does a thing I keep doing manually. How hard could it be?" - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "This is the best kind of programming. No requirements document, no sprint planning, no pull request reviews. Just a text editor and a problem. The freedom to make terrible architectural decisions, rewrite everything twice, and follow tangents that turn out to be dead ends." - } - ] - }, - { - "_type": "block", - "style": "h2", - "children": [{ "_type": "span", "text": "Why side projects matter" }] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Side projects are where you learn things your day job would never teach you. Not because the problems are harder, but because you're free to take risks. Try a language you've never used. Build something without a framework. Deploy to a platform you've only read about. The stakes are zero, which makes the learning maximum." - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "By Sunday evening, the thing sort of works. It's rough, the error handling is nonexistent, and the README is a single sentence. But it solves the problem I set out to solve, and I learned three things I didn't know on Friday. Not a bad weekend." - } - ] - } - ] - }, - "taxonomies": { - "category": ["development"], - "tag": ["creativity"] - } - }, - { - "id": "post-7", - "slug": "notes-on-simplicity", - "status": "published", - "data": { - "title": "Notes on Simplicity", - "excerpt": "Simplicity isn't the absence of complexity. It's the result of understanding a problem well enough to solve it cleanly.", - "featured_image": { - "$media": { - "url": "https://images.unsplash.com/photo-1559051668-e1fa58f25786?w=1200&h=800&fit=crop", - "alt": "Geometric pattern carved into white paper", - "filename": "notes-on-simplicity.jpg" - } - }, - "content": [ - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Every piece of software starts simple. A few files, a clear purpose, a small surface area. Then features get added, edge cases get handled, and before long you're looking at something that requires a diagram to understand. This isn't inevitable, but it takes discipline to prevent." - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "The hard part of simplicity isn't the initial design. It's the ongoing resistance to complication. Every feature request, every bug fix, every refactor is an opportunity to add complexity. Saying no is the most important design skill, and the least celebrated." - } - ] - }, - { - "_type": "block", - "style": "h2", - "children": [{ "_type": "span", "text": "Removing as a feature" }] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "The best version of a product often has fewer features than the previous one. Not because features were missing, but because someone had the courage to remove things that weren't earning their keep. Every feature has a cost - in maintenance, in cognitive load, in the weight of the interface." - } - ] - }, - { - "_type": "block", - "style": "normal", - "children": [ - { - "_type": "span", - "text": "Simplicity is a practice, not a destination. You never arrive at simple. You just keep asking: is this necessary? Could this be clearer? Is there a way to solve this problem by removing something instead of adding something? The answer is yes more often than you'd expect." - } - ] } ] }, @@ -775,4 +700,4 @@ } ] } -} +} \ No newline at end of file diff --git a/src/layouts/Base.astro b/src/layouts/Base.astro index b74aaa9..1107545 100644 --- a/src/layouts/Base.astro +++ b/src/layouts/Base.astro @@ -1,5 +1,5 @@ --- -import { getMenu, getEmDashCollection, getSiteSettings } from "emdash"; +import { getMenu, getEmDashCollection, getSiteSettings, getEmDashEntry } from "emdash"; import { WidgetArea, EmDashHead, @@ -47,6 +47,15 @@ const fullTitle = title.includes(siteTitle) ? title : `${title} — ${siteTitle} // Fetch primary menu defined in seed const menu = await getMenu("primary"); +// Fetch homepage for footer text +let homepage; +try { + const result = await getEmDashEntry("homepage", "homepage-main"); + homepage = result.entry; +} catch { + homepage = null; +} + // Fetch pages for footer const { entries: pages } = await getEmDashCollection("pages"); @@ -148,7 +157,7 @@ const isLoggedIn = !!Astro.locals.user; : siteTitle } -
+