Makko, the people-oriented static site generator made for blogging. forge.starlightnet.work/Team/Makko
ssg static-site-generator makko starlight-network
at main 12 kB view raw
1<!doctype html> 2<html lang="en"> 3 <head> 4 <meta charset="UTF-8" /> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0" /> 6 <title>Makko!</title> 7 <style> 8 :root { 9 --background: light-dark(white, #131313); 10 --foreground: light-dark(#131313, white); 11 --accent: light-dark(hsl(269, 80%, 40%), hsl(269, 65%, 75%)); 12 --accent-transparent: light-dark( 13 hsla(269, 60%, 70%, 20%), 14 hsla(269, 80%, 40%, 20%) 15 ); 16 --opacity: 0.8; 17 --lines: light-dark(var(--foreground), var(--accent)); 18 color-scheme: light dark; 19 } 20 21 * { 22 margin: 0; 23 padding: 0; 24 box-sizing: border-box; 25 } 26 27 body { 28 font-family: 29 ui-monospace, "Cascadia Code", "Source Code Pro", Menlo, 30 Consolas, "DejaVu Sans Mono", monospace; 31 overflow: hidden; 32 } 33 34 .banner { 35 position: fixed; 36 top: 0; 37 left: 0; 38 width: 100%; 39 color: var(--foreground); 40 padding: 8px 12px; 41 z-index: 999999; 42 transition: opacity 0.3s ease; 43 height: 50px; 44 display: flex; 45 align-items: center; 46 border-bottom: 1px solid var(--lines); 47 } 48 49 .banner.offline { 50 opacity: 0.7; 51 } 52 53 .status-text { 54 white-space: nowrap; 55 } 56 57 .url-bar { 58 flex: 1; 59 width: 100%; 60 overflow-x: auto; 61 white-space: nowrap; 62 } 63 64 .url-path { 65 display: flex; 66 gap: 6px; 67 padding: 6px 10px; 68 } 69 70 .url-bar::-webkit-scrollbar { 71 height: 4px; 72 } 73 74 .url-bar::-webkit-scrollbar-track { 75 background: #333; 76 } 77 78 .url-bar::-webkit-scrollbar-thumb { 79 background: #666; 80 border-radius: 2px; 81 } 82 83 .url-bar a { 84 color: var(--accent); 85 text-decoration: none; 86 } 87 88 .url-bar a:hover { 89 text-decoration: underline; 90 } 91 92 .hamburger { 93 display: none; 94 background: none; 95 border: none; 96 color: var(--foreground); 97 font-size: 20px; 98 cursor: pointer; 99 padding: 4px 8px; 100 } 101 102 .sidebar { 103 position: fixed; 104 top: 50px; 105 left: 0; 106 width: 250px; 107 height: calc(100vh - 50px); 108 background: var(--background); 109 color: var(--foreground); 110 padding: 16px; 111 overflow-y: auto; 112 transition: transform 0.3s ease; 113 z-index: 999998; 114 border-right: 1px solid var(--lines); 115 } 116 117 .sidebar h3 { 118 font-size: 0.8em; 119 margin-bottom: 12px; 120 color: var(--foreground); 121 text-transform: uppercase; 122 } 123 124 .sidebar-links { 125 list-style: none; 126 } 127 128 .content-frame { 129 position: fixed; 130 top: 50px; 131 left: 250px; 132 width: calc(100% - 250px); 133 height: calc(100vh - 50px); 134 border: none; 135 transition: 136 left 0.3s ease, 137 width 0.3s ease; 138 } 139 140 a, 141 a:visited { 142 text-decoration: underline dashed; 143 } 144 145 a:visited { 146 color: var(--accent); 147 } 148 149 a { 150 color: var(--accent); 151 background-color: var(--accent-transparent); 152 padding: 4px 8px; 153 } 154 155 @media (max-width: 768px) { 156 .hamburger { 157 display: block; 158 } 159 160 .sidebar { 161 transform: translateX(-100%); 162 } 163 164 .sidebar.open { 165 transform: translateX(0); 166 } 167 168 .content-frame { 169 left: 0; 170 width: 100%; 171 } 172 } 173 </style> 174 </head> 175 <body> 176 <div class="banner"> 177 <button class="hamburger"></button> 178 <div class="url-bar"> 179 <span class="url-path">/</span> 180 </div> 181 <span class="status-text">LIVE!</span> 182 </div> 183 184 <div class="sidebar"> 185 <h3>Navigation</h3> 186 <ul class="sidebar-links"> 187 <li><a href="/">Home</a></li> 188 <li><a href="/about.html">About</a></li> 189 <li><a href="/blog/">Blog</a></li> 190 </ul> 191 </div> 192 193 <iframe 194 class="content-frame" 195 src="index.html?makko-disable-banner=1" 196 ></iframe> 197 198 <script> 199 (function () { 200 const statusText = document.querySelector(".status-text"); 201 const banner = document.querySelector(".banner"); 202 const iframe = document.querySelector(".content-frame"); 203 const sidebar = document.querySelector(".sidebar"); 204 const hamburger = document.querySelector(".hamburger"); 205 const urlPath = document.querySelector(".url-path"); 206 207 let lastState = null; 208 let currentSrc = iframe.src; 209 210 hamburger.addEventListener("click", function () { 211 sidebar.classList.toggle("open"); 212 }); 213 214 document.addEventListener("click", function (e) { 215 if ( 216 window.innerWidth <= 768 && 217 sidebar.classList.contains("open") && 218 !sidebar.contains(e.target) && 219 e.target !== hamburger 220 ) { 221 sidebar.classList.remove("open"); 222 } 223 }); 224 225 function updateUrlBar(url) { 226 const urlObj = new URL(url); 227 const pathname = urlObj.pathname; 228 229 if (pathname === "/") { 230 urlPath.innerHTML = '<a href="/">/</a>'; 231 return; 232 } 233 234 const parts = pathname 235 .split("/") 236 .filter((p) => (p == "/" ? null : p)); 237 let html = '<a href="/">/</a>'; 238 let accumulatedPath = ""; 239 240 parts.forEach((part, index) => { 241 accumulatedPath += "/" + part; 242 const isLast = index === parts.length - 1; 243 244 if (isLast && !part.includes(".")) { 245 // Directory without trailing slash 246 html += `<a href="${accumulatedPath}/">${part}</a>`; 247 } else if (isLast) { 248 // File 249 html += `<a href="${accumulatedPath}">/${part}</a>`; 250 } else { 251 // Directory in the middle 252 html += `<a href="${accumulatedPath}/">${part}</a>`; 253 } 254 html += " "; 255 }); 256 257 urlPath.innerHTML = html; 258 } 259 260 // Navigate to URL in iframe 261 function navigateTo(url) { 262 const urlObj = new URL(url, window.location.origin); 263 urlObj.searchParams.set("makko-disable-banner", "1"); 264 iframe.src = urlObj.href; 265 currentSrc = urlObj.href; 266 updateUrlBar(urlObj.href); 267 268 if (window.innerWidth <= 768) { 269 sidebar.classList.remove("open"); 270 } 271 } 272 273 // Handle sidebar link clicks 274 sidebar.addEventListener("click", function (e) { 275 const link = e.target.closest("a"); 276 if (link && link.href) { 277 e.preventDefault(); 278 navigateTo(link.href); 279 } 280 }); 281 282 // Handle URL bar clicks 283 urlPath.addEventListener("click", function (e) { 284 const link = e.target.closest("a"); 285 if (link && link.href) { 286 e.preventDefault(); 287 navigateTo(link.href); 288 } 289 }); 290 291 // Intercept navigation within the iframe 292 iframe.addEventListener("load", function () { 293 try { 294 const iframeDoc = 295 iframe.contentDocument || 296 iframe.contentWindow.document; 297 const iframeUrl = iframe.contentWindow.location.href; 298 updateUrlBar(iframeUrl); 299 300 // Intercept all clicks on links 301 iframeDoc.addEventListener( 302 "click", 303 function (e) { 304 const target = e.target.closest("a"); 305 if (target && target.href) { 306 const url = new URL(target.href); 307 308 // Only intercept same-origin links 309 if (url.origin === window.location.origin) { 310 e.preventDefault(); 311 navigateTo(url.href); 312 } 313 } 314 }, 315 true, 316 ); 317 } catch (e) { 318 console.log("Cannot intercept cross-origin iframe"); 319 } 320 }); 321 322 async function poll() { 323 try { 324 const response = await fetch("/.makko/state"); 325 const result = await response.text(); 326 const parsed = parseInt(result.trim(), 10); 327 328 if (!Number.isNaN(parsed)) { 329 banner.classList.remove("offline"); 330 statusText.textContent = "LIVE!"; 331 332 if (lastState !== null && parsed !== lastState) { 333 navigateTo(currentSrc); 334 } 335 336 lastState = parsed; 337 } else { 338 throw new Error("Invalid response"); 339 } 340 } catch (e) { 341 banner.classList.add("offline"); 342 statusText.textContent = "OFFLINE..."; 343 } 344 } 345 346 updateUrlBar(currentSrc); 347 setInterval(poll, 300); 348 })(); 349 </script> 350 </body> 351</html>