this repo has no description
1
fork

Configure Feed

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

feat: add sticky date widgets that float while scrolling

- Wrap date sections in containers for scroll tracking
- Add sticky positioning that activates below fixed header (76px)
- Release sticky when next date or pagination approaches
- Disable sticky behavior on mobile for better UX
- Fix Material Icons FOUT by preloading font and hiding text until loaded
- Only activate sticky behavior on pages with scrollable content

+208 -2
+55
internal/assets/css/screen.css
··· 247 247 width: 100%; 248 248 } 249 249 250 + .date-section { 251 + position: relative; 252 + } 253 + 250 254 .date-widget { 251 255 margin-top: 30px; 252 256 margin-left: -130px; ··· 270 274 .date-widget:hover { 271 275 transform: translateY(-2px); 272 276 box-shadow: var(--shadow-xl); 277 + } 278 + 279 + .date-widget.sticky { 280 + position: fixed; 281 + top: 76px; 282 + margin-top: 0; 283 + margin-left: 0; 284 + float: none; 285 + z-index: 150; 286 + box-shadow: var(--shadow-xl); 287 + } 288 + 289 + .date-widget.sticky.at-bottom { 290 + position: absolute; 291 + top: auto; 292 + bottom: 0; 273 293 } 274 294 275 295 .date-number { ··· 938 958 height: 60px; 939 959 } 940 960 961 + /* Disable sticky behavior on mobile */ 962 + .date-widget.sticky { 963 + position: relative !important; 964 + top: auto !important; 965 + left: auto !important; 966 + margin-top: 30px !important; 967 + } 968 + 941 969 .date-number { 942 970 font-size: 24px; 943 971 } ··· 1038 1066 iframe[src*="instagram.com"] { 1039 1067 max-width: 540px !important; 1040 1068 margin: var(--space-2) auto !important; 1069 + } 1070 + 1071 + /* Material Symbols - Hide text until font loads to prevent FOUT */ 1072 + .material-symbols-rounded { 1073 + font-family: 'Material Symbols Rounded'; 1074 + font-weight: normal; 1075 + font-style: normal; 1076 + display: inline-block; 1077 + line-height: 1; 1078 + text-transform: none; 1079 + letter-spacing: normal; 1080 + word-wrap: normal; 1081 + white-space: nowrap; 1082 + direction: ltr; 1083 + -webkit-font-smoothing: antialiased; 1084 + text-rendering: optimizeLegibility; 1085 + -moz-osx-font-smoothing: grayscale; 1086 + font-feature-settings: 'liga'; 1087 + font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24; 1088 + /* Hide text until font loads */ 1089 + color: transparent; 1090 + transition: color 0.1s; 1091 + } 1092 + 1093 + /* Show icons when fonts are loaded (set by JS) */ 1094 + html.fonts-loaded .material-symbols-rounded { 1095 + color: inherit; 1041 1096 } 1042 1097 1043 1098 /* Compact Mode Styles */
+14
internal/handler/handlers.go
··· 313 313 // Generate Container HTML 314 314 containerHTML := "" 315 315 lastDate := "" 316 + sectionOpen := false 316 317 317 318 for _, p := range processedItems { 318 319 if dtype != "rss" && dtype != "xml" { 319 320 if p.FullDate != lastDate { 321 + // Close previous section if open 322 + if sectionOpen { 323 + containerHTML += "</div>" 324 + } 325 + // Open new date section 326 + containerHTML += fmt.Sprintf(`<div class="date-section" data-date="%s">`, p.FullDate) 327 + sectionOpen = true 328 + 320 329 // Date Changed, Render Date Template 321 330 dateData := map[string]string{ 322 331 "Date": p.DateRawDay, ··· 332 341 } 333 342 } 334 343 containerHTML += p.HTML 344 + } 345 + 346 + // Close final section if open 347 + if sectionOpen { 348 + containerHTML += "</div>" 335 349 } 336 350 337 351 // Hot Links (Side bar) - Only for HTML
+139 -2
internal/templates/views/index.html
··· 7 7 href="/css/screen.css" 8 8 media="screen" 9 9 /> 10 - <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" /> 10 + <link rel="preload" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=block" as="style" /> 11 + <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Rounded:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200&display=block" /> 11 12 12 13 <!-- Theme Init --> 13 14 <script> ··· 20 21 document.documentElement.setAttribute("data-theme", "dark"); 21 22 } 22 23 23 - // Compact Mode Ini 24 + // Compact Mode Init 24 25 var isCompact = localStorage.getItem("compact") === "true"; 25 26 if (isCompact) { 26 27 document.documentElement.classList.add("compact-mode"); 28 + } 29 + 30 + // Font loading detection 31 + if (document.fonts && document.fonts.ready) { 32 + document.fonts.ready.then(function() { 33 + document.documentElement.classList.add("fonts-loaded"); 34 + }); 35 + } else { 36 + // Fallback: show icons after short delay 37 + setTimeout(function() { 38 + document.documentElement.classList.add("fonts-loaded"); 39 + }, 100); 27 40 } 28 41 })(); 29 42 </script> ··· 398 411 399 412 {{template "footer" .}} 400 413 414 + <!-- Sticky Date Widget Script --> 415 + <script> 416 + (function() { 417 + // Check if page has enough content to scroll 418 + function hasScrollableContent() { 419 + return document.documentElement.scrollHeight > window.innerHeight; 420 + } 421 + 422 + // Only initialize if there's scrollable content 423 + if (!hasScrollableContent()) { 424 + return; 425 + } 426 + 427 + var sections = document.querySelectorAll('.date-section'); 428 + var navigationBottom = document.querySelector('#navigation'); 429 + 430 + if (sections.length === 0) { 431 + return; 432 + } 433 + 434 + // Store original positions of all date widgets once at the start 435 + sections.forEach(function(section, index) { 436 + var dateWidget = section.querySelector('.date-widget'); 437 + if (dateWidget) { 438 + var rect = dateWidget.getBoundingClientRect(); 439 + dateWidget.setAttribute('data-original-top', rect.top + window.pageYOffset); 440 + dateWidget.setAttribute('data-original-left', rect.left); 441 + } 442 + }); 443 + 444 + function updateStickyDates() { 445 + var scrollTop = window.pageYOffset || document.documentElement.scrollTop; 446 + var viewportHeight = window.innerHeight; 447 + 448 + // Get navigation bottom position 449 + var navBottomTop = navigationBottom ? navigationBottom.getBoundingClientRect().top + scrollTop : Infinity; 450 + 451 + sections.forEach(function(section, index) { 452 + var dateWidget = section.querySelector('.date-widget'); 453 + if (!dateWidget) return; 454 + 455 + var sectionRect = section.getBoundingClientRect(); 456 + var sectionTop = sectionRect.top + scrollTop; 457 + var sectionBottom = sectionRect.bottom + scrollTop; 458 + 459 + // Get stored original positions 460 + var originalDateTop = parseInt(dateWidget.getAttribute('data-original-top')); 461 + var originalDateLeft = parseFloat(dateWidget.getAttribute('data-original-left')); 462 + 463 + // Get next section if exists 464 + var nextSection = sections[index + 1]; 465 + var nextSectionTop = nextSection ? 466 + nextSection.getBoundingClientRect().top + scrollTop : Infinity; 467 + 468 + // Calculate date widget height 469 + var dateHeight = dateWidget.offsetHeight; 470 + 471 + // Sticky top position (below the 56px fixed header + 20px spacing) 472 + var stickyTop = 76; 473 + 474 + // Determine if date should be sticky based on the date widget's original position 475 + var shouldBeSticky = scrollTop + stickyTop >= originalDateTop && 476 + scrollTop < sectionBottom - dateHeight; 477 + 478 + // Check if we should stop for next date (next date approaching) 479 + var nextDateApproaching = nextSection && 480 + (scrollTop + stickyTop + dateHeight + 30) >= nextSectionTop; 481 + 482 + // Check if we should stop for navigation (bottom pagination) 483 + var navigationApproaching = navBottomTop !== Infinity && 484 + (scrollTop + stickyTop + dateHeight + 30) >= navBottomTop; 485 + 486 + if (shouldBeSticky && !nextDateApproaching && !navigationApproaching) { 487 + // Make sticky 488 + dateWidget.classList.add('sticky'); 489 + dateWidget.classList.remove('at-bottom'); 490 + dateWidget.style.left = originalDateLeft + 'px'; 491 + } else if (shouldBeSticky && (nextDateApproaching || navigationApproaching)) { 492 + // Pin at bottom of section 493 + dateWidget.classList.remove('sticky'); 494 + dateWidget.classList.add('at-bottom'); 495 + dateWidget.style.left = ''; 496 + } else { 497 + // Static position 498 + dateWidget.classList.remove('sticky', 'at-bottom'); 499 + dateWidget.style.left = ''; 500 + } 501 + }); 502 + } 503 + 504 + // Throttle scroll events for performance 505 + var scrollTimeout; 506 + function throttledUpdate() { 507 + if (scrollTimeout) { 508 + return; 509 + } 510 + scrollTimeout = setTimeout(function() { 511 + updateStickyDates(); 512 + scrollTimeout = null; 513 + }, 10); 514 + } 515 + 516 + // Listen for scroll 517 + window.addEventListener('scroll', throttledUpdate); 518 + window.addEventListener('resize', function() { 519 + // Check if content is still scrollable after resize 520 + if (!hasScrollableContent()) { 521 + // Remove all sticky classes if no longer scrollable 522 + sections.forEach(function(section) { 523 + var dateWidget = section.querySelector('.date-widget'); 524 + if (dateWidget) { 525 + dateWidget.classList.remove('sticky', 'at-bottom'); 526 + dateWidget.style.left = ''; 527 + } 528 + }); 529 + } else { 530 + updateStickyDates(); 531 + } 532 + }); 533 + 534 + // Initial check 535 + updateStickyDates(); 536 + })(); 537 + </script> 401 538 402 539 </body> 403 540 </html>