this repo has no description
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

feat: add mobile responsive layout with drawer nav

Add viewport meta tag, hamburger menu with slide-out drawer,
inline date banners, and content optimizations for mobile.
All changes scoped to @media (max-width: 768px) so desktop
layout is unchanged.

+403 -14
+294 -14
internal/assets/css/screen.css
··· 1001 1001 1002 1002 /* Material Symbols Sizing */ 1003 1003 1004 + /* Mobile Elements - hidden on desktop */ 1005 + .mobile-hamburger, 1006 + .mobile-site-name, 1007 + .mobile-drawer, 1008 + .mobile-drawer-backdrop { 1009 + display: none; 1010 + } 1011 + 1004 1012 /* Mobile Responsiveness */ 1005 1013 @media (max-width: 768px) { 1006 1014 #page { 1007 - padding: var(--space-3) var(--space-3) 80px var(--space-3); 1015 + padding: var(--space-3) var(--space-3) 40px var(--space-3); 1008 1016 max-width: 100%; 1009 1017 } 1010 1018 1011 1019 #masthead { 1012 - font-size: 48px; 1013 - line-height: 1.1; 1014 - flex-direction: column; 1015 - align-items: flex-start; 1016 - margin-bottom: var(--space-4); 1020 + display: none; 1017 1021 } 1018 1022 1019 1023 #sidebar { 1020 1024 display: none; 1021 1025 } 1022 1026 1023 - /* Date Widget on Mobile */ 1027 + #footer { 1028 + display: none; 1029 + } 1030 + 1031 + /* Header: hide desktop nav, show hamburger + site name */ 1032 + .nav-left, 1033 + .nav-right { 1034 + display: none !important; 1035 + } 1036 + 1037 + .mobile-site-name { 1038 + display: block; 1039 + font-size: 20px; 1040 + font-weight: bold; 1041 + color: var(--text-primary); 1042 + text-decoration: none; 1043 + letter-spacing: -0.02em; 1044 + } 1045 + 1046 + .mobile-hamburger { 1047 + display: flex; 1048 + } 1049 + 1050 + .nav-header { 1051 + justify-content: space-between; 1052 + } 1053 + 1054 + /* Drawer backdrop */ 1055 + .mobile-drawer-backdrop { 1056 + display: block; 1057 + position: fixed; 1058 + top: 0; 1059 + left: 0; 1060 + right: 0; 1061 + bottom: 0; 1062 + background: rgba(0, 0, 0, 0.5); 1063 + z-index: 1999; 1064 + opacity: 0; 1065 + pointer-events: none; 1066 + transition: opacity 0.3s ease; 1067 + } 1068 + 1069 + .drawer-open .mobile-drawer-backdrop { 1070 + opacity: 1; 1071 + pointer-events: auto; 1072 + } 1073 + 1074 + /* Drawer panel */ 1075 + .mobile-drawer { 1076 + display: flex; 1077 + flex-direction: column; 1078 + position: fixed; 1079 + top: 0; 1080 + right: 0; 1081 + bottom: 0; 1082 + width: 280px; 1083 + max-width: 85vw; 1084 + background: var(--card-bg); 1085 + z-index: 2000; 1086 + transform: translateX(100%); 1087 + transition: transform 0.3s ease; 1088 + overflow-y: auto; 1089 + box-shadow: var(--shadow-xl); 1090 + } 1091 + 1092 + .drawer-open .mobile-drawer { 1093 + transform: translateX(0); 1094 + } 1095 + 1096 + .drawer-open body { 1097 + overflow: hidden; 1098 + } 1099 + 1100 + /* Drawer header */ 1101 + .mobile-drawer-header { 1102 + display: flex; 1103 + justify-content: flex-end; 1104 + padding: var(--space-3) var(--space-4); 1105 + border-bottom: 1px solid var(--border-default); 1106 + } 1107 + 1108 + /* Drawer search */ 1109 + .mobile-drawer-search { 1110 + padding: var(--space-3) var(--space-4); 1111 + margin: 0; 1112 + } 1113 + 1114 + .mobile-drawer-search input[type="text"] { 1115 + width: 100%; 1116 + padding: var(--space-3); 1117 + border-radius: var(--radius-md); 1118 + border: 1px solid var(--border-default); 1119 + font-size: var(--font-size-lg); 1120 + background: var(--bg-canvas); 1121 + color: var(--text-primary); 1122 + box-sizing: border-box; 1123 + } 1124 + 1125 + .mobile-drawer-search input[type="text"]:focus { 1126 + outline: none; 1127 + border-color: var(--accent-primary); 1128 + box-shadow: 0 0 0 3px rgba(9, 105, 218, 0.1); 1129 + } 1130 + 1131 + /* Drawer nav links */ 1132 + .mobile-drawer-nav { 1133 + display: flex; 1134 + flex-direction: column; 1135 + border-bottom: 1px solid var(--border-default); 1136 + } 1137 + 1138 + .mobile-drawer-nav a { 1139 + display: flex; 1140 + align-items: center; 1141 + min-height: 48px; 1142 + padding: 0 var(--space-4); 1143 + color: var(--text-primary); 1144 + text-decoration: none; 1145 + font-size: var(--font-size-lg); 1146 + font-weight: var(--font-weight-medium); 1147 + transition: background-color 0.15s ease; 1148 + } 1149 + 1150 + .mobile-drawer-nav a:hover, 1151 + .mobile-drawer-nav a:active { 1152 + background: var(--bg-surface); 1153 + color: var(--accent-primary); 1154 + } 1155 + 1156 + /* Drawer sections */ 1157 + .mobile-drawer-section { 1158 + padding: var(--space-3) var(--space-4); 1159 + border-bottom: 1px solid var(--border-default); 1160 + } 1161 + 1162 + .mobile-drawer-section-title { 1163 + font-size: var(--font-size-sm); 1164 + font-weight: var(--font-weight-bold); 1165 + color: var(--text-secondary); 1166 + text-transform: uppercase; 1167 + letter-spacing: 0.5px; 1168 + margin-bottom: var(--space-2); 1169 + } 1170 + 1171 + .mobile-drawer-hot .sm { 1172 + font-size: var(--font-size-sm); 1173 + text-align: left; 1174 + padding-bottom: var(--space-2); 1175 + } 1176 + 1177 + .mobile-drawer-hot .link a { 1178 + font-size: var(--font-size-sm); 1179 + font-weight: normal; 1180 + color: var(--accent-primary); 1181 + } 1182 + 1183 + /* Drawer toggle buttons */ 1184 + .mobile-drawer-toggles { 1185 + display: flex; 1186 + flex-direction: column; 1187 + padding: var(--space-2) var(--space-4); 1188 + border-bottom: 1px solid var(--border-default); 1189 + } 1190 + 1191 + .mobile-drawer-toggle-btn { 1192 + display: flex; 1193 + align-items: center; 1194 + gap: var(--space-3); 1195 + min-height: 48px; 1196 + padding: 0 var(--space-2); 1197 + background: none; 1198 + border: none; 1199 + color: var(--text-primary); 1200 + font-size: var(--font-size-lg); 1201 + font-family: var(--font-sans); 1202 + cursor: pointer; 1203 + border-radius: var(--radius-md); 1204 + transition: background-color 0.15s ease; 1205 + width: 100%; 1206 + text-align: left; 1207 + } 1208 + 1209 + .mobile-drawer-toggle-btn:hover, 1210 + .mobile-drawer-toggle-btn:active { 1211 + background: var(--bg-surface); 1212 + } 1213 + 1214 + .mobile-drawer-toggle-btn .material-symbols-rounded { 1215 + font-size: 22px; 1216 + } 1217 + 1218 + /* Drawer footer links */ 1219 + .mobile-drawer-footer { 1220 + display: flex; 1221 + gap: var(--space-4); 1222 + padding: var(--space-4); 1223 + margin-top: auto; 1224 + } 1225 + 1226 + .mobile-drawer-footer a { 1227 + color: var(--text-secondary); 1228 + text-decoration: none; 1229 + font-size: var(--font-size-sm); 1230 + } 1231 + 1232 + .mobile-drawer-footer a:hover { 1233 + color: var(--accent-primary); 1234 + } 1235 + 1236 + /* Date Widget - inline banners on mobile */ 1024 1237 .date-widget { 1025 1238 margin-left: 0; 1239 + margin-top: 0; 1026 1240 float: none; 1027 - margin-bottom: var(--space-3); 1028 - width: 60px; 1029 - height: 60px; 1241 + display: flex; 1242 + flex-direction: row; 1243 + align-items: center; 1244 + width: 100%; 1245 + height: auto; 1246 + padding: var(--space-2) 0; 1247 + margin-bottom: 0; 1248 + border: none; 1249 + box-shadow: none; 1250 + background: none; 1251 + border-radius: 0; 1252 + border-bottom: 1px solid var(--border-default); 1253 + gap: var(--space-1); 1254 + } 1255 + 1256 + .date-widget:hover { 1257 + transform: none; 1258 + box-shadow: none; 1030 1259 } 1031 1260 1032 1261 /* Disable sticky behavior on mobile */ ··· 1034 1263 position: relative !important; 1035 1264 top: auto !important; 1036 1265 left: auto !important; 1037 - margin-top: 30px !important; 1266 + margin-top: 0 !important; 1038 1267 } 1039 1268 1040 1269 .date-number { 1041 - font-size: 24px; 1270 + font-size: 13px; 1271 + font-weight: var(--font-weight-semibold); 1272 + color: var(--text-secondary); 1273 + line-height: 1; 1274 + order: 2; 1275 + margin-bottom: 0; 1276 + } 1277 + 1278 + .date-meta { 1279 + display: contents; 1280 + } 1281 + 1282 + .date-day { 1283 + font-size: 13px; 1284 + font-weight: var(--font-weight-semibold); 1285 + text-transform: none; 1286 + color: var(--text-secondary); 1287 + line-height: 1; 1288 + letter-spacing: normal; 1289 + order: 0; 1042 1290 } 1043 1291 1044 - .date-day, 1045 1292 .date-month { 1046 - font-size: 9px; 1293 + font-size: 13px; 1294 + font-weight: var(--font-weight-semibold); 1295 + text-transform: none; 1296 + color: var(--text-secondary); 1297 + line-height: 1; 1298 + letter-spacing: normal; 1299 + order: 1; 1300 + } 1301 + 1302 + .date-year { 1303 + font-size: 13px; 1304 + font-weight: var(--font-weight-semibold); 1305 + color: var(--text-secondary); 1306 + opacity: 0.7; 1307 + order: 3; 1308 + margin-top: 0; 1309 + } 1310 + 1311 + /* Content optimizations */ 1312 + .item img { 1313 + max-width: 100%; 1314 + height: auto; 1315 + } 1316 + 1317 + .link a { 1318 + font-size: 18px; 1319 + } 1320 + 1321 + .broken-link { 1322 + max-width: 200px; 1047 1323 } 1048 1324 1049 1325 .quote-card { ··· 1057 1333 } 1058 1334 1059 1335 .youtube-embed-wrapper { 1336 + max-width: 100%; 1337 + } 1338 + 1339 + .link-card-wrapper { 1060 1340 max-width: 100%; 1061 1341 } 1062 1342 }
+108
internal/templates/views/header.html
··· 228 228 <a href="/api/docs">api docs</a> 229 229 </div> 230 230 231 + <a href="/" class="mobile-site-name" onclick="event.preventDefault(); window.location = window.location.origin + '/';">tumblefish.</a> 232 + 231 233 <div class="nav-right"> 232 234 <!-- Hot Shit Dropdown --> 233 235 <div class="dropdown"> ··· 257 259 <span class="material-symbols-rounded">contrast</span> 258 260 </button> 259 261 </div> 262 + 263 + <button class="mobile-hamburger nav-icon-btn" aria-label="Open menu"> 264 + <span class="material-symbols-rounded">menu</span> 265 + </button> 266 + </div> 267 + 268 + <!-- Mobile Drawer --> 269 + <div class="mobile-drawer-backdrop"></div> 270 + <div class="mobile-drawer"> 271 + <div class="mobile-drawer-header"> 272 + <button class="mobile-drawer-close nav-icon-btn" aria-label="Close menu"> 273 + <span class="material-symbols-rounded">close</span> 274 + </button> 275 + </div> 276 + <form action="/search" method="get" class="mobile-drawer-search"> 277 + <input type="text" name="search" placeholder="search..." /> 278 + </form> 279 + <nav class="mobile-drawer-nav"> 280 + <a href="/" onclick="event.preventDefault(); window.location = window.location.origin + '/';">home</a> 281 + <a href="/buttons/">buttons</a> 282 + <a href="/index.xml">feed</a> 283 + <a href="/stats">stats</a> 284 + <a href="/api/docs">api docs</a> 285 + </nav> 286 + <div class="mobile-drawer-section"> 287 + <div class="mobile-drawer-section-title">Hot Shit</div> 288 + <div class="mobile-drawer-hot"> 289 + {{if .Hot}} 290 + {{.Hot}} 291 + {{else}} 292 + <div class="sm">Nothing hot right now.</div> 293 + {{end}} 294 + </div> 295 + </div> 296 + <div class="mobile-drawer-toggles"> 297 + <button class="mobile-drawer-toggle-btn" id="mobile-theme-toggle"> 298 + <span class="material-symbols-rounded">contrast</span> 299 + Dark Mode 300 + </button> 301 + <button class="mobile-drawer-toggle-btn" id="mobile-compact-toggle"> 302 + <span class="material-symbols-rounded">view_headline</span> 303 + Compact Mode 304 + </button> 305 + <button class="mobile-drawer-toggle-btn" id="mobile-scott-toggle"> 306 + <span class="material-symbols-rounded">history</span> 307 + Scott Mode 308 + </button> 309 + </div> 310 + <div class="mobile-drawer-footer"> 311 + <a href="/stats">Stats</a> 312 + <a href="/api/docs">API Docs</a> 313 + <a href="https://github.com/stahnma/tumble">GitHub</a> 314 + </div> 260 315 </div> 261 316 262 317 <!-- Theme & Compact Toggle Logic (Event Listeners) --> ··· 327 382 html.setAttribute("data-scott-mode", "true"); 328 383 html.removeAttribute("data-theme"); // Force light 329 384 localStorage.setItem("scottMode", "true"); 385 + }); 386 + } 387 + 388 + // Mobile Drawer 389 + var hamburger = document.querySelector(".mobile-hamburger"); 390 + var backdrop = document.querySelector(".mobile-drawer-backdrop"); 391 + var drawerClose = document.querySelector(".mobile-drawer-close"); 392 + var htmlEl = document.documentElement; 393 + 394 + function openDrawer() { 395 + htmlEl.classList.add("drawer-open"); 396 + } 397 + function closeDrawer() { 398 + htmlEl.classList.remove("drawer-open"); 399 + } 400 + 401 + if (hamburger) hamburger.addEventListener("click", openDrawer); 402 + if (backdrop) backdrop.addEventListener("click", closeDrawer); 403 + if (drawerClose) drawerClose.addEventListener("click", closeDrawer); 404 + 405 + // Swipe-right to close drawer 406 + var drawer = document.querySelector(".mobile-drawer"); 407 + if (drawer) { 408 + var touchStartX = 0; 409 + drawer.addEventListener("touchstart", function (e) { 410 + touchStartX = e.touches[0].clientX; 411 + }, { passive: true }); 412 + drawer.addEventListener("touchend", function (e) { 413 + var dx = e.changedTouches[0].clientX - touchStartX; 414 + if (dx > 60) closeDrawer(); 415 + }, { passive: true }); 416 + } 417 + 418 + // Mobile drawer toggle buttons 419 + var mobileTheme = document.getElementById("mobile-theme-toggle"); 420 + if (mobileTheme) { 421 + mobileTheme.addEventListener("click", function () { 422 + if (themeToggle) themeToggle.click(); 423 + }); 424 + } 425 + 426 + var mobileCompact = document.getElementById("mobile-compact-toggle"); 427 + if (mobileCompact) { 428 + mobileCompact.addEventListener("click", function () { 429 + if (compactToggle) compactToggle.click(); 430 + }); 431 + } 432 + 433 + var mobileScott = document.getElementById("mobile-scott-toggle"); 434 + if (mobileScott) { 435 + mobileScott.addEventListener("click", function () { 436 + if (scottToggle) scottToggle.click(); 437 + closeDrawer(); 330 438 }); 331 439 } 332 440 })();
+1
internal/templates/views/index.html
··· 2 2 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en"> 3 3 <head> 4 4 <title>tumblefish.{{.PageTitle}}</title> 5 + <meta name="viewport" content="width=device-width, initial-scale=1" /> 5 6 <link rel="icon" id="favicon" href="/favicon.ico" type="image/x-icon" /> 6 7 <link 7 8 rel="stylesheet"