Makko, the people-oriented static site generator made for blogging.

Docs cleanup

+483 -43
+19 -12
doc/blog/english.md
··· 2 2 id: TGm1pyN0WiY 3 3 title: English Docs 4 4 description: Documentation for the Makko Static Site Generator and blogging tool. 5 - author: Starlight Network Team 5 + author: The Starlight Network 6 6 visibility: public 7 7 disable_templating: true 8 8 created: 2025-07-17T21:09:58+00:00 9 9 --- 10 10 11 + ![Makko, a pink rabbit with huge glasses, trying to balance.](/media/docs.svg) 12 + 11 13 ### Some notes before you begin: 12 - > Makko assumes you have some level of knowledge working with websites, specifically some knowledge about writing and reading basic HTML. You can refer to MDN which has guides for HTML and more [here on their website](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Structuring_content) 14 + Makko assumes you have some level of knowledge working with websites, specifically some knowledge about writing and reading basic HTML. You can refer to MDN which has guides for HTML and more [here on their website](https://developer.mozilla.org/en-US/docs/Learn_web_development/Core/Structuring_content) 13 15 14 16 > **If you are coming from Makko 1.0 or Makko 1.1, please scroll to the [migration section](#makko-migration).** 15 17 ··· 84 86 85 87 `templates/feed.html` 86 88 ```html 87 - <head> 88 - <title>{{website.title}}</title> 89 - </head> 90 - <body> 91 - <h1>{{website.title}}</h1> 92 - <p1 style="opacity: 60%">{{website.description}}</p1> 93 - ... 94 - </body> 89 + <!doctype html> 90 + <html lang="en"> 91 + <head> 92 + <title>{{website.title}}</title> 93 + </head> 94 + <body> 95 + <h1>{{website.title}}</h1> 96 + <p1 style="opacity: 60%">{{website.description}}</p1> 97 + ... 98 + </body> 99 + </html> 95 100 ``` 96 101 97 102 You get: ··· 292 297 - `disable_templating`: Do you want to use `{{}}` in your post but Mustache's templating gets in your way? setting this to `true` will delegate templating only for the feed and the post template. 293 298 294 299 > **Note:** The Frontmatter intentionally does not support `{{}}` templates. 300 + 295 301 > **IMPORTANT!!!** Currently, the frontmatter is actually **NOT TO-SPEC YAML**, this is an implementation issue that should be resolved shortly once Makko can get its own proper YAML parser instead of [having a pretty broken one.](https://forge.starlightnet.work/Team/Makko/issues/12) 296 302 297 303 ··· 370 376 // Lists of posts with X tag, same format as "post" field. 371 377 // Only valid in "feed.html". 372 378 "by_tag": { 373 - "awesome": [...], 379 + "awesome": [...], 374 380 "super-cool": [...], 375 - "dope-post": [...], 381 + "dope-post": [...], 376 382 ... 377 383 } 378 384 ``` ··· 389 395 author: THe GuY 123 xXGamerXx 390 396 visibility: public 391 397 --- 398 + 392 399 <h1>hey check this picture of a dog that i found</h1> 393 400 <img src="dogpicture.png"/> 394 401 <p>pretty neat huh</p>
+334
doc/blog/styles.css
··· 1 + /* STUFFS */ 2 + :root { 3 + --background: light-dark(white, #131313); 4 + --foreground: light-dark(#131313, white); 5 + --accent: light-dark(hsl(269, 80%, 40%), hsl(269, 65%, 75%)); 6 + --accent-transparent: light-dark( 7 + hsla(269, 60%, 70%, 20%), 8 + hsla(269, 80%, 40%, 20%) 9 + ); 10 + --opacity: 0.8; 11 + color-scheme: light dark; 12 + } 13 + 14 + /* FONTS */ 15 + @font-face { 16 + font-family: "Departure Mono"; 17 + src: url("/media/DepartureMono-Regular.woff2") format("woff2"); 18 + } 19 + 20 + /* BASE */ 21 + body { 22 + min-height: 100vh; 23 + max-width: 800px; 24 + padding: 40px; 25 + margin: auto; 26 + 27 + display: flex; 28 + flex-direction: column; 29 + gap: 40px; 30 + 31 + line-height: 1.2lh; 32 + 33 + font-family: 34 + ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, Consolas, 35 + "DejaVu Sans Mono", monospace; 36 + 37 + background-color: var(--background); 38 + background-size: cover 100%; 39 + background-position: bottom; 40 + 41 + color: var(--foreground); 42 + } 43 + 44 + section:has(footer) { 45 + margin-top: auto; 46 + } 47 + 48 + /* TEXT AND HEADINGS */ 49 + * { 50 + margin: 0; 51 + padding: 0; 52 + } 53 + 54 + h1:not(:first-child), 55 + h2:not(:first-child) { 56 + margin-top: 65px; 57 + } 58 + 59 + h3:not(:first-child), 60 + h4:not(:first-child) { 61 + margin-top: 35px; 62 + } 63 + 64 + h5:not(:first-child), 65 + h6:not(:first-child) { 66 + margin-top: 30px; 67 + } 68 + 69 + /* LINKS */ 70 + a, 71 + a:visited { 72 + text-decoration: underline dashed; 73 + } 74 + 75 + a:visited { 76 + color: var(--accent); 77 + } 78 + 79 + a { 80 + color: var(--accent); 81 + background-color: var(--accent-transparent); 82 + padding: 1px 4px; 83 + } 84 + 85 + .buttons { 86 + display: flex; 87 + flex-wrap: wrap; 88 + align-items: start; 89 + gap: 0.5rem; 90 + } 91 + 92 + .buttons a { 93 + background: none; 94 + text-decoration: none; 95 + } 96 + 97 + .buttons img { 98 + display: block; 99 + font-family: inherit; 100 + font-weight: 900; 101 + width: 88px; 102 + height: 31px; 103 + font-size: 1.2em; 104 + border: 0.1em solid var(--foreground); 105 + border-radius: 0px; 106 + box-shadow: 0.1em 0.1em; 107 + cursor: pointer; 108 + color: var(--accent); 109 + transition: 0.1s; 110 + } 111 + 112 + .buttons img:hover { 113 + transform: translate(-0.05em, -0.05em); 114 + box-shadow: 0.15em 0.15em; 115 + } 116 + 117 + .buttons img:active { 118 + transform: translate(0.05em, 0.05em); 119 + box-shadow: 0em 0em; 120 + } 121 + 122 + /* TABLES */ 123 + table { 124 + margin: 10px 0; 125 + border-collapse: collapse; 126 + } 127 + 128 + table, 129 + th, 130 + td { 131 + border: 1px solid; 132 + padding: 5px; 133 + } 134 + 135 + /* LAYOUT */ 136 + section, 137 + main { 138 + display: flex; 139 + flex-direction: column; 140 + gap: 20px; 141 + } 142 + 143 + blockquote { 144 + border-left: 2px solid grey; 145 + margin: 0; 146 + padding-inline: 1em; 147 + opacity: var(--opacity); 148 + } 149 + 150 + /* DIVISION */ 151 + hr { 152 + border: none; 153 + margin: 0 auto; 154 + } 155 + 156 + hr:before { 157 + content: "--------- @ ---------"; 158 + text-align: center; 159 + } 160 + 161 + /* MEDIA */ 162 + img { 163 + max-width: 100%; 164 + } 165 + 166 + /* NAVIGATION */ 167 + nav { 168 + display: flex; 169 + } 170 + 171 + nav > a { 172 + background-color: var(--background); 173 + padding-right: 16px; 174 + width: fit-content; 175 + text-wrap: nowrap; 176 + } 177 + 178 + nav > img { 179 + min-width: 15%; 180 + width: 100%; 181 + height: 1.1lh; 182 + border-radius: 0; 183 + } 184 + 185 + /* CODE */ 186 + :not(pre) > code, 187 + pre:has(code) { 188 + border: none; 189 + background-color: light-dark(rgba(225, 225, 225, 0.5), rgba(0, 0, 0, 0.5)); 190 + color: light-dark(black, white); 191 + } 192 + 193 + :not(pre) > code { 194 + padding: 4px 6px; 195 + } 196 + 197 + pre:has(code) { 198 + overflow-x: auto; 199 + tab-size: 2; 200 + padding: 20px; 201 + } 202 + 203 + code { 204 + font-size: 1.1em; 205 + padding: 0; 206 + } 207 + 208 + /* LISTS */ 209 + ul, 210 + ol { 211 + display: flex; 212 + flex-direction: column; 213 + gap: 0.8lh; 214 + margin: 0; 215 + padding-left: 40px; 216 + } 217 + 218 + li { 219 + list-style: "→ "; 220 + opacity: var(--opacity); 221 + text-align: left; 222 + } 223 + 224 + /* MISCELANEOUS */ 225 + .buttons { 226 + image-rendering: pixelated; 227 + } 228 + 229 + /* p > img { width: 200px; } */ 230 + 231 + hero { 232 + text-align: center; 233 + } 234 + 235 + hero > h1 { 236 + line-height: 1.4lh; 237 + padding-bottom: 0.5lh; 238 + } 239 + 240 + .break { 241 + background-image: url(/media/gradient.webp); 242 + background-size: cover; 243 + width: 100%; 244 + height: 2px; 245 + } 246 + 247 + .special { 248 + background-image: url(/media/gradient.webp); 249 + color: black; 250 + padding: 2px 4px; 251 + background-size: contain; 252 + } 253 + 254 + #toc-container { 255 + position: fixed; 256 + left: 20px; 257 + top: 100px; 258 + width: 300px; 259 + max-height: calc(100vh - 120px); 260 + overflow-y: auto; 261 + padding: 15px; 262 + z-index: 1000; 263 + 264 + display: flex; 265 + flex-direction: column; 266 + gap: 20px; 267 + } 268 + 269 + #toc-content ul { 270 + list-style: none; 271 + 272 + padding-left: 20px; 273 + padding-top: 10px; 274 + display: flex; 275 + flex-direction: column; 276 + gap: 5px; 277 + } 278 + 279 + #toc-content li { 280 + list-style: none; 281 + } 282 + 283 + #toc-content a { 284 + color: #333; 285 + text-decoration: none; 286 + display: block; 287 + padding: 6px 8px; 288 + background-color: transparent; 289 + font-weight: normal; 290 + } 291 + 292 + #toc-content a:hover { 293 + background-color: #f0f0f0; 294 + color: var(--accent); 295 + font-weight: bold; 296 + } 297 + 298 + #toc-content a.active { 299 + background-color: var(--accent-transparent); 300 + color: var(--accent); 301 + font-weight: 500; 302 + font-weight: bold; 303 + } 304 + 305 + .header-link { 306 + font-size: 0.5em; 307 + } 308 + 309 + @media (max-width: 1500px) { 310 + body { 311 + margin: 0; 312 + margin-left: auto; 313 + } 314 + } 315 + 316 + @media (max-width: 1200px) { 317 + #toc-container { 318 + display: none; 319 + } 320 + 321 + body { 322 + margin: auto; 323 + } 324 + } 325 + 326 + @media (prefers-color-scheme: light) { 327 + body { 328 + background: none; 329 + } 330 + 331 + .invertible { 332 + filter: invert(1); 333 + } 334 + }
+122
doc/blog/toc.js
··· 1 + const OFFSET = 300; 2 + 3 + window.addEventListener("load", function () { 4 + function toKebabCase(text) { 5 + return text 6 + .toLowerCase() 7 + .trim() 8 + .replace(/[^\w\s-]/g, "") 9 + .replace(/\s+/g, "-") 10 + .replace(/-+/g, "-"); 11 + } 12 + 13 + const headers = document.querySelectorAll("h1, h2, h3, h4, h5, h6"); 14 + headers.forEach((header) => { 15 + if (!header.id) { 16 + header.id = toKebabCase(header.textContent); 17 + } 18 + }); 19 + 20 + const tocItems = []; 21 + headers.forEach((header) => { 22 + const level = parseInt(header.tagName.substring(1)); 23 + tocItems.push({ 24 + id: header.id, 25 + text: header.textContent, 26 + level: level, 27 + children: [], 28 + }); 29 + }); 30 + 31 + function buildTree(items) { 32 + const root = { children: [], level: 0 }; 33 + const stack = [root]; 34 + 35 + items.forEach((item) => { 36 + while ( 37 + stack.length > 1 && 38 + stack[stack.length - 1].level >= item.level 39 + ) { 40 + stack.pop(); 41 + } 42 + 43 + const parent = stack[stack.length - 1]; 44 + parent.children.push(item); 45 + 46 + stack.push(item); 47 + }); 48 + 49 + return root.children; 50 + } 51 + 52 + function buildTocHTML(items) { 53 + if (items.length === 0) return ""; 54 + 55 + let html = "<ul>"; 56 + items.forEach((item) => { 57 + html += `<li>`; 58 + html += `<a href="#${item.id}">${item.text}</a>`; 59 + if (item.children.length > 0) { 60 + html += buildTocHTML(item.children); 61 + } 62 + html += `</li>`; 63 + }); 64 + html += "</ul>"; 65 + 66 + return html; 67 + } 68 + 69 + const tree = buildTree(tocItems); 70 + 71 + const tocContainer = document.createElement("div"); 72 + tocContainer.id = "toc-container"; 73 + tocContainer.innerHTML = ` 74 + <div id="toc-content"> 75 + ${buildTocHTML(tree)} 76 + </div> 77 + `; 78 + 79 + document.body.appendChild(tocContainer); 80 + 81 + document.querySelectorAll("#toc-content a").forEach((link) => { 82 + link.addEventListener("click", function (e) { 83 + e.preventDefault(); 84 + const targetId = this.getAttribute("href").substring(1); 85 + const targetElement = document.getElementById(targetId); 86 + 87 + if (targetElement) { 88 + const offsetPosition = targetElement.offsetTop - OFFSET; 89 + window.scrollTo({ 90 + top: offsetPosition, 91 + behavior: "smooth", 92 + }); 93 + history.pushState(null, null, `#${targetId}`); 94 + } 95 + }); 96 + }); 97 + 98 + function highlightCurrentSection() { 99 + let currentId = ""; 100 + const scrollPos = window.scrollY + OFFSET; 101 + 102 + headers.forEach((header) => { 103 + if (header.offsetTop <= scrollPos) { 104 + currentId = header.id; 105 + } 106 + }); 107 + 108 + if (currentId && window.location.hash !== `#${currentId}`) { 109 + history.replaceState(null, null, `#${currentId}`); 110 + } 111 + 112 + document.querySelectorAll("#toc-content a").forEach((link) => { 113 + link.classList.remove("active"); 114 + if (link.getAttribute("href") === `#${currentId}`) { 115 + link.classList.add("active"); 116 + } 117 + }); 118 + } 119 + 120 + window.addEventListener("scroll", highlightCurrentSection); 121 + highlightCurrentSection(); 122 + });
+1 -1
doc/makko.json
··· 23 23 "on_modify": null 24 24 }, 25 25 "hashes": { 26 - "TGm1pyN0WiY": "ndLHjzJIwAw" 26 + "TGm1pyN0WiY": "ckFxMoBFaXU" 27 27 } 28 28 }
+1 -1
doc/templates/feed.html
··· 5 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 6 <title>{{website.title}}</title> 7 7 8 - <link rel="stylesheet" href="https://starlightnet.work/styles.css" /> 8 + <link rel="stylesheet" href="/styles.css" /> 9 9 </head> 10 10 11 11 <body>
+3 -11
doc/templates/post.html
··· 6 6 <title>{{title}}</title> 7 7 8 8 <script src="/highlight.min.js"></script> 9 + <script src="toc.js"></script> 9 10 10 11 <script> 11 12 hljs.highlightAll(); 12 13 </script> 13 14 14 15 <link rel="stylesheet" href="/highlight.css" /> 15 - <link rel="stylesheet" href="https://starlightnet.work/styles.css" /> 16 + <link rel="stylesheet" href="/styles.css" /> 16 17 </head> 17 18 18 19 <body> 19 20 <nav> 20 - <a href="/">←</a> 21 + <a href="/">INDEX</a> 21 22 <img src="/media/gradient.webp" /> 22 23 </nav> 23 - 24 - <section> 25 - <img 26 - src="/media/docs.svg" 27 - loading="lazy" 28 - style="max-height: 300px" 29 - alt="Makko's mascot, with oversized glasses, trying to balance." 30 - /> 31 - </section> 32 24 33 25 {{#post}} 34 26
+1 -1
makko.schema.json
··· 1 1 { 2 - "title": "Makko Blog Configuration", 2 + "title": "Makko Website Settings", 3 3 "type": "object", 4 4 "properties": { 5 5 "$schema": {
+2 -17
src/network.zig
··· 29 29 30 30 const args = .{ @tagName(req.method), req.url.path }; 31 31 if (self.log.colors) 32 - self.log.raw("[{s}] \x1b[2m{s:<30}\x1b[0m ", args) 32 + self.log.raw("[{s}] \x1b[2m{s}\x1b[0m\n", args) 33 33 else 34 - self.log.raw("[{s}] {s:<30} ", args); 34 + self.log.raw("[{s}] {s}\n", args); 35 35 36 36 action(self, req, res) catch |err| { 37 37 const writer = res.writer(); ··· 49 49 res.content_type = .HTML; 50 50 res.status = 500; 51 51 }; 52 - 53 - const icon = switch (res.status) { 54 - 200 => "", 55 - 300...309 => "↩️", 56 - else => "❌", 57 - }; 58 - 59 - if (self.log.colors) { 60 - const color = switch (res.status) { 61 - 200 => "", 62 - 300...309 => "\x1b[2m", 63 - else => "\x1b[31m", 64 - }; 65 - self.log.raw("{s} [{}] {s}\x1b[0m\n", .{ color, res.status, icon }); 66 - } else self.log.raw("[{}] {s}\n", .{ res.status, icon }); 67 52 } 68 53 }; 69 54