Musings from the mountains himwant.org

Setup Book of Code

+183
+11
src/content.config.ts
··· 23 23 }), 24 24 }) 25 25 26 + const aocCollection = defineCollection({ 27 + loader: glob({ pattern: ['**/*.md', '**/*.mdx'], base: './src/content/aoc' }), 28 + 29 + schema: z.object({ 30 + title: z.string(), // e.g. "Trebuchet?!" 31 + tags: z.array(z.string()).optional().default([]), // e.g. ["rust", "parsing"] 32 + }), 33 + }); 34 + 35 + 26 36 const homeCollection = defineCollection({ 27 37 loader: glob({ pattern: ['home.md', 'home.mdx'], base: './src/content' }), 28 38 schema: ({ image }) => ··· 52 62 53 63 export const collections = { 54 64 posts: postsCollection, 65 + aoc: aocCollection, 55 66 home: homeCollection, 56 67 addendum: addendumCollection, 57 68 }
+39
src/content/aoc/2025/01.md
··· 1 + --- 2 + title: "Secret Entrance" 3 + published: 2025-12-15 4 + draft: false 5 + --- 6 + 7 + 8 + 9 + # Part 1 10 + 11 + We are given line seperated operations that happen on a 100 pin circular dial. The dial starts at 50 and we are to output, how many times the dial stops at 0! 12 + 13 + An example- 14 + 15 + > L68 16 + > L30 17 + > R48 18 + > L5 19 + > R60 20 + > L55 21 + > L1 22 + > L99 23 + > R14 24 + > L82 25 + 26 + - Dial @ 50 27 + - L68 -> 82 28 + - L30 -> 52 29 + - R48 -> **0**! 30 + - L5 -> 95 31 + - R60 -> 55 32 + - L55 -> **0**! 33 + - L1 -> 99 34 + - L99 -> **0**! 35 + - R14 -> 14 36 + - L82 -> 32 37 + 38 + So, in total `3` times! `3` is our answer! 39 +
+10
src/content/aoc/2025/01.org
··· 1 + #+title: Secret Entrance 2 + #+OPTIONS: toc:nil 3 + 4 + * Part 1 5 + We are given line seperated operations that happen on a 100 pin circular dial. The dial starts at 50 and we are to output, how many times the dial stops at 0! 6 + 7 + #+begin_src rust 8 + #+end_src 9 + 10 + #+RESULTS:
+45
src/pages/aoc/[...slug].astro
··· 1 + --- 2 + import Layout from '~/layouts/Layout.astro' 3 + import { getCollection, render } from 'astro:content' 4 + import TableOfContents from '~/components/TableOfContents.astro' 5 + 6 + export async function getStaticPaths() { 7 + const posts = await getCollection('aoc'); 8 + return posts.map((post) => { 9 + // We strip the extension for the URL slug: "2024/01.md" -> "2024/01" 10 + const slug = post.id.replace(/\.(md|mdx)$/, ''); 11 + return { 12 + params: { slug }, 13 + props: { post }, 14 + }; 15 + }); 16 + } 17 + 18 + const { post } = Astro.props; 19 + const { headings, Content } = await render(post); 20 + 21 + // Parse ID for display 22 + const parts = post.id.split('/'); 23 + const year = parseInt(parts[0]); 24 + const day = parseInt(parts[1].replace(/\.(md|mdx)$/, '')); 25 + --- 26 + 27 + <Layout title={`AoC ${year} Day ${day}`}> 28 + <article class="max-w-full py-7.5"> 29 + <header class="mb-8 border-l-4 border-accent pl-4"> 30 + <h1 class="text-3xl font-bold font-mono text-heading1"> 31 + Day {day}: {post.data.title} 32 + </h1> 33 + <div class="mt-2 text-sm font-mono opacity-70"> 34 + Year: {year} 35 + </div> 36 + </header> 37 + 38 + <div class="flex flex-col xl:flex-row xl:items-start"> 39 + {headings.length > 0 && <TableOfContents headings={headings} />} 40 + <div class="mb-5 prose prose-code:before:hidden prose-code:after:hidden xl:min-w-full"> 41 + <Content /> 42 + </div> 43 + </div> 44 + </article> 45 + </Layout>
+78
src/pages/aoc/index.astro
··· 1 + --- 2 + import Layout from '~/layouts/Layout.astro' 3 + import { getCollection } from 'astro:content' 4 + 5 + const allAoc = await getCollection('aoc'); 6 + 7 + // --- ADD THIS DEBUG BLOCK --- 8 + console.log("------------------------------------------------"); 9 + console.log("DEBUG: Total AoC posts found:", allAoc.length); 10 + if (allAoc.length > 0) { 11 + console.log("DEBUG: First Post ID:", allAoc[0].id); 12 + console.log("DEBUG: First Post Data:", allAoc[0].data); 13 + } else { 14 + console.log("DEBUG: No posts found! Check src/content/config.ts"); 15 + } 16 + console.log("------------------------------------------------"); 17 + // ---------------------------- 18 + 19 + // Helper to extract metadata from path 20 + function parseAocId(id: string) { 21 + const parts = id.split('/'); 22 + const year = parseInt(parts[0]); 23 + const filename = parts[1] || ""; 24 + const day = parseInt(filename.replace(/\.(md|mdx)$/, '')); 25 + return { year, day }; 26 + } 27 + 28 + // Attach parsed data to each post for sorting/grouping 29 + const enrichedAoc = allAoc.map(post => ({ 30 + ...post, 31 + meta: parseAocId(post.id) 32 + })); 33 + 34 + // Sort: Newest Year -> Day 1 -> Day 25 35 + enrichedAoc.sort((a, b) => { 36 + if (a.meta.year !== b.meta.year) return b.meta.year - a.meta.year; 37 + return a.meta.day - b.meta.day; 38 + }); 39 + 40 + // Group by Year 41 + const groupedAoc = enrichedAoc.reduce((acc, post) => { 42 + const y = post.meta.year; 43 + if (!acc[y]) acc[y] = []; 44 + acc[y].push(post); 45 + return acc; 46 + }, {}); 47 + 48 + const years = Object.keys(groupedAoc).sort((a, b) => b - a); 49 + --- 50 + 51 + <Layout title="Advent of Code"> 52 + <div class="max-w-3xl mx-auto py-7.5 px-4 md:px-0"> 53 + <h1 class="text-3xl font-bold mb-8">Advent of Code</h1> 54 + 55 + {years.map((year) => ( 56 + <section class="mb-12"> 57 + <h2 class="text-2xl font-bold mb-4 border-b border-gray-200 dark:border-gray-700 pb-2"> 58 + {year} 59 + </h2> 60 + <div class="grid grid-cols-1 sm:grid-cols-2 gap-4"> 61 + {groupedAoc[year].map((post) => ( 62 + <a 63 + href={`/aoc/${post.id.replace(/\.(md|mdx)$/, '')}`} 64 + class="block p-4 rounded-lg border border-gray-200 dark:border-gray-700 hover:border-accent hover:shadow-sm transition-all group" 65 + > 66 + <div class="flex items-center justify-between mb-2"> 67 + <span class="text-sm font-mono text-accent">Day {post.meta.day.toString().padStart(2, '0')}</span> 68 + </div> 69 + <h3 class="font-medium group-hover:text-accent transition-colors"> 70 + {post.data.title} 71 + </h3> 72 + </a> 73 + ))} 74 + </div> 75 + </section> 76 + ))} 77 + </div> 78 + </Layout>