From 973be40335da74831ddbcc78e825b305569a7d82 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 08:30:19 +0200 Subject: [PATCH 01/11] Hero stagger animations --- src/app/(home)/page.tsx | 96 +++++++++++++++++++++++++++++------------ 1 file changed, 69 insertions(+), 27 deletions(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index f091abb..a6c4a81 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -4,6 +4,9 @@ import MorphCode from "@/components/Morph"; import { 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 = [ { @@ -66,6 +69,33 @@ public struct Player extends Entity { }, ]; +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 +121,46 @@ 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 + + + )} + + ); } From e397a7a4968cf678ff57af3c810e3c93e436c25b Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 08:53:07 +0200 Subject: [PATCH 02/11] Blog --- source.config.ts | 21 ++++++-- src/app/blog/[slug]/page.tsx | 95 +++++++++++++++++++++++++++++++++++ src/app/blog/layout.tsx | 6 +++ src/app/blog/page.tsx | 59 ++++++++++++++++++++++ src/lib/source.ts | 30 ++++++----- src/lib/utils/reading-time.ts | 16 ++++++ 6 files changed, 212 insertions(+), 15 deletions(-) create mode 100644 src/app/blog/[slug]/page.tsx create mode 100644 src/app/blog/layout.tsx create mode 100644 src/app/blog/page.tsx create mode 100644 src/lib/utils/reading-time.ts 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/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`; +} From 938efcef155e92e3d0eb80ed2db2d1916d1a487a Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 08:58:25 +0200 Subject: [PATCH 03/11] Fixed title clipping --- src/app/(home)/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index a6c4a81..be6469d 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -129,7 +129,7 @@ function ShowSection({ whileInView="show" > {show.title} From e4d2d9cd18d6d788d71b8572798a884d133b1719 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 09:01:46 +0200 Subject: [PATCH 04/11] Fixed motion preloading --- src/app/(home)/page.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index be6469d..c32900d 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -121,9 +121,12 @@ function ShowSection({ }, [show.code, setActiveCode]); return ( - + Date: Mon, 11 May 2026 09:06:02 +0200 Subject: [PATCH 05/11] Black bg on code when light mode --- src/app/(home)/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index c32900d..bad752f 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -174,7 +174,7 @@ export default function HomePage() {
-
+
From 706f3b54726c92b27b1135ffaa661a01a230f33b Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 09:08:03 +0200 Subject: [PATCH 06/11] Improved icons --- content/docs/get-started.mdx | 2 +- content/docs/index.mdx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) 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. From d21e6c33d3f551fd20e10ac19724e3a2306d9be3 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 09:28:20 +0200 Subject: [PATCH 07/11] Improved last two features --- src/app/(home)/page.tsx | 53 +++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 12 deletions(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index bad752f..73593ee 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -44,27 +44,56 @@ 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); + } + } }`, }, ]; From 0edb821c7453d4da87757c966742a33849e6e9cc Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 19:07:51 +0200 Subject: [PATCH 08/11] Improved code --- src/app/(home)/page.tsx | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 73593ee..5d7c2e4 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -200,17 +200,22 @@ export default function HomePage() { const [currentText, setCurrentText] = useState(mistShowcase[0]?.code || ""); return ( -
-
-
-
+
+
+
+ -
+
-
-
+
+
{mistShowcase.map((show) => (
+
); } From 10dfe6c17904cbeb6c3750cdd8778e7784e28722 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 19:07:58 +0200 Subject: [PATCH 09/11] variants --- src/lib/utils/variants.ts | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/lib/utils/variants.ts 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, + }, + }, +}; From 772ed3552dda789d678f84a3b11c56675897c21b Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 19:16:18 +0200 Subject: [PATCH 10/11] Scroll indicator --- src/app/(home)/page.tsx | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index 5d7c2e4..c6572ab 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -1,7 +1,7 @@ "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"; @@ -226,6 +226,27 @@ export default function HomePage() {
+ {/*
+ +
*/} + + + + +
); } From ab5678114333246cc2a14634d3c0d444ad1f2673 Mon Sep 17 00:00:00 2001 From: Klesti Selimaj Date: Mon, 11 May 2026 19:17:10 +0200 Subject: [PATCH 11/11] Scroll indicator color --- src/app/(home)/page.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/(home)/page.tsx b/src/app/(home)/page.tsx index c6572ab..6954def 100644 --- a/src/app/(home)/page.tsx +++ b/src/app/(home)/page.tsx @@ -244,7 +244,7 @@ export default function HomePage() { }} className="flex flex-col items-center gap-1 text-muted-foreground" > - +