From b43143e3ce369ed2ffb2e517eb6695153a61c866 Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:32:28 -0700 Subject: [PATCH 01/28] ci: rerun deploy after workflow fix From 936880f0d7ae69e96d3cd5d5726cf084d602b151 Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:32:39 -0700 Subject: [PATCH 02/28] ci: rerun deploy after workflow fix From 2507e930157b1a7a89726e0d13b502c7665deedb Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:32:56 -0700 Subject: [PATCH 03/28] ci: keep updated deploy workflow --- .github/workflows/deploy-dev-to-beta.yml | 62 ++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .github/workflows/deploy-dev-to-beta.yml diff --git a/.github/workflows/deploy-dev-to-beta.yml b/.github/workflows/deploy-dev-to-beta.yml new file mode 100644 index 0000000..512bf3f --- /dev/null +++ b/.github/workflows/deploy-dev-to-beta.yml @@ -0,0 +1,62 @@ +name: Deploy dev -> main:/beta + +on: + push: + branches: [ dev ] + workflow_dispatch: {} + +jobs: + build-and-publish: + runs-on: ubuntu-latest + permissions: + contents: write + + steps: + - name: Checkout dev + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: Set up Ruby + uses: ruby/setup-ruby@v1 + with: + ruby-version: '3.1' + + - name: Install bundler & dependencies + run: | + gem install bundler + bundle config set --local deployment 'true' + bundle install --jobs 4 --retry 3 + + - name: Build site (Jekyll) + run: bundle exec jekyll build --destination _site + + - name: Remove CNAME from build (prevent overriding custom domain) + run: rm -f _site/CNAME || true + + - name: Checkout main into separate folder + uses: actions/checkout@v4 + with: + repository: ${{ github.repository }} + ref: main + token: ${{ secrets.GITHUB_TOKEN }} + path: main-worktree + + - name: Copy built site into main-worktree/beta + run: | + rm -rf main-worktree/beta || true + mkdir -p main-worktree/beta + cp -a _site/. main-worktree/beta/ + + - name: Commit & push beta content to main + run: | + cd main-worktree + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add -A beta + git commit -m "Deploy dev -> beta: $GITHUB_SHA" || echo "No changes to commit" + # ensure push uses token auth (works around some permission/remote issues) + git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}.git + git push origin main + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From b79a9dac5580cbd42e313384a15239a6bfa36031 Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:49:44 -0700 Subject: [PATCH 04/28] fix(ci): use Jekyll relative_url for assets, manifest, and service-worker --- _layouts/default.html | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 35c43b9..bc926f0 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -7,24 +7,25 @@ - + - - + + - - - + + + - + - - + + + @@ -34,7 +35,7 @@ {% include links.html %} {% include footer.html %} - + From ca0d2792d81ba61de769a7678b01ef9185fc8892 Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:56:20 -0700 Subject: [PATCH 05/28] fix(ci): use relative (no-leading-slash) asset paths so beta copies work under /beta/ --- _layouts/default.html | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index bc926f0..0ad4d41 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -7,14 +7,14 @@ - + - - + + - - - + + + - - + + @@ -35,7 +35,7 @@ {% include links.html %} {% include footer.html %} - + From c46cf133be12e9ef76026ef8fb99b265687a280a Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:57:35 -0700 Subject: [PATCH 06/28] ci: trigger deploy (ensure latest templates used) From 947163011e54133963c0f1432960ea5c8fd0ca84 Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:58:25 -0700 Subject: [PATCH 07/28] fix(ci): use relative path for bootstrap-icons.svg in links include --- _includes/links.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/links.html b/_includes/links.html index 5140380..fe6569c 100644 --- a/_includes/links.html +++ b/_includes/links.html @@ -3,7 +3,7 @@
  • - +
    {{ item.text }}
    From a25f34bc7341e87feb413353ee612acf27d50098 Mon Sep 17 00:00:00 2001 From: Iautner Date: Thu, 18 Sep 2025 17:59:45 -0700 Subject: [PATCH 08/28] fix(ci): make bootstrap-icons.svg href relative (no-leading-slash) --- _includes/links.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/_includes/links.html b/_includes/links.html index fe6569c..65243c9 100644 --- a/_includes/links.html +++ b/_includes/links.html @@ -3,7 +3,7 @@
  • - +
    {{ item.text }}
    From 623ec07a9c6ac53b3630a8f998ca814eb5986e0b Mon Sep 17 00:00:00 2001 From: Lautner <30170747+Iautner@users.noreply.github.com> Date: Wed, 1 Oct 2025 00:47:46 +1000 Subject: [PATCH 09/28] added .history and /node_modules to .gitignore --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 3afc81b..aa4771a 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ _site/ .DS_Store Dockerfile /.vscode +/.history +/node_modules From 0be189b4e1819f938f8d9240748d6c56975fe974 Mon Sep 17 00:00:00 2001 From: Lautner <30170747+Iautner@users.noreply.github.com> Date: Wed, 1 Oct 2025 01:25:49 +1000 Subject: [PATCH 10/28] Modernize PWA with enhanced UI, manifest, and service worker Revamps the default layout for a more app-like experience, including improved meta tags, loading indicators, and accessibility. Updates custom CSS for modern theming, glass effects, and responsive design. Replaces lazy-load.js with a feature-rich PWA/app shell script supporting touch feedback, offline notifications, install banners, and accessibility improvements. Expands manifest.json with new fields, categories, and shortcuts for better PWA support. Upgrades the service worker for robust caching, cache busting, and resource management. --- _layouts/default.html | 115 +++++++++-- assets/css/custom.css | 326 ++++++++++++++++++++++++++++++- assets/js/lazy-load.js | 421 +++++++++++++++++++++++++++++++++++++++-- manifest.json | 90 +++++---- service-worker.js | 104 ++++++---- 5 files changed, 947 insertions(+), 109 deletions(-) diff --git a/_layouts/default.html b/_layouts/default.html index 0ad4d41..60049e8 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -5,37 +5,120 @@ {{ site.data.metadata.title }} - - + + + + + + + + + + + + + + + + + + + + - + + + + - + + + - + - + + + + - - + + + + + + + + + + + - -
    - {% include profile.html %} - {% include interests.html %} - {% include links.html %} - {% include footer.html %} -
    - + + +
    +
    +
    + + +
    +
    +
    + {% include profile.html %} + {% include interests.html %} + {% include links.html %} + {% include footer.html %} +
    +
    +
    + + + + + + diff --git a/assets/css/custom.css b/assets/css/custom.css index 6e3257c..7f449a9 100644 --- a/assets/css/custom.css +++ b/assets/css/custom.css @@ -1,6 +1,330 @@ -/* Add CSS rules to override Primer */ +/* Modern App-like CSS with Native Animations and Glass Effects */ +/* CSS Custom Properties for theming */ +:root { + --app-primary: #0366d6; + --app-secondary: #586069; + --app-bg-primary: #ffffff; + --app-bg-secondary: #f6f8fa; + --app-border: rgba(27, 31, 35, 0.15); + --app-shadow: rgba(27, 31, 35, 0.04); + --app-glass-bg: rgba(255, 255, 255, 0.1); + --app-glass-border: rgba(255, 255, 255, 0.2); + --app-text-primary: #24292e; + --app-text-secondary: #586069; + --app-radius: 12px; + --app-transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + --app-bounce: cubic-bezier(0.68, -0.55, 0.265, 1.55); +} + +/* Dark mode variables */ +[data-color-mode="dark"], [data-color-mode="auto"][data-light-theme="dark"] { + --app-bg-primary: #0d1117; + --app-bg-secondary: #161b22; + --app-border: rgba(240, 246, 252, 0.1); + --app-shadow: rgba(0, 0, 0, 0.3); + --app-glass-bg: rgba(255, 255, 255, 0.05); + --app-glass-border: rgba(255, 255, 255, 0.1); + --app-text-primary: #f0f6fc; + --app-text-secondary: #8b949e; +} + +/* Base styles with app-like feel */ +* { + box-sizing: border-box; +} + +html { + scroll-behavior: smooth; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + text-rendering: optimizeLegibility; +} + +body { + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Noto Sans', Helvetica, Arial, sans-serif; + background: var(--app-bg-primary); + color: var(--app-text-primary); + margin: 0; + padding: 0; + min-height: 100vh; + overflow-x: hidden; + position: relative; +} + +/* App loader */ +.app-loader { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: var(--app-bg-primary); + display: flex; + align-items: center; + justify-content: center; + z-index: 9999; + transition: opacity 0.3s ease; +} + +.loader-spinner { + width: 40px; + height: 40px; + border: 3px solid var(--app-border); + border-top: 3px solid var(--app-primary); + border-radius: 50%; + animation: spin 1s linear infinite; +} + +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} + +/* App container */ +.app-container { + opacity: 0; + transition: opacity 0.3s ease; + min-height: 100vh; +} + +.app-body { + padding: env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left); + background: linear-gradient(135deg, var(--app-bg-primary) 0%, var(--app-bg-secondary) 100%); +} + +/* Main content with glass effect */ +.main-content { + padding: 1rem; + max-width: 768px; + margin: 0 auto; +} + +.content-wrapper { + background: var(--app-glass-bg); + backdrop-filter: blur(20px); + -webkit-backdrop-filter: blur(20px); + border: 1px solid var(--app-glass-border); + border-radius: var(--app-radius); + padding: 2rem; + box-shadow: 0 8px 32px var(--app-shadow); + position: relative; + overflow: hidden; + animation: slideUp 0.6s var(--app-bounce); +} + +/* Fallback for browsers without backdrop-filter */ +@supports not (backdrop-filter: blur(20px)) { + .content-wrapper { + background: var(--app-bg-secondary); + border: 1px solid var(--app-border); + } +} + +@keyframes slideUp { + from { + opacity: 0; + transform: translateY(30px); + } + to { + opacity: 1; + transform: translateY(0); + } +} + +/* Enhanced SVG icons */ svg { width: 1.25em; height: 1.25em; + transition: var(--app-transition); + filter: drop-shadow(0 1px 2px var(--app-shadow)); +} + +/* Button and link enhancements */ +a, button { + transition: var(--app-transition); + position: relative; + overflow: hidden; +} + +a:hover, button:hover { + transform: translateY(-1px); + box-shadow: 0 4px 12px var(--app-shadow); +} + +a:active, button:active { + transform: translateY(0); + transition: all 0.1s ease; +} + +/* Touch feedback for mobile */ +.touch-feedback { + position: relative; + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.touch-feedback::before { + content: ''; + position: absolute; + top: 50%; + left: 50%; + width: 0; + height: 0; + border-radius: 50%; + background: rgba(255, 255, 255, 0.3); + transform: translate(-50%, -50%); + transition: width 0.3s, height 0.3s; + pointer-events: none; + z-index: 1; +} + +.touch-feedback:active::before { + width: 200px; + height: 200px; +} + +/* Card-like elements */ +.card { + background: var(--app-glass-bg); + backdrop-filter: blur(10px); + -webkit-backdrop-filter: blur(10px); + border: 1px solid var(--app-glass-border); + border-radius: var(--app-radius); + padding: 1.5rem; + margin: 1rem 0; + box-shadow: 0 4px 16px var(--app-shadow); + transition: var(--app-transition); + animation: fadeIn 0.5s ease; +} + +.card:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px var(--app-shadow); +} + +@keyframes fadeIn { + from { opacity: 0; transform: scale(0.95); } + to { opacity: 1; transform: scale(1); } +} + +/* Responsive design for all screen sizes */ +@media (max-width: 768px) { + .content-wrapper { + padding: 1.5rem; + margin: 0.5rem; + border-radius: 16px; + } + + .main-content { + padding: 0.5rem; + } + + /* Enhanced touch targets for mobile */ + a, button { + min-height: 44px; + min-width: 44px; + display: inline-flex; + align-items: center; + justify-content: center; + } +} + +@media (max-width: 480px) { + .content-wrapper { + padding: 1rem; + margin: 0.25rem; + border-radius: 12px; + } + + .app-body { + padding: 0; + } +} + +/* High-resolution displays */ +@media (-webkit-min-device-pixel-ratio: 2), (min-resolution: 192dpi) { + svg { + filter: drop-shadow(0 0.5px 1px var(--app-shadow)); + } +} + +/* Landscape orientation adjustments */ +@media (orientation: landscape) and (max-height: 600px) { + .content-wrapper { + padding: 1rem; + } +} + +/* Dark mode specific enhancements */ +@media (prefers-color-scheme: dark) { + .loader-spinner { + filter: drop-shadow(0 0 4px var(--app-primary)); + } + + .content-wrapper { + box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5); + } +} + +/* Reduced motion for accessibility */ +@media (prefers-reduced-motion: reduce) { + * { + animation-duration: 0.01ms !important; + animation-iteration-count: 1 !important; + transition-duration: 0.01ms !important; + scroll-behavior: auto !important; + } + + .content-wrapper { + animation: none; + } +} + +/* Focus styles for accessibility */ +a:focus, button:focus { + outline: 2px solid var(--app-primary); + outline-offset: 2px; + border-radius: 4px; +} + +/* Print styles */ +@media print { + .app-loader, .touch-feedback::before { + display: none !important; + } + + .content-wrapper { + background: white !important; + box-shadow: none !important; + border: 1px solid #ccc !important; + } +} + +/* Performance optimizations */ +.content-wrapper { + will-change: transform; + contain: layout style paint; +} + +/* iOS specific fixes */ +@supports (-webkit-touch-callout: none) { + .app-body { + padding-top: max(env(safe-area-inset-top), 20px); + padding-bottom: max(env(safe-area-inset-bottom), 20px); + } + + /* Fix for iOS bounce scrolling */ + body { + position: fixed; + width: 100%; + height: 100%; + overflow: hidden; + } + + .app-container { + overflow-y: auto; + -webkit-overflow-scrolling: touch; + } } \ No newline at end of file diff --git a/assets/js/lazy-load.js b/assets/js/lazy-load.js index c7293ed..730ef47 100644 --- a/assets/js/lazy-load.js +++ b/assets/js/lazy-load.js @@ -1,25 +1,408 @@ -document.addEventListener("DOMContentLoaded", function() { - var lazyImages = [].slice.call(document.querySelectorAll("img.lazy")); - - if ("IntersectionObserver" in window) { - let lazyImageObserver = new IntersectionObserver(function(entries, observer) { - entries.forEach(function(entry) { - if (entry.isIntersecting) { - let lazyImage = entry.target; - lazyImage.src = lazyImage.dataset.src; - lazyImage.classList.remove("lazy"); - lazyImageObserver.unobserve(lazyImage); +// Enhanced PWA JavaScript with modern features and optimizations + +class LautnerApp { + constructor() { + this.isOnline = navigator.onLine; + this.isTouch = 'ontouchstart' in window; + this.isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent); + this.isStandalone = window.matchMedia('(display-mode: standalone)').matches; + + this.init(); + } + + init() { + this.setupLazyLoading(); + this.setupTouchFeedback(); + this.setupNetworkStatus(); + this.setupPerformanceOptimizations(); + this.setupPWAFeatures(); + this.setupAccessibility(); + + console.log('🚀 Lautner App initialized with modern PWA features'); + } + + // Enhanced lazy loading with better performance + setupLazyLoading() { + const lazyImages = document.querySelectorAll('img.lazy, img[data-src]'); + + if ('IntersectionObserver' in window) { + const imageObserver = new IntersectionObserver((entries, observer) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + const img = entry.target; + this.loadImage(img); + observer.unobserve(img); + } + }); + }, { + rootMargin: '50px 0px', + threshold: 0.01 + }); + + lazyImages.forEach(img => imageObserver.observe(img)); + } else { + // Fallback for older browsers + lazyImages.forEach(img => this.loadImage(img)); + } + } + + loadImage(img) { + const src = img.dataset.src || img.src; + if (src) { + img.src = src; + img.classList.remove('lazy'); + img.classList.add('loaded'); + + // Add loading animation + img.style.opacity = '0'; + img.onload = () => { + img.style.transition = 'opacity 0.3s ease'; + img.style.opacity = '1'; + }; + } + } + + // Touch feedback for native-like interactions + setupTouchFeedback() { + if (!this.isTouch) return; + + // Add touch feedback to interactive elements + const interactiveElements = document.querySelectorAll('a, button, .card, [role="button"]'); + + interactiveElements.forEach(element => { + element.classList.add('touch-feedback'); + + element.addEventListener('touchstart', this.handleTouchStart.bind(this), { passive: true }); + element.addEventListener('touchend', this.handleTouchEnd.bind(this), { passive: true }); + element.addEventListener('touchcancel', this.handleTouchEnd.bind(this), { passive: true }); + }); + } + + handleTouchStart(event) { + const element = event.currentTarget; + element.style.transform = 'scale(0.98)'; + element.style.transition = 'transform 0.1s ease'; + + // Haptic feedback for supported devices + if ('vibrate' in navigator) { + navigator.vibrate(1); + } + } + + handleTouchEnd(event) { + const element = event.currentTarget; + setTimeout(() => { + element.style.transform = 'scale(1)'; + element.style.transition = 'transform 0.2s cubic-bezier(0.4, 0, 0.2, 1)'; + }, 50); + } + + // Network status monitoring + setupNetworkStatus() { + const updateNetworkStatus = () => { + this.isOnline = navigator.onLine; + document.body.classList.toggle('offline', !this.isOnline); + + if (!this.isOnline) { + this.showOfflineNotification(); + } + }; + + window.addEventListener('online', updateNetworkStatus); + window.addEventListener('offline', updateNetworkStatus); + updateNetworkStatus(); + } + + showOfflineNotification() { + const notification = document.createElement('div'); + notification.className = 'offline-notification'; + notification.innerHTML = ` +
    + 📱 You're offline - some features may be limited +
    + `; + + document.body.appendChild(notification); + + setTimeout(() => { + notification.style.opacity = '0'; + setTimeout(() => notification.remove(), 300); + }, 3000); + } + + // Performance optimizations + setupPerformanceOptimizations() { + // Preload critical resources on interaction + let hasInteracted = false; + const preloadOnInteraction = () => { + if (hasInteracted) return; + hasInteracted = true; + + // Preload fonts + const fontLink = document.createElement('link'); + fontLink.rel = 'preload'; + fontLink.as = 'font'; + fontLink.type = 'font/woff2'; + fontLink.crossOrigin = 'anonymous'; + document.head.appendChild(fontLink); + }; + + ['mousedown', 'touchstart', 'keydown'].forEach(event => { + document.addEventListener(event, preloadOnInteraction, { once: true, passive: true }); + }); + + // Intersection Observer for animations + if ('IntersectionObserver' in window) { + const animationObserver = new IntersectionObserver((entries) => { + entries.forEach(entry => { + if (entry.isIntersecting) { + entry.target.classList.add('animate-in'); + animationObserver.unobserve(entry.target); + } + }); + }, { threshold: 0.1 }); + + document.querySelectorAll('.card, .content-wrapper > *').forEach(el => { + animationObserver.observe(el); + }); + } + } + + // PWA-specific features + setupPWAFeatures() { + // Install prompt + let deferredPrompt; + + window.addEventListener('beforeinstallprompt', (e) => { + e.preventDefault(); + deferredPrompt = e; + this.showInstallPromotion(); + }); + + // Track installation + window.addEventListener('appinstalled', () => { + console.log('🎉 PWA was installed successfully!'); + deferredPrompt = null; + }); + + // Share API + if ('share' in navigator) { + this.enableNativeSharing(); + } + + // Update available notification + if ('serviceWorker' in navigator) { + navigator.serviceWorker.addEventListener('controllerchange', () => { + window.location.reload(); + }); + } + } + + showInstallPromotion() { + // Only show on mobile devices + if (!this.isTouch || this.isStandalone) return; + + const installBanner = document.createElement('div'); + installBanner.className = 'install-banner'; + installBanner.innerHTML = ` +
    + 📱 Install Lautner app for the best experience! + + +
    + `; + + document.body.appendChild(installBanner); + + installBanner.querySelector('.install-btn').addEventListener('click', async () => { + if (deferredPrompt) { + deferredPrompt.prompt(); + const { outcome } = await deferredPrompt.userChoice; + console.log(`User response to install prompt: ${outcome}`); + deferredPrompt = null; + } + installBanner.remove(); + }); + + installBanner.querySelector('.dismiss-btn').addEventListener('click', () => { + installBanner.remove(); + }); + } + + enableNativeSharing() { + const shareButtons = document.querySelectorAll('[data-share]'); + shareButtons.forEach(button => { + button.addEventListener('click', async () => { + try { + await navigator.share({ + title: document.title, + text: document.querySelector('meta[name="description"]')?.content || '', + url: window.location.href + }); + } catch (err) { + console.log('Sharing failed:', err); } }); }); - - lazyImages.forEach(function(lazyImage) { - lazyImageObserver.observe(lazyImage); + } + + // Accessibility enhancements + setupAccessibility() { + // Focus management + document.addEventListener('keydown', (e) => { + if (e.key === 'Tab') { + document.body.classList.add('keyboard-navigation'); + } }); - } else { - lazyImages.forEach(function(lazyImage) { - lazyImage.src = lazyImage.dataset.src; - lazyImage.classList.remove("lazy"); + + document.addEventListener('mousedown', () => { + document.body.classList.remove('keyboard-navigation'); }); + + // Announce dynamic content changes to screen readers + this.createAriaLiveRegion(); + } + + createAriaLiveRegion() { + const liveRegion = document.createElement('div'); + liveRegion.setAttribute('aria-live', 'polite'); + liveRegion.setAttribute('aria-atomic', 'true'); + liveRegion.className = 'sr-only'; + liveRegion.id = 'aria-live-region'; + document.body.appendChild(liveRegion); } -}); \ No newline at end of file + + announceToScreenReader(message) { + const liveRegion = document.getElementById('aria-live-region'); + if (liveRegion) { + liveRegion.textContent = message; + setTimeout(() => { + liveRegion.textContent = ''; + }, 1000); + } + } +} + +// Additional CSS for new features +const additionalStyles = ` + +`; + +// Initialize the app when DOM is ready +if (document.readyState === 'loading') { + document.addEventListener('DOMContentLoaded', () => { + new LautnerApp(); + document.head.insertAdjacentHTML('beforeend', additionalStyles); + }); +} else { + new LautnerApp(); + document.head.insertAdjacentHTML('beforeend', additionalStyles); +} + +// Export for global access +window.LautnerApp = LautnerApp; \ No newline at end of file diff --git a/manifest.json b/manifest.json index c8fdfb1..c66bd26 100644 --- a/manifest.json +++ b/manifest.json @@ -1,43 +1,67 @@ { -"protocol_handlers": [{"protocol":"","url":"/"}], -"name": "Lautner", -"short_name": "Lautner", -"start_url": "/", -"display": "standalone", -"description": "Lautner's links and socials, Fun Minecraft content, modded Minecraft, let's play, and challenge videos!", -"lang": " The default language of your application", -"dir": "auto", -"theme_color": "#000000", -"background_color": "#000000", -"orientation": "any", -"icons": [ + "name": "Lautner", + "short_name": "Lautner", + "description": "Lautner's links and socials, Fun Minecraft content, modded Minecraft, let's play, and challenge videos!", + "start_url": "/", + "scope": "/", + "display": "standalone", + "display_override": ["window-controls-overlay", "standalone", "minimal-ui"], + "orientation": "any", + "lang": "en", + "dir": "auto", + "theme_color": "#000000", + "background_color": "#000000", + "edge_side_panel": { + "preferred_width": 400 + }, + "icons": [ { - "src": "/manifest-icon-512.maskable.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "maskable" + "src": "/manifest-icon-512.maskable.png", + "sizes": "512x512", + "type": "image/png", + "purpose": "maskable" }, { - "src": "/manifest-icon-192.maskable.png", - "sizes": "192x192", - "type": "image/png", - "purpose": "any" + "src": "/manifest-icon-192.maskable.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "any" + }, + { + "src": "/manifest-icon-192.maskable.png", + "sizes": "192x192", + "type": "image/png", + "purpose": "monochrome" } -], -"screenshots": [ + ], + "screenshots": [ { - "src": "/Screenshot.png", - "sizes": "2880x1800", - "type": "image/png", - "description": "A screenshot of the home page" + "src": "/Screenshot.png", + "sizes": "2880x1800", + "type": "image/png", + "description": "A screenshot of the home page", + "form_factor": "wide" } -], -"prefer_related_applications": false, -"shortcuts": [ + ], + "categories": ["entertainment", "social", "games"], + "shortcuts": [ { - "name":"Shortcut", - "url":"/shortcut", - "description":"shortcut" + "name": "Quick Links", + "short_name": "Links", + "description": "Access social links directly", + "url": "/#links", + "icons": [ + { + "src": "/manifest-icon-192.maskable.png", + "sizes": "192x192" + } + ] } -] + ], + "prefer_related_applications": false, + "protocol_handlers": [], + "handle_links": "preferred", + "launch_handler": { + "client_mode": ["navigate-existing", "auto"] + } } \ No newline at end of file diff --git a/service-worker.js b/service-worker.js index a57f769..d6b0f76 100644 --- a/service-worker.js +++ b/service-worker.js @@ -1,60 +1,84 @@ - // Based off of https://github.com/pwa-builder/PWABuilder/blob/main/docs/sw.js - - /* - Welcome to our basic Service Worker! This Service Worker offers a basic offline experience - while also being easily customizeable. You can add in your own code to implement the capabilities - listed below, or change anything else you would like. - - - Need an introduction to Service Workers? Check our docs here: https://docs.pwabuilder.com/#/home/sw-intro - Want to learn more about how our Service Worker generation works? Check our docs here: https://docs.pwabuilder.com/#/studio/existing-app?id=add-a-service-worker - - Did you know that Service Workers offer many more capabilities than just offline? - - Background Sync: https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/advanced-capabilities/06 - - Periodic Background Sync: https://web.dev/periodic-background-sync/ - - Push Notifications: https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/advanced-capabilities/07?id=push-notifications-on-the-web - - Badges: https://microsoft.github.io/win-student-devs/#/30DaysOfPWA/advanced-capabilities/07?id=application-badges - */ + // Enhanced Service Worker with modern PWA capabilities + // Optimized for app-like experience across all devices + const CACHE_NAME = 'lautner-app-v2.0'; + const RUNTIME_CACHE = 'lautner-runtime-v2.0'; + const OFFLINE_CACHE = 'lautner-offline-v2.0'; + const HOSTNAME_WHITELIST = [ self.location.hostname, 'fonts.gstatic.com', 'fonts.googleapis.com', - 'cdn.jsdelivr.net' - ] + 'cdn.jsdelivr.net', + 'cdnjs.cloudflare.com', + 'unpkg.com' + ]; + + // Critical resources to cache immediately + const CRITICAL_RESOURCES = [ + '/', + '/index.html', + '/assets/css/primer.css', + '/assets/css/custom.css', + '/assets/css/bootstrap-icons.css', + '/assets/js/lazy-load.js', + '/manifest.json' + ]; + + // Install event - cache critical resources + self.addEventListener('install', event => { + event.waitUntil( + caches.open(CACHE_NAME) + .then(cache => { + console.log('Caching critical resources'); + return cache.addAll(CRITICAL_RESOURCES); + }) + .then(() => self.skipWaiting()) + ); + }); - // The Util Function to hack URLs of intercepted requests + // Enhanced URL utility function for cache busting and protocol fixes const getFixedUrl = (req) => { - var now = Date.now() - var url = new URL(req.url) + const now = Date.now(); + const url = new URL(req.url); - // 1. fixed http URL - // Just keep syncing with location.protocol - // fetch(httpURL) belongs to active mixed content. - // And fetch(httpRequest) is not supported yet. - url.protocol = self.location.protocol + // 1. Fixed http URL - sync with location.protocol + url.protocol = self.location.protocol; - // 2. add query for caching-busting. - // Github Pages served with Cache-Control: max-age=600 - // max-age on mutable content is error-prone, with SW life of bugs can even extend. - // Until cache mode of Fetch API landed, we have to workaround cache-busting with query string. - // Cache-Control-Bug: https://bugs.chromium.org/p/chromium/issues/detail?id=453190 + // 2. Add query for cache-busting (GitHub Pages specific) if (url.hostname === self.location.hostname) { - url.search += (url.search ? '&' : '?') + 'cache-bust=' + now + url.search += (url.search ? '&' : '?') + 'cache-bust=' + now; } - return url.href - } + + return url.href; + }; /** * @Lifecycle Activate - * New one activated when old isnt being used. - * - * waitUntil(): activating ====> activated + * Clean up old caches and claim all clients */ self.addEventListener('activate', event => { - event.waitUntil(self.clients.claim()) - }) + event.waitUntil( + Promise.all([ + // Clean up old caches + caches.keys().then(cacheNames => { + return Promise.all( + cacheNames.map(cacheName => { + if (cacheName !== CACHE_NAME && + cacheName !== RUNTIME_CACHE && + cacheName !== OFFLINE_CACHE) { + console.log('Deleting old cache:', cacheName); + return caches.delete(cacheName); + } + }) + ); + }), + // Take control of all pages immediately + self.clients.claim() + ]) + ); + }); /** * @Functional Fetch From ad59552bb9c0d4854b7ae41727bffee49ab7f293 Mon Sep 17 00:00:00 2001 From: Lautner <30170747+Iautner@users.noreply.github.com> Date: Wed, 1 Oct 2025 02:00:21 +1000 Subject: [PATCH 11/28] Refactor UI for cleaner design and improved accessibility Removed app loader and touch feedback for a simpler, faster UI. Updated footer, enhanced link cards for accessibility, and streamlined CSS for better consistency and dark mode support. JavaScript was refactored to remove PWA install prompts, touch feedback, and performance optimizations, focusing on lazy loading, network status, and accessibility. --- _includes/footer.html | 2 +- _includes/links.html | 2 +- _layouts/default.html | 27 +--- assets/css/custom.css | 287 +++++++++++++---------------------- assets/js/lazy-load.js | 332 +---------------------------------------- 5 files changed, 114 insertions(+), 536 deletions(-) diff --git a/_includes/footer.html b/_includes/footer.html index d4abd12..ef57ac6 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -1,3 +1,3 @@ \ No newline at end of file diff --git a/_includes/links.html b/_includes/links.html index 65243c9..ee0dfa3 100644 --- a/_includes/links.html +++ b/_includes/links.html @@ -1,7 +1,7 @@