175 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
			
		
		
	
	
			175 lines
		
	
	
		
			6.4 KiB
		
	
	
	
		
			HTML
		
	
	
	
| <!DOCTYPE html>
 | |
| <html lang="en">
 | |
| <head>
 | |
| <title>{{.Title }} | ShowerLoop</title>
 | |
| 
 | |
| <meta charset="utf-8">
 | |
| <meta name="viewport" content="width=device-width, initial-scale=1">
 | |
| <!-- Meta description -->
 | |
| {{ with .Description }}
 | |
| <meta name="description" content="{{ . }}">
 | |
| {{ else }}
 | |
| <meta name="description" content="ShowerLoop - Real-time filtration, purification, recycling & heat recovery system for showers. Open source and sustainable water conservation technology.">
 | |
| {{ end }}
 | |
| 
 | |
| <!-- Back/Forward Cache Fix for Hugo Development Server -->
 | |
| {{ if eq hugo.Environment "development" }}
 | |
| <script>
 | |
|   // Store WebSocket connections to properly handle back/forward navigation
 | |
|   let liveReloadSocket = null;
 | |
|   
 | |
|   // Override the default WebSocket constructor to control the livereload connection
 | |
|   const OriginalWebSocket = window.WebSocket;
 | |
|   window.WebSocket = function(url, protocols) {
 | |
|     // If this is Hugo's livereload WebSocket
 | |
|     if (url.includes('/__livereload')) {
 | |
|       // Don't create the WebSocket immediately if document is still loading
 | |
|       // This allows the bfcache to work
 | |
|       if (document.readyState === 'complete') {
 | |
|         liveReloadSocket = new OriginalWebSocket(url, protocols);
 | |
|         
 | |
|         // Store the original handlers
 | |
|         const originalHandlers = {
 | |
|           onmessage: null,
 | |
|           onclose: null
 | |
|         };
 | |
|         
 | |
|         // Proxy the event handlers
 | |
|         Object.defineProperty(liveReloadSocket, 'onmessage', {
 | |
|           set: function(handler) {
 | |
|             originalHandlers.onmessage = handler;
 | |
|           },
 | |
|           get: function() {
 | |
|             return originalHandlers.onmessage;
 | |
|           }
 | |
|         });
 | |
|         
 | |
|         Object.defineProperty(liveReloadSocket, 'onclose', {
 | |
|           set: function(handler) {
 | |
|             originalHandlers.onclose = handler;
 | |
|           },
 | |
|           get: function() {
 | |
|             return originalHandlers.onclose;
 | |
|           }
 | |
|         });
 | |
|         
 | |
|         // Implement our own handlers
 | |
|         liveReloadSocket.addEventListener('message', function(e) {
 | |
|           if (originalHandlers.onmessage) originalHandlers.onmessage(e);
 | |
|         });
 | |
|         
 | |
|         liveReloadSocket.addEventListener('close', function(e) {
 | |
|           liveReloadSocket = null;
 | |
|           if (originalHandlers.onclose) originalHandlers.onclose(e);
 | |
|         });
 | |
|         
 | |
|         return liveReloadSocket;
 | |
|       }
 | |
|       
 | |
|       // Return a fake WebSocket object that does nothing
 | |
|       // This allows the page to be cached
 | |
|       return {
 | |
|         send: function() {},
 | |
|         close: function() {},
 | |
|         addEventListener: function() {},
 | |
|         removeEventListener: function() {},
 | |
|         set onmessage(handler) {},
 | |
|         set onclose(handler) {}
 | |
|       };
 | |
|     }
 | |
|     
 | |
|     // For all other WebSockets, use the original constructor
 | |
|     return new OriginalWebSocket(url, protocols);
 | |
|   };
 | |
|   
 | |
|   // Copy properties from the original WebSocket
 | |
|   for (const prop in OriginalWebSocket) {
 | |
|     if (OriginalWebSocket.hasOwnProperty(prop)) {
 | |
|       window.WebSocket[prop] = OriginalWebSocket[prop];
 | |
|     }
 | |
|   }
 | |
|   window.WebSocket.prototype = OriginalWebSocket.prototype;
 | |
|   
 | |
|   // When page is shown (including from bfcache)
 | |
|   document.addEventListener('pageshow', function(event) {
 | |
|     // Check if page is coming from bfcache
 | |
|     if (event.persisted) {
 | |
|       console.log('Page restored from bfcache');
 | |
|       
 | |
|       // Reconnect WebSocket for live reload
 | |
|       const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
 | |
|       liveReloadSocket = new OriginalWebSocket(`${protocol}//${window.location.host}/__livereload`);
 | |
|       
 | |
|       liveReloadSocket.onmessage = function(e) {
 | |
|         if (e.data === 'reload') {
 | |
|           window.location.reload();
 | |
|         }
 | |
|       };
 | |
|     }
 | |
|   });
 | |
|   
 | |
|   // Disconnect WebSocket before page unloads
 | |
|   window.addEventListener('pagehide', function() {
 | |
|     if (liveReloadSocket) {
 | |
|       liveReloadSocket.onclose = null; // Prevent auto-reconnect
 | |
|       liveReloadSocket.close();
 | |
|       liveReloadSocket = null;
 | |
|     }
 | |
|   });
 | |
| </script>
 | |
| {{ end }}
 | |
| 
 | |
| <!-- Critical CSS preloaded -->
 | |
| <link rel="preload" href="/css/vendor/material-icons.css" as="style">
 | |
| <!-- Removed VideoJS preload to reduce initial payload -->
 | |
| 
 | |
| <!-- Preload logo image -->
 | |
| <link rel="preload" href="/images/logo2.webp" as="image">
 | |
| 
 | |
| <!-- Material Design -->
 | |
| <link rel="stylesheet" href="/css/vendor/material-icons.css">
 | |
| <link rel="stylesheet" href="/css/vendor/material.indigo-pink.min.css" media="print" onload="this.media='all'">
 | |
| <noscript><link rel="stylesheet" href="/css/vendor/material.indigo-pink.min.css"></noscript>
 | |
| 
 | |
| <!-- Font Awesome - deferred loading -->
 | |
| <link rel="stylesheet" href="/css/vendor/fontawesome.min.css" media="print" onload="this.media='all'">
 | |
| <noscript><link rel="stylesheet" href="/css/vendor/fontawesome.min.css"></noscript>
 | |
| 
 | |
| <!-- Modern browsers - ES modules with efficient loading -->
 | |
| {{ $hasVideo := or (eq .Layout "video") (isset .Params "hero_video_url") (findRE "<video.*class=\"video-js\".*>" .Content) }}
 | |
| 
 | |
| <!-- Essential utilities first - other scripts depend on this -->
 | |
| <script type="module">
 | |
|   // Create a dynamic import that will resolve ./utils.js relative imports
 | |
|   import * as utils from '/js/utils.modern.min.js';
 | |
|   window.utilsModule = utils; // Make it globally available if needed
 | |
| </script>
 | |
| 
 | |
| <!-- Core app script - always load this -->
 | |
| <script type="module" src="/js/app.modern.min.js" defer></script>
 | |
| 
 | |
| <!-- Load only video initialization code (actual VideoJS loaded on demand) -->
 | |
| {{ if $hasVideo }}
 | |
| <script type="module" src="/js/video-init.modern.min.js" defer></script>
 | |
| {{ end }}
 | |
| 
 | |
| <!-- Accessibility script -->
 | |
| <script type="module" src="/js/skip-to-content.modern.min.js" defer></script>
 | |
| 
 | |
| <!-- Material Design loaded with defer for non-critical UI -->
 | |
| <script type="module" src="/js/material.modern.min.js" defer></script>
 | |
| 
 | |
| <!-- Legacy browsers support - with polyfills -->
 | |
| <script nomodule src="/js/app.min.js" defer></script>
 | |
| {{ if $hasVideo }}
 | |
| <script nomodule src="/js/video-init.min.js" defer></script>
 | |
| {{ end }}
 | |
| <script nomodule src="/js/skip-to-content.min.js" defer></script>
 | |
| <script nomodule src="/js/material.min.js" defer></script>
 | |
| 
 | |
| <!-- Custom CSS -->
 | |
| <link rel="stylesheet" type="text/css" href="/css/app.min.css">
 | |
| <link rel="stylesheet" type="text/css" href="/css/custom.css" media="print" onload="this.media='all'">
 | |
| <noscript><link rel="stylesheet" href="/css/custom.css"></noscript>
 | |
| </head>
 |