diff --git a/content/docs/get-started.mdx b/content/docs/get-started.mdx index 44fd3f4..9c2330e 100644 --- a/content/docs/get-started.mdx +++ b/content/docs/get-started.mdx @@ -1,7 +1,7 @@ --- title: Get Started description: This guide will help you install the Mist CLI, understand its basic commands, and compile your first program. -icon: Download +icon: Rocket --- ## Installation diff --git a/content/docs/index.mdx b/content/docs/index.mdx index 9b871a8..c472144 100644 --- a/content/docs/index.mdx +++ b/content/docs/index.mdx @@ -1,7 +1,7 @@ --- title: Introduction description: What Mist is and the thinking behind it. -icon: Rocket +icon: Brain --- Mist is a systems programming language that compiles directly into Rust. diff --git a/source.config.ts b/source.config.ts index a35628a..805f0be 100644 --- a/source.config.ts +++ b/source.config.ts @@ -1,10 +1,15 @@ -import { defineConfig, defineDocs } from 'fumadocs-mdx/config'; -import { metaSchema, pageSchema } from 'fumadocs-core/source/schema'; +import { + defineCollections, + defineConfig, + defineDocs, +} from "fumadocs-mdx/config"; +import { metaSchema, pageSchema } from "fumadocs-core/source/schema"; +import { z } from "zod"; // You can customize Zod schemas for frontmatter and `meta.json` here // see https://fumadocs.dev/docs/mdx/collections export const docs = defineDocs({ - dir: 'content/docs', + dir: "content/docs", docs: { schema: pageSchema, postprocess: { @@ -16,6 +21,16 @@ export const docs = defineDocs({ }, }); +export const blogPosts = defineCollections({ + type: "doc", + dir: "content/blog", + // add required frontmatter properties + schema: pageSchema.extend({ + author: z.string(), + date: z.string().date().or(z.date()), + }), +}); + export default defineConfig({ mdxOptions: { // MDX options diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index f091abb..6954def 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -1,9 +1,12 @@ "use client"; import MorphCode from "@/components/Morph"; -import { NewspaperIcon, RocketIcon } from "lucide-react"; +import { ChevronsDownIcon, NewspaperIcon, RocketIcon } from "lucide-react"; import Link from "next/link"; import { useEffect, useRef, useState } from "react"; +import { motion, Variants } from "framer-motion"; + +const MotionLink = motion(Link); export const mistShowcase = [ { @@ -41,31 +44,87 @@ void process_data() { title: "Class and Type System", description: "Classes are syntactic sugar for Rust structs. Using 'extends' provides inheritance-style syntax over struct composition, compiling into the underlying Rust type system.", - code: `public class Entity { - u32 id; + code: `public struct PluginInfo { + public String name, + public String version, } + +public class PluginRegistry { + T plugins; -// Compiles to struct composition in Rust -public struct Player extends Entity { - String username; + public constructor(T plugins) { + self.plugins = plugins; + } - public constructor(u32 id, str* name) { - self.id = id; - self.username = name.to_string(); + public T plugins(self) { + return self.plugins; } }`, }, { - title: "Explicit Mutability", + title: "Better Developer Experience", description: - "Mist maintains memory intent through explicit self pointers and mutability modifiers.", - code: `public void update_id(self mut*, u32 new_id) { - self.id = new_id; + "While Rust can already be a great DX, Mist implements concepts that are proven to improve structure.", + code: `public class Logger { + String prefix; + + public constructor(str* prefix) { + self.prefix = prefix.to_string(); + } + + public void log(self*, LogLevel level, str* message) { + match(level) { + LogLevel::Info => { + println!("{level} {} {}", self.prefix, message); + } + + LogLevel::Warning => { + println!("{level} {} {}", self.prefix, message); + } + + LogLevel::Error => { + println!("{level} {} {}", self.prefix, message); + } + } + } + + impl fmt::Display { + fmt::Result fmt(self*, std::fmt::Formatter<'_> mut* f) { + return write!(f, "logger ({})", self.prefix); + } + } }`, }, ]; +const container: Variants = { + hidden: {}, + show: { + transition: { + staggerChildren: 0.2, + delayChildren: 0.2, + }, + }, +}; + +const item: Variants = { + hidden: { + opacity: 0, + y: 12, + filter: "blur(10px)", + }, + show: { + opacity: 1, + y: 0, + filter: "blur(0px)", + transition: { + duration: 0.6, + ease: "easeOut" as const, + }, + }, +}; + // Sub-component to handle individual section observation function ShowSection({ show, @@ -91,34 +150,49 @@ function ShowSection({ }, [show.code, setActiveCode]); return ( -
-

- {show.title} -

-

{show.description}

- {show.cta && ( -
- - - Get Started - + + + {show.title} + - - - View Blog - -
- )} -
+ + {show.description} + + {show.cta && ( + + + + Get Started + + + + + View Blog + + + )} + + ); } @@ -126,17 +200,22 @@ export default function HomePage() { const [currentText, setCurrentText] = useState(mistShowcase[0]?.code || ""); return ( -
-
-
-
+
+
+
+ -
+
-
-
+
+
{mistShowcase.map((show) => (
+
+ {/*
+ +
*/} + + + + +
); } diff --git a/src/app/blog/[slug]/page.tsx b/src/app/blog/[slug]/page.tsx new file mode 100644 index 0000000..6f8490f --- /dev/null +++ b/src/app/blog/[slug]/page.tsx @@ -0,0 +1,95 @@ +import { notFound } from "next/navigation"; +import Link from "next/link"; +import defaultMdxComponents from "fumadocs-ui/mdx"; +import { blog } from "@/lib/source"; +import { ChevronLeft, Calendar, User, Clock } from "lucide-react"; +import { calculateReadingTime } from "@/lib/utils/reading-time"; + +export default async function Page(props: { + params: Promise<{ slug: string }>; +}) { + const params = await props.params; + const page = blog.getPage([params.slug]); + + if (!page) notFound(); + + const text = await page.data.getText("raw"); + + const Mdx = page.data.body; + + const readingTime = calculateReadingTime(text); + + return ( +
+ {/* Navigation & Breadcrumbs */} + + + Back to Blog + + + {/* Header Section */} +
+

+ {page.data.title} +

+

+ {page.data.description} +

+ +
+
+ + + {page.data.author} + +
+
+ + +
+
+ + {readingTime} +
+
+
+ + {/* Content Layout */} +
+
+ +
+ + {/* Optional Sidebar (TOC or Newsletter) */} + {/* */} +
+
+ ); +} + +export function generateStaticParams(): { slug: string }[] { + return blog.getPages().map((page) => ({ + slug: page.slugs[0], + })); +} diff --git a/src/app/blog/layout.tsx b/src/app/blog/layout.tsx new file mode 100644 index 0000000..c16b056 --- /dev/null +++ b/src/app/blog/layout.tsx @@ -0,0 +1,6 @@ +import { HomeLayout } from "fumadocs-ui/layouts/home"; +import { baseOptions } from "@/lib/layout.shared"; + +export default function Layout({ children }: LayoutProps<"/">) { + return {children}; +} diff --git a/src/app/blog/page.tsx b/src/app/blog/page.tsx new file mode 100644 index 0000000..1e6ac7a --- /dev/null +++ b/src/app/blog/page.tsx @@ -0,0 +1,59 @@ +import Link from "next/link"; +import { blog } from "@/lib/source"; +import { Calendar, ArrowRight } from "lucide-react"; + +export default function Blog() { + // Sort posts by date (descending) so the newest is always first + const posts = [...blog.getPages()].sort( + (a, b) => new Date(b.data.date).getTime() - new Date(a.data.date).getTime(), + ); + + return ( +
+
+

+ Writing & Insights +

+

+ Thoughts on software, design, and building for the web. +

+
+ +
+ {posts.map((post) => ( + + {/* Date Tag */} +
+ + {new Date(post.data.date).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + })} +
+ + {/* Title & Description */} +
+

+ {post.data.title} +

+

+ {post.data.description} +

+
+ + {/* "Read More" Footer */} +
+ Read Article + +
+ + ))} +
+
+ ); +} diff --git a/src/lib/source.ts b/src/lib/source.ts index a00a3fc..dafa50c 100644 --- a/src/lib/source.ts +++ b/src/lib/source.ts @@ -1,7 +1,8 @@ -import { docs } from 'collections/server'; -import { loader } from 'fumadocs-core/source'; -import { lucideIconsPlugin } from 'fumadocs-core/source/lucide-icons'; -import { docsContentRoute, docsImageRoute, docsRoute } from './shared'; +import { blogPosts, docs } from "collections/server"; +import { loader } from "fumadocs-core/source"; +import { lucideIconsPlugin } from "fumadocs-core/source/lucide-icons"; +import { docsContentRoute, docsImageRoute, docsRoute } from "./shared"; +import { toFumadocsSource } from "fumadocs-mdx/runtime/server"; // See https://fumadocs.dev/docs/headless/source-api for more info export const source = loader({ @@ -10,28 +11,33 @@ export const source = loader({ plugins: [lucideIconsPlugin()], }); -export function getPageImage(page: (typeof source)['$inferPage']) { - const segments = [...page.slugs, 'image.png']; +export function getPageImage(page: (typeof source)["$inferPage"]) { + const segments = [...page.slugs, "image.png"]; return { segments, - url: `${docsImageRoute}/${segments.join('/')}`, + url: `${docsImageRoute}/${segments.join("/")}`, }; } -export function getPageMarkdownUrl(page: (typeof source)['$inferPage']) { - const segments = [...page.slugs, 'content.md']; +export function getPageMarkdownUrl(page: (typeof source)["$inferPage"]) { + const segments = [...page.slugs, "content.md"]; return { segments, - url: `${docsContentRoute}/${segments.join('/')}`, + url: `${docsContentRoute}/${segments.join("/")}`, }; } -export async function getLLMText(page: (typeof source)['$inferPage']) { - const processed = await page.data.getText('processed'); +export async function getLLMText(page: (typeof source)["$inferPage"]) { + const processed = await page.data.getText("processed"); return `# ${page.data.title} (${page.url}) ${processed}`; } + +export const blog = loader({ + baseUrl: "/blog", + source: toFumadocsSource(blogPosts, []), +}); diff --git a/src/lib/utils/reading-time.ts b/src/lib/utils/reading-time.ts new file mode 100644 index 0000000..954bcdf --- /dev/null +++ b/src/lib/utils/reading-time.ts @@ -0,0 +1,16 @@ +export function calculateReadingTime(content: string): string { + const WPM = 225; // Average adult reading speed + + // 1. Strip HTML tags and Markdown syntax (simplified) + const plainText = content + .replace(/[#*`~_\[\]()]/g, "") // Remove common MD symbols + .replace(/<[^>]*>/g, ""); // Remove HTML tags + + // 2. Count words + const words = plainText.trim().split(/\s+/).length; + + // 3. Calculate minutes + const minutes = Math.ceil(words / WPM); + + return `${minutes} min read`; +} diff --git a/src/lib/utils/variants.ts b/src/lib/utils/variants.ts new file mode 100644 index 0000000..19fca21 --- /dev/null +++ b/src/lib/utils/variants.ts @@ -0,0 +1,28 @@ +import { Variants } from "framer-motion"; + +export const container: Variants = { + hidden: {}, + show: { + transition: { + staggerChildren: 0.2, + delayChildren: 0.2, + }, + }, +}; + +export const item: Variants = { + hidden: { + opacity: 0, + y: 12, + filter: "blur(10px)", + }, + show: { + opacity: 1, + y: 0, + filter: "blur(0px)", + transition: { + duration: 0.6, + ease: "easeOut" as const, + }, + }, +};