diff --git a/package-lock.json b/package-lock.json index 17e8dc6..1a05a82 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,12 +36,15 @@ "date-fns": "^4.1.0", "embla-carousel-autoplay": "^8.5.2", "embla-carousel-react": "^8.5.2", + "katex": "^0.17.0", "lucide-react": "^0.479.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-fast-marquee": "^1.6.5", "react-hook-form": "^7.54.2", "react-icons": "^5.4.0", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0", "tailwind-merge": "^3.0.2", "tailwindcss": "^4.0.12", "tailwindcss-animate": "^1.0.7", @@ -2970,6 +2973,12 @@ "@types/unist": "*" } }, + "node_modules/@types/katex": { + "version": "0.16.8", + "resolved": "https://registry.npmjs.org/@types/katex/-/katex-0.16.8.tgz", + "integrity": "sha512-trgaNyfU+Xh2Tc+ABIb44a5AYUpicB3uwirOioeOkNPPbmgRNtcWyDeeFRzjPZENO9Vq8gvVqfhaaXWLlevVwg==", + "license": "MIT" + }, "node_modules/@types/mdast": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/mdast/-/mdast-4.0.4.tgz", @@ -3979,6 +3988,15 @@ "url": "https://github.com/sponsors/wooorm" } }, + "node_modules/commander": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-8.3.0.tgz", + "integrity": "sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, "node_modules/common-ancestor-path": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/common-ancestor-path/-/common-ancestor-path-1.0.1.tgz", @@ -4691,6 +4709,21 @@ "uncrypto": "^0.1.3" } }, + "node_modules/hast-util-from-dom": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/hast-util-from-dom/-/hast-util-from-dom-5.0.1.tgz", + "integrity": "sha512-N+LqofjR2zuzTjCPzyDUdSshy4Ma6li7p/c3pA78uTwzFgENbgbUrm2ugwsOdcjI1muO+o6Dgzp9p8WHtn/39Q==", + "license": "ISC", + "dependencies": { + "@types/hast": "^3.0.0", + "hastscript": "^9.0.0", + "web-namespaces": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-html": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-html/-/hast-util-from-html-2.0.3.tgz", @@ -4709,6 +4742,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-from-html-isomorphic": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hast-util-from-html-isomorphic/-/hast-util-from-html-isomorphic-2.0.0.tgz", + "integrity": "sha512-zJfpXq44yff2hmE0XmwEOzdWin5xwH+QIhMLOScpX91e/NSGPsAzNCvLQDIEPyO2TXi+lBmU6hjLIhV8MwP2kw==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "hast-util-from-dom": "^5.0.0", + "hast-util-from-html": "^2.0.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/hast-util-from-parse5": { "version": "8.0.3", "resolved": "https://registry.npmjs.org/hast-util-from-parse5/-/hast-util-from-parse5-8.0.3.tgz", @@ -5193,6 +5242,22 @@ "integrity": "sha512-H8jvkz1O50L3dMZCsLqiuB2tA7muqbSg1AtGEkN0leAqGjsUzDJir3Zwr02BhqdcITPg3ei3mZ+HjMocAknhhg==", "license": "MIT" }, + "node_modules/katex": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.17.0.tgz", + "integrity": "sha512-Vdw0ATsQ9V+LuegM/BTwQqV/6cTl5lbGcIrU+BCgLxyf6bo38ybOr372tuSIxir3CN720flu1meYR6XzNMwQnw==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/kleur": { "version": "4.1.5", "resolved": "https://registry.npmjs.org/kleur/-/kleur-4.1.5.tgz", @@ -5680,6 +5745,25 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/mdast-util-math": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/mdast-util-math/-/mdast-util-math-3.0.0.tgz", + "integrity": "sha512-Tl9GBNeG/AhJnQM221bJR2HPvLOSnLE/T9cJI9tlc6zwQk2nPk/4f0cHkOdEixQPC/j8UtKDdITswvLAy1OZ1w==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/mdast": "^4.0.0", + "devlop": "^1.0.0", + "longest-streak": "^3.0.0", + "mdast-util-from-markdown": "^2.0.0", + "mdast-util-to-markdown": "^2.1.0", + "unist-util-remove-position": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/mdast-util-mdx": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/mdast-util-mdx/-/mdast-util-mdx-3.0.0.tgz", @@ -6031,6 +6115,41 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/micromark-extension-math": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/micromark-extension-math/-/micromark-extension-math-3.1.0.tgz", + "integrity": "sha512-lvEqd+fHjATVs+2v/8kg9i5Q0AP2k85H0WUOwpIVvUML8BapsMvh1XAogmQjOCsLpoKRCVQqEkQBB3NhVBcsOg==", + "license": "MIT", + "dependencies": { + "@types/katex": "^0.16.0", + "devlop": "^1.0.0", + "katex": "^0.16.0", + "micromark-factory-space": "^2.0.0", + "micromark-util-character": "^2.0.0", + "micromark-util-symbol": "^2.0.0", + "micromark-util-types": "^2.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/micromark-extension-math/node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/micromark-extension-mdx-expression": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/micromark-extension-mdx-expression/-/micromark-extension-mdx-expression-3.0.1.tgz", @@ -7291,6 +7410,41 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-katex": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/rehype-katex/-/rehype-katex-7.0.1.tgz", + "integrity": "sha512-OiM2wrZ/wuhKkigASodFoo8wimG3H12LWQaH8qSPVJn9apWKFSH3YOCtbKpBorTVw/eI7cuT21XBbvwEswbIOA==", + "license": "MIT", + "dependencies": { + "@types/hast": "^3.0.0", + "@types/katex": "^0.16.0", + "hast-util-from-html-isomorphic": "^2.0.0", + "hast-util-to-text": "^4.0.0", + "katex": "^0.16.0", + "unist-util-visit-parents": "^6.0.0", + "vfile": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-katex/node_modules/katex": { + "version": "0.16.47", + "resolved": "https://registry.npmjs.org/katex/-/katex-0.16.47.tgz", + "integrity": "sha512-Eeo8Ys1doU1z+x8AZsPpQu+p/QcZBI5PeOo7QGQdy2x2m0MU/hYagBbGOmXwr5KVbEfVuWv9LpnQWeehogurjg==", + "funding": [ + "https://opencollective.com/katex", + "https://github.com/sponsors/katex" + ], + "license": "MIT", + "dependencies": { + "commander": "^8.3.0" + }, + "bin": { + "katex": "cli.js" + } + }, "node_modules/rehype-parse": { "version": "9.0.1", "resolved": "https://registry.npmjs.org/rehype-parse/-/rehype-parse-9.0.1.tgz", @@ -7369,6 +7523,22 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/remark-math": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", + "integrity": "sha512-MMqgnP74Igy+S3WwnhQ7kqGlEerTETXMvJhrUzDikVZ2/uogJCb+WHUg97hK9/jcfc0dkD73s3LN8zU49cTEtA==", + "license": "MIT", + "dependencies": { + "@types/mdast": "^4.0.0", + "mdast-util-math": "^3.0.0", + "micromark-extension-math": "^3.0.0", + "unified": "^11.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/remark-mdx": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/remark-mdx/-/remark-mdx-3.1.0.tgz", diff --git a/package.json b/package.json index 0512fc8..b27a5ac 100644 --- a/package.json +++ b/package.json @@ -48,12 +48,15 @@ "date-fns": "^4.1.0", "embla-carousel-autoplay": "^8.5.2", "embla-carousel-react": "^8.5.2", + "katex": "^0.17.0", "lucide-react": "^0.479.0", "react": "^19.0.0", "react-dom": "^19.0.0", "react-fast-marquee": "^1.6.5", "react-hook-form": "^7.54.2", "react-icons": "^5.4.0", + "rehype-katex": "^7.0.1", + "remark-math": "^6.0.0", "tailwind-merge": "^3.0.2", "tailwindcss": "^4.0.12", "tailwindcss-animate": "^1.0.7", diff --git a/sites/art/astro.config.mjs b/sites/art/astro.config.mjs index 9ce1692..1f85fb3 100644 --- a/sites/art/astro.config.mjs +++ b/sites/art/astro.config.mjs @@ -1,17 +1,27 @@ // @ts-check import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import remarkCallouts from "../../src/remark-callouts.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; +const remarkPlugins = [remarkMath, remarkCallouts]; +const rehypePlugins = [rehypeKatex]; + // https://astro.build/config export default defineConfig({ site: ACTIVE_BLOG_SITE.url, - integrations: [mdx(), sitemap(), react()], + integrations: [mdx({ remarkPlugins, rehypePlugins }), sitemap(), react()], + markdown: { + remarkPlugins, + rehypePlugins, + }, output: "static", outDir: "dist", diff --git a/sites/art/src/components/sections/blog-post.tsx b/sites/art/src/components/sections/blog-post.tsx index 84022db..cb7319b 100644 --- a/sites/art/src/components/sections/blog-post.tsx +++ b/sites/art/src/components/sections/blog-post.tsx @@ -1,87 +1,26 @@ -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { format } from "date-fns"; -import { Calendar, Clock, ArrowLeft, User } from "lucide-react"; +import type { ReactNode } from "react"; const BlogPost = ({ post, children, }: { post: any; - children: React.ReactNode; + children: ReactNode; }) => { - const { title, authorName, image, pubDate, description, authorImage } = - post.data; + const { title, image } = post.data; + return ( - <> - {/* Hero section with gradient background and post info */} -
-
-
-

- {title} -

-

- {description} -

-
+
+
+ {image ? ( +
+ {title} +
+ ) : null} -
- {/* Author info */} - {authorImage ? ( -
-
- ) : ( -
-
- -
-
- - {authorName || "Anonymous"} - - Author -
-
- )} - - {/* Date info */} -
-
- - {format(pubDate, "MMMM d, yyyy")} -
- -
- - 5 min read -
-
-
-
-
- - {/* Featured image */} - {image && ( -
-
- {title} -
-
- )} - - {/* Article content */} -
-
- {children} -
-
- +
{children}
+ + ); }; diff --git a/sites/art/src/pages/[...slug].astro b/sites/art/src/pages/[...slug].astro index 62160d4..4b14673 100644 --- a/sites/art/src/pages/[...slug].astro +++ b/sites/art/src/pages/[...slug].astro @@ -3,7 +3,6 @@ import { getCollection, render } from 'astro:content'; import CoralComments from '@/components/CoralComments.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro'; import { BlogPost } from '@/components/sections/blog-post'; -import NewsletterSignup from '@/components/sections/newsletter-signup'; import { ACTIVE_BLOG_SITE } from '@/blog-sites.js'; import { SITE_TITLE } from '@/consts'; @@ -23,12 +22,9 @@ const coralStoryURL = new URL(`/${postSlug}/`, ACTIVE_BLOG_SITE.url).toString(); --- -
- - - -
+ + + -
diff --git a/sites/art/src/styles/global.css b/sites/art/src/styles/global.css index 7b48053..e4deefe 100644 --- a/sites/art/src/styles/global.css +++ b/sites/art/src/styles/global.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; +@import "../../../../src/styles/article.css"; @plugin 'tailwindcss-animate'; @plugin '@tailwindcss/typography'; diff --git a/sites/poetry/astro.config.mjs b/sites/poetry/astro.config.mjs index 9ce1692..1f85fb3 100644 --- a/sites/poetry/astro.config.mjs +++ b/sites/poetry/astro.config.mjs @@ -1,17 +1,27 @@ // @ts-check import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import remarkCallouts from "../../src/remark-callouts.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; +const remarkPlugins = [remarkMath, remarkCallouts]; +const rehypePlugins = [rehypeKatex]; + // https://astro.build/config export default defineConfig({ site: ACTIVE_BLOG_SITE.url, - integrations: [mdx(), sitemap(), react()], + integrations: [mdx({ remarkPlugins, rehypePlugins }), sitemap(), react()], + markdown: { + remarkPlugins, + rehypePlugins, + }, output: "static", outDir: "dist", diff --git a/sites/poetry/src/styles/global.css b/sites/poetry/src/styles/global.css index a752642..d32e67d 100644 --- a/sites/poetry/src/styles/global.css +++ b/sites/poetry/src/styles/global.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; +@import "../../../../src/styles/article.css"; @plugin 'tailwindcss-animate'; @plugin '@tailwindcss/typography'; diff --git a/sites/puzzles/astro.config.mjs b/sites/puzzles/astro.config.mjs index 9ce1692..1f85fb3 100644 --- a/sites/puzzles/astro.config.mjs +++ b/sites/puzzles/astro.config.mjs @@ -1,17 +1,27 @@ // @ts-check import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import remarkCallouts from "../../src/remark-callouts.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; +const remarkPlugins = [remarkMath, remarkCallouts]; +const rehypePlugins = [rehypeKatex]; + // https://astro.build/config export default defineConfig({ site: ACTIVE_BLOG_SITE.url, - integrations: [mdx(), sitemap(), react()], + integrations: [mdx({ remarkPlugins, rehypePlugins }), sitemap(), react()], + markdown: { + remarkPlugins, + rehypePlugins, + }, output: "static", outDir: "dist", diff --git a/sites/puzzles/src/components/sections/blog-post.tsx b/sites/puzzles/src/components/sections/blog-post.tsx index 84022db..cb7319b 100644 --- a/sites/puzzles/src/components/sections/blog-post.tsx +++ b/sites/puzzles/src/components/sections/blog-post.tsx @@ -1,87 +1,26 @@ -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { format } from "date-fns"; -import { Calendar, Clock, ArrowLeft, User } from "lucide-react"; +import type { ReactNode } from "react"; const BlogPost = ({ post, children, }: { post: any; - children: React.ReactNode; + children: ReactNode; }) => { - const { title, authorName, image, pubDate, description, authorImage } = - post.data; + const { title, image } = post.data; + return ( - <> - {/* Hero section with gradient background and post info */} -
-
-
-

- {title} -

-

- {description} -

-
+
+
+ {image ? ( +
+ {title} +
+ ) : null} -
- {/* Author info */} - {authorImage ? ( -
-
- ) : ( -
-
- -
-
- - {authorName || "Anonymous"} - - Author -
-
- )} - - {/* Date info */} -
-
- - {format(pubDate, "MMMM d, yyyy")} -
- -
- - 5 min read -
-
-
-
-
- - {/* Featured image */} - {image && ( -
-
- {title} -
-
- )} - - {/* Article content */} -
-
- {children} -
-
- +
{children}
+ + ); }; diff --git a/sites/puzzles/src/pages/[...slug].astro b/sites/puzzles/src/pages/[...slug].astro index 62160d4..4b14673 100644 --- a/sites/puzzles/src/pages/[...slug].astro +++ b/sites/puzzles/src/pages/[...slug].astro @@ -3,7 +3,6 @@ import { getCollection, render } from 'astro:content'; import CoralComments from '@/components/CoralComments.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro'; import { BlogPost } from '@/components/sections/blog-post'; -import NewsletterSignup from '@/components/sections/newsletter-signup'; import { ACTIVE_BLOG_SITE } from '@/blog-sites.js'; import { SITE_TITLE } from '@/consts'; @@ -23,12 +22,9 @@ const coralStoryURL = new URL(`/${postSlug}/`, ACTIVE_BLOG_SITE.url).toString(); --- -
- - - -
+ + + -
diff --git a/sites/puzzles/src/styles/global.css b/sites/puzzles/src/styles/global.css index 31358b5..df3f434 100644 --- a/sites/puzzles/src/styles/global.css +++ b/sites/puzzles/src/styles/global.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; +@import "../../../../src/styles/article.css"; @plugin 'tailwindcss-animate'; @plugin '@tailwindcss/typography'; diff --git a/sites/reflections/astro.config.mjs b/sites/reflections/astro.config.mjs index 9ce1692..1f85fb3 100644 --- a/sites/reflections/astro.config.mjs +++ b/sites/reflections/astro.config.mjs @@ -1,17 +1,27 @@ // @ts-check import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import remarkCallouts from "../../src/remark-callouts.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; +const remarkPlugins = [remarkMath, remarkCallouts]; +const rehypePlugins = [rehypeKatex]; + // https://astro.build/config export default defineConfig({ site: ACTIVE_BLOG_SITE.url, - integrations: [mdx(), sitemap(), react()], + integrations: [mdx({ remarkPlugins, rehypePlugins }), sitemap(), react()], + markdown: { + remarkPlugins, + rehypePlugins, + }, output: "static", outDir: "dist", diff --git a/sites/reflections/src/components/sections/blog-post.tsx b/sites/reflections/src/components/sections/blog-post.tsx index 84022db..cb7319b 100644 --- a/sites/reflections/src/components/sections/blog-post.tsx +++ b/sites/reflections/src/components/sections/blog-post.tsx @@ -1,87 +1,26 @@ -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { format } from "date-fns"; -import { Calendar, Clock, ArrowLeft, User } from "lucide-react"; +import type { ReactNode } from "react"; const BlogPost = ({ post, children, }: { post: any; - children: React.ReactNode; + children: ReactNode; }) => { - const { title, authorName, image, pubDate, description, authorImage } = - post.data; + const { title, image } = post.data; + return ( - <> - {/* Hero section with gradient background and post info */} -
-
-
-

- {title} -

-

- {description} -

-
+
+
+ {image ? ( +
+ {title} +
+ ) : null} -
- {/* Author info */} - {authorImage ? ( -
-
- ) : ( -
-
- -
-
- - {authorName || "Anonymous"} - - Author -
-
- )} - - {/* Date info */} -
-
- - {format(pubDate, "MMMM d, yyyy")} -
- -
- - 5 min read -
-
-
-
-
- - {/* Featured image */} - {image && ( -
-
- {title} -
-
- )} - - {/* Article content */} -
-
- {children} -
-
- +
{children}
+ + ); }; diff --git a/sites/reflections/src/pages/[...slug].astro b/sites/reflections/src/pages/[...slug].astro index 62160d4..4b14673 100644 --- a/sites/reflections/src/pages/[...slug].astro +++ b/sites/reflections/src/pages/[...slug].astro @@ -3,7 +3,6 @@ import { getCollection, render } from 'astro:content'; import CoralComments from '@/components/CoralComments.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro'; import { BlogPost } from '@/components/sections/blog-post'; -import NewsletterSignup from '@/components/sections/newsletter-signup'; import { ACTIVE_BLOG_SITE } from '@/blog-sites.js'; import { SITE_TITLE } from '@/consts'; @@ -23,12 +22,9 @@ const coralStoryURL = new URL(`/${postSlug}/`, ACTIVE_BLOG_SITE.url).toString(); --- -
- - - -
+ + + -
diff --git a/sites/reflections/src/styles/global.css b/sites/reflections/src/styles/global.css index c6c49a0..851c68a 100644 --- a/sites/reflections/src/styles/global.css +++ b/sites/reflections/src/styles/global.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; +@import "../../../../src/styles/article.css"; @plugin 'tailwindcss-animate'; @plugin '@tailwindcss/typography'; diff --git a/sites/research/astro.config.mjs b/sites/research/astro.config.mjs index 9ce1692..1f85fb3 100644 --- a/sites/research/astro.config.mjs +++ b/sites/research/astro.config.mjs @@ -1,17 +1,27 @@ // @ts-check import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import remarkCallouts from "../../src/remark-callouts.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; +const remarkPlugins = [remarkMath, remarkCallouts]; +const rehypePlugins = [rehypeKatex]; + // https://astro.build/config export default defineConfig({ site: ACTIVE_BLOG_SITE.url, - integrations: [mdx(), sitemap(), react()], + integrations: [mdx({ remarkPlugins, rehypePlugins }), sitemap(), react()], + markdown: { + remarkPlugins, + rehypePlugins, + }, output: "static", outDir: "dist", diff --git a/sites/research/src/components/sections/blog-post.tsx b/sites/research/src/components/sections/blog-post.tsx index 84022db..cb7319b 100644 --- a/sites/research/src/components/sections/blog-post.tsx +++ b/sites/research/src/components/sections/blog-post.tsx @@ -1,87 +1,26 @@ -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { format } from "date-fns"; -import { Calendar, Clock, ArrowLeft, User } from "lucide-react"; +import type { ReactNode } from "react"; const BlogPost = ({ post, children, }: { post: any; - children: React.ReactNode; + children: ReactNode; }) => { - const { title, authorName, image, pubDate, description, authorImage } = - post.data; + const { title, image } = post.data; + return ( - <> - {/* Hero section with gradient background and post info */} -
-
-
-

- {title} -

-

- {description} -

-
+
+
+ {image ? ( +
+ {title} +
+ ) : null} -
- {/* Author info */} - {authorImage ? ( -
-
- ) : ( -
-
- -
-
- - {authorName || "Anonymous"} - - Author -
-
- )} - - {/* Date info */} -
-
- - {format(pubDate, "MMMM d, yyyy")} -
- -
- - 5 min read -
-
-
-
-
- - {/* Featured image */} - {image && ( -
-
- {title} -
-
- )} - - {/* Article content */} -
-
- {children} -
-
- +
{children}
+ + ); }; diff --git a/sites/research/src/pages/[...slug].astro b/sites/research/src/pages/[...slug].astro index 62160d4..4b14673 100644 --- a/sites/research/src/pages/[...slug].astro +++ b/sites/research/src/pages/[...slug].astro @@ -3,7 +3,6 @@ import { getCollection, render } from 'astro:content'; import CoralComments from '@/components/CoralComments.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro'; import { BlogPost } from '@/components/sections/blog-post'; -import NewsletterSignup from '@/components/sections/newsletter-signup'; import { ACTIVE_BLOG_SITE } from '@/blog-sites.js'; import { SITE_TITLE } from '@/consts'; @@ -23,12 +22,9 @@ const coralStoryURL = new URL(`/${postSlug}/`, ACTIVE_BLOG_SITE.url).toString(); --- -
- - - -
+ + + -
diff --git a/sites/research/src/styles/global.css b/sites/research/src/styles/global.css index 77a5bc8..23c782f 100644 --- a/sites/research/src/styles/global.css +++ b/sites/research/src/styles/global.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; +@import "../../../../src/styles/article.css"; @plugin 'tailwindcss-animate'; @plugin '@tailwindcss/typography'; diff --git a/sites/reviews/astro.config.mjs b/sites/reviews/astro.config.mjs index 9ce1692..1f85fb3 100644 --- a/sites/reviews/astro.config.mjs +++ b/sites/reviews/astro.config.mjs @@ -1,17 +1,27 @@ // @ts-check import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import remarkCallouts from "../../src/remark-callouts.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; +const remarkPlugins = [remarkMath, remarkCallouts]; +const rehypePlugins = [rehypeKatex]; + // https://astro.build/config export default defineConfig({ site: ACTIVE_BLOG_SITE.url, - integrations: [mdx(), sitemap(), react()], + integrations: [mdx({ remarkPlugins, rehypePlugins }), sitemap(), react()], + markdown: { + remarkPlugins, + rehypePlugins, + }, output: "static", outDir: "dist", diff --git a/sites/reviews/src/components/sections/blog-post.tsx b/sites/reviews/src/components/sections/blog-post.tsx index 84022db..cb7319b 100644 --- a/sites/reviews/src/components/sections/blog-post.tsx +++ b/sites/reviews/src/components/sections/blog-post.tsx @@ -1,87 +1,26 @@ -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { format } from "date-fns"; -import { Calendar, Clock, ArrowLeft, User } from "lucide-react"; +import type { ReactNode } from "react"; const BlogPost = ({ post, children, }: { post: any; - children: React.ReactNode; + children: ReactNode; }) => { - const { title, authorName, image, pubDate, description, authorImage } = - post.data; + const { title, image } = post.data; + return ( - <> - {/* Hero section with gradient background and post info */} -
-
-
-

- {title} -

-

- {description} -

-
+
+
+ {image ? ( +
+ {title} +
+ ) : null} -
- {/* Author info */} - {authorImage ? ( -
-
- ) : ( -
-
- -
-
- - {authorName || "Anonymous"} - - Author -
-
- )} - - {/* Date info */} -
-
- - {format(pubDate, "MMMM d, yyyy")} -
- -
- - 5 min read -
-
-
-
-
- - {/* Featured image */} - {image && ( -
-
- {title} -
-
- )} - - {/* Article content */} -
-
- {children} -
-
- +
{children}
+ + ); }; diff --git a/sites/reviews/src/pages/[...slug].astro b/sites/reviews/src/pages/[...slug].astro index 62160d4..4b14673 100644 --- a/sites/reviews/src/pages/[...slug].astro +++ b/sites/reviews/src/pages/[...slug].astro @@ -3,7 +3,6 @@ import { getCollection, render } from 'astro:content'; import CoralComments from '@/components/CoralComments.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro'; import { BlogPost } from '@/components/sections/blog-post'; -import NewsletterSignup from '@/components/sections/newsletter-signup'; import { ACTIVE_BLOG_SITE } from '@/blog-sites.js'; import { SITE_TITLE } from '@/consts'; @@ -23,12 +22,9 @@ const coralStoryURL = new URL(`/${postSlug}/`, ACTIVE_BLOG_SITE.url).toString(); --- -
- - - -
+ + + -
diff --git a/sites/reviews/src/styles/global.css b/sites/reviews/src/styles/global.css index e333af9..081657d 100644 --- a/sites/reviews/src/styles/global.css +++ b/sites/reviews/src/styles/global.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; +@import "../../../../src/styles/article.css"; @plugin 'tailwindcss-animate'; @plugin '@tailwindcss/typography'; diff --git a/sites/vibes/astro.config.mjs b/sites/vibes/astro.config.mjs index 9ce1692..1f85fb3 100644 --- a/sites/vibes/astro.config.mjs +++ b/sites/vibes/astro.config.mjs @@ -1,17 +1,27 @@ // @ts-check import { defineConfig } from "astro/config"; import mdx from "@astrojs/mdx"; +import rehypeKatex from "rehype-katex"; +import remarkMath from "remark-math"; import sitemap from "@astrojs/sitemap"; import react from "@astrojs/react"; // @ts-ignore - TailwindCSS v4 Vite plugin has type compatibility issues with Astro import tailwindcss from "@tailwindcss/vite"; +import remarkCallouts from "../../src/remark-callouts.mjs"; import { ACTIVE_BLOG_SITE } from "./src/blog-sites.js"; +const remarkPlugins = [remarkMath, remarkCallouts]; +const rehypePlugins = [rehypeKatex]; + // https://astro.build/config export default defineConfig({ site: ACTIVE_BLOG_SITE.url, - integrations: [mdx(), sitemap(), react()], + integrations: [mdx({ remarkPlugins, rehypePlugins }), sitemap(), react()], + markdown: { + remarkPlugins, + rehypePlugins, + }, output: "static", outDir: "dist", diff --git a/sites/vibes/src/components/sections/blog-post.tsx b/sites/vibes/src/components/sections/blog-post.tsx index 84022db..cb7319b 100644 --- a/sites/vibes/src/components/sections/blog-post.tsx +++ b/sites/vibes/src/components/sections/blog-post.tsx @@ -1,87 +1,26 @@ -import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar"; -import { Badge } from "@/components/ui/badge"; -import { Button } from "@/components/ui/button"; -import { format } from "date-fns"; -import { Calendar, Clock, ArrowLeft, User } from "lucide-react"; +import type { ReactNode } from "react"; const BlogPost = ({ post, children, }: { post: any; - children: React.ReactNode; + children: ReactNode; }) => { - const { title, authorName, image, pubDate, description, authorImage } = - post.data; + const { title, image } = post.data; + return ( - <> - {/* Hero section with gradient background and post info */} -
-
-
-

- {title} -

-

- {description} -

-
+
+
+ {image ? ( +
+ {title} +
+ ) : null} -
- {/* Author info */} - {authorImage ? ( -
-
- ) : ( -
-
- -
-
- - {authorName || "Anonymous"} - - Author -
-
- )} - - {/* Date info */} -
-
- - {format(pubDate, "MMMM d, yyyy")} -
- -
- - 5 min read -
-
-
-
-
- - {/* Featured image */} - {image && ( -
-
- {title} -
-
- )} - - {/* Article content */} -
-
- {children} -
-
- +
{children}
+ + ); }; diff --git a/sites/vibes/src/pages/[...slug].astro b/sites/vibes/src/pages/[...slug].astro index 62160d4..4b14673 100644 --- a/sites/vibes/src/pages/[...slug].astro +++ b/sites/vibes/src/pages/[...slug].astro @@ -3,7 +3,6 @@ import { getCollection, render } from 'astro:content'; import CoralComments from '@/components/CoralComments.astro'; import DefaultLayout from '@/layouts/DefaultLayout.astro'; import { BlogPost } from '@/components/sections/blog-post'; -import NewsletterSignup from '@/components/sections/newsletter-signup'; import { ACTIVE_BLOG_SITE } from '@/blog-sites.js'; import { SITE_TITLE } from '@/consts'; @@ -23,12 +22,9 @@ const coralStoryURL = new URL(`/${postSlug}/`, ACTIVE_BLOG_SITE.url).toString(); --- -
- - - -
+ + + -
diff --git a/sites/vibes/src/styles/global.css b/sites/vibes/src/styles/global.css index 38e1f98..a048ddd 100644 --- a/sites/vibes/src/styles/global.css +++ b/sites/vibes/src/styles/global.css @@ -1,4 +1,6 @@ @import "tailwindcss"; +@import "katex/dist/katex.min.css"; +@import "../../../../src/styles/article.css"; @plugin 'tailwindcss-animate'; @plugin '@tailwindcss/typography'; diff --git a/src/remark-callouts.mjs b/src/remark-callouts.mjs new file mode 100644 index 0000000..c519661 --- /dev/null +++ b/src/remark-callouts.mjs @@ -0,0 +1,88 @@ +const CALLOUT_LABELS = new Map([ + ["aside", "Aside"], + ["caution", "Caution"], + ["note", "Note"], + ["tip", "Tip"], + ["warning", "Warning"], +]); + +function textFromInline(node) { + if (!node) return ""; + if (typeof node.value === "string") return node.value; + if (!Array.isArray(node.children)) return ""; + return node.children.map(textFromInline).join(""); +} + +function normalizeLabel(value) { + return value + .trim() + .replace(/^["“”]+|["“”]+$/g, "") + .replace(/:$/, "") + .toLowerCase(); +} + +function paragraphCalloutLabel(paragraph) { + if (paragraph?.type !== "paragraph") return null; + + const first = paragraph.children?.[0]; + if (first?.type !== "strong") return null; + + const label = CALLOUT_LABELS.get(normalizeLabel(textFromInline(first))); + if (!label) return null; + + return label; +} + +function removeLeadingLabel(paragraph, label) { + if (paragraph?.type !== "paragraph") return; + + const first = paragraph.children?.[0]; + const firstText = normalizeLabel(textFromInline(first)); + if (!first || first.type !== "strong" || firstText !== label.toLowerCase()) { + return; + } + + paragraph.children.shift(); + + const next = paragraph.children[0]; + if (next?.type === "text") { + next.value = next.value.replace(/^:\s*/, "").replace(/^\s+/, ""); + if (!next.value) paragraph.children.shift(); + } +} + +function transformBlockquote(node) { + if (node.type !== "blockquote") return; + + const first = node.children?.[0]; + const label = paragraphCalloutLabel(first); + if (!label) return; + + removeLeadingLabel(first, label); + if (first.children?.length === 0) { + node.children.shift(); + } + + node.data = { + hName: "aside", + hProperties: { + className: ["callout", `callout-${label.toLowerCase()}`], + dataCalloutLabel: label, + }, + }; +} + +function visit(node) { + transformBlockquote(node); + + if (!Array.isArray(node.children)) return; + for (const child of node.children) { + visit(child); + } +} + +export default function remarkCallouts() { + return function transformer(tree) { + visit(tree); + }; +} diff --git a/src/styles/article.css b/src/styles/article.css new file mode 100644 index 0000000..48d6441 --- /dev/null +++ b/src/styles/article.css @@ -0,0 +1,264 @@ +.blog-post-shell { + margin-inline: auto; + max-width: 78rem; + padding: 3rem 1.75rem 4.5rem; +} + +.blog-article { + margin-inline: auto; + max-width: 68rem; +} + +.blog-post-header { + margin-bottom: 2rem; + border-bottom: 1px solid hsl(var(--border)); + padding-bottom: 1.35rem; +} + +.blog-post-meta { + color: hsl(var(--muted-foreground)); + font-family: var(--font-mono); + font-size: 0.75rem; + letter-spacing: 0.04em; + text-transform: uppercase; +} + +.blog-post-header .blog-post-title { + margin-top: 0.65rem; + color: hsl(var(--foreground)); + font-size: clamp(1.9rem, 3.2vw, 2.45rem); + font-weight: 650; + line-height: 1.08; +} + +.blog-post-description { + margin-top: 0.8rem; + max-width: 42rem; + color: hsl(var(--muted-foreground)); + font-size: 1rem; + line-height: 1.65; +} + +.blog-post-hero-image { + margin: 2rem auto; + overflow: hidden; + border: 1px solid hsl(var(--border)); + border-radius: 0.75rem; +} + +.blog-post-hero-image img { + display: block; + width: 100%; + height: auto; +} + +.article-content { + color: hsl(var(--foreground)); + font-family: var(--font-heliotrope); + font-size: 1.0625rem; + line-height: 1.78; +} + +.article-content > * + * { + margin-top: 1.2rem; +} + +.article-content :where(h1, h2, h3, h4, h5, h6) { + color: hsl(var(--foreground)); + font-family: var(--font-fraunces); + font-style: normal; + font-weight: 650; + letter-spacing: 0; + line-height: 1.18; +} + +.article-content :where(h1) { + margin-top: 2.4rem; + font-size: clamp(1.65rem, 2.5vw, 2.1rem); +} + +.article-content :where(h2) { + margin-top: 2.15rem; + font-size: clamp(1.38rem, 2.1vw, 1.72rem); +} + +.article-content :where(h3) { + margin-top: 1.85rem; + font-size: clamp(1.15rem, 1.75vw, 1.35rem); +} + +.article-content :where(h4, h5, h6) { + margin-top: 1.5rem; + font-size: 1.05rem; +} + +.article-content :where(p, ul, ol, blockquote, pre, table, figure, iframe) { + margin-bottom: 0; +} + +.article-content :where(ul, ol) { + padding-left: 1.35rem; +} + +.article-content :where(li + li) { + margin-top: 0.35rem; +} + +.article-content :where(a) { + color: hsl(var(--primary)); + text-decoration-line: underline; + text-decoration-thickness: 0.08em; + text-underline-offset: 0.18em; +} + +.article-content :where(code) { + border: 1px solid hsl(var(--border)); + border-radius: 0.25rem; + background: hsl(var(--muted)); + padding: 0.08rem 0.28rem; + font-family: var(--font-mono); + font-size: 0.88em; +} + +.article-content :where(pre) { + overflow-x: auto; + border: 1px solid hsl(var(--border)); + border-radius: 0.65rem; + background: hsl(var(--muted)); + padding: 1rem; +} + +.article-content :where(pre code) { + border: 0; + background: transparent; + padding: 0; +} + +.article-content :where(blockquote) { + margin-inline: 0; + border-left: 3px solid hsl(var(--border)); + padding: 0.15rem 0 0.15rem 1rem; + color: color-mix(in srgb, hsl(var(--foreground)) 82%, transparent); + font-style: normal; +} + +.article-content :where(blockquote p) { + font-style: normal; +} + +.article-content :where(blockquote h1) { + font-size: 1.45rem; +} + +.article-content :where(blockquote h2) { + font-size: 1.3rem; +} + +.article-content :where(blockquote h3) { + font-size: 1.12rem; +} + +.article-content :where(.callout) { + margin-block: 1.5rem; + border: 1px solid hsl(var(--border)); + border-left: 4px solid hsl(var(--primary)); + border-radius: 0.7rem; + background: color-mix(in srgb, hsl(var(--accent)) 54%, transparent); + padding: 1rem 1.15rem; + color: hsl(var(--foreground)); +} + +.article-content :where(.callout > *:first-child) { + margin-top: 0; +} + +.article-content :where(.callout h1) { + font-size: 1.35rem; +} + +.article-content :where(.callout h2) { + font-size: 1.25rem; +} + +.article-content :where(.callout h3) { + font-size: 1.1rem; +} + +.article-content :where(aside:not(.callout)) { + margin-block: 1.5rem; + border: 1px solid hsl(var(--border)); + border-radius: 0.7rem; + background: hsl(var(--muted)); + padding: 1rem 1.15rem; +} + +.article-content :where(img) { + display: block; + max-width: 100%; + height: auto; + margin-inline: auto; + border-radius: 0.35rem; +} + +.article-content :where(figure) { + margin-inline: 0; +} + +.article-content :where(figcaption) { + color: hsl(var(--muted-foreground)); + font-size: 0.92rem; + text-align: center; +} + +.article-content :where(iframe) { + display: block; + width: 100%; + max-width: 100%; + aspect-ratio: 16 / 9; + height: auto; + margin-inline: auto; + border: 0; +} + +.article-content :where(table) { + display: block; + width: 100%; + overflow-x: auto; + border-collapse: collapse; +} + +.article-content :where(th, td) { + border: 1px solid hsl(var(--border)); + padding: 0.45rem 0.6rem; +} + +.article-content :where(.twitter-tweet) { + max-width: 100% !important; + margin: 1.5rem auto !important; +} + +.article-content :where(.katex) { + font-size: 1.02em; +} + +.article-content :where(.katex-display) { + overflow-x: auto; + overflow-y: hidden; + padding: 0.25rem 0; +} + +@media (min-width: 768px) { + .blog-post-shell { + padding-top: 4rem; + } +} + +@media (max-width: 640px) { + .blog-post-shell { + padding-inline: 1.15rem; + } + + .article-content { + font-size: 1rem; + } +}