Reorganize repository: Move Hugo site to docker/showerloop/public, clean up root directory, update Docker configuration to use Caddy, add Woodpecker CI config
|  | @ -1,3 +1,4 @@ | |||
| .DS_Store | ||||
| node_modules | ||||
| public | ||||
| public/public | ||||
| temp_backup | ||||
|  | @ -0,0 +1,113 @@ | |||
| # build:1 | ||||
| labels: | ||||
|   location: manager | ||||
| clone: | ||||
|   git: | ||||
|     image: woodpeckerci/plugin-git | ||||
|     settings: | ||||
|       partial: false | ||||
|       depth: 1 | ||||
| when: | ||||
|   branch: [main, production] | ||||
| steps: | ||||
|   # Build Step for staging Branch | ||||
|   build-staging: | ||||
|     name: build-staging | ||||
|     image: woodpeckerci/plugin-docker-buildx | ||||
|     environment: | ||||
|       REGISTRY_USER: | ||||
|         from_secret: REGISTRY_USER | ||||
|       REGISTRY_PASSWORD: | ||||
|         from_secret: REGISTRY_PASSWORD | ||||
|     volumes: | ||||
|       - /var/run/docker.sock:/var/run/docker.sock | ||||
|     commands: | ||||
|       - echo "Building application for staging branch" | ||||
|       - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us | ||||
|       - echo compose build | ||||
|       - docker compose -f docker-compose.staging.yml pull --ignore-buildable | ||||
|       - docker compose -f docker-compose.staging.yml build --pull | ||||
|     when: | ||||
|       event: push | ||||
| 
 | ||||
|   deploy-new: | ||||
|     name: deploy-new | ||||
|     image: woodpeckerci/plugin-docker-buildx | ||||
|     environment: | ||||
|       REGISTRY_USER: | ||||
|         from_secret: REGISTRY_USER | ||||
|       REGISTRY_PASSWORD: | ||||
|         from_secret: REGISTRY_PASSWORD | ||||
|     volumes: | ||||
|       - /var/run/docker.sock:/var/run/docker.sock | ||||
|     commands: | ||||
|       - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us | ||||
|       - echo compose push | ||||
|       - docker compose -f docker-compose.staging.yml push | ||||
|       - docker stack deploy --with-registry-auth -c ./stack.staging.yml $${CI_REPO_NAME}-staging | ||||
|     when: | ||||
|       event: push | ||||
| 
 | ||||
|   cleanup-staging: | ||||
|     name: cleanup-staging | ||||
|     image: woodpeckerci/plugin-docker-buildx | ||||
|     environment: | ||||
|       REGISTRY_USER: | ||||
|         from_secret: REGISTRY_USER | ||||
|       REGISTRY_PASSWORD: | ||||
|         from_secret: REGISTRY_PASSWORD | ||||
|     volumes: | ||||
|       - /var/run/docker.sock:/var/run/docker.sock | ||||
|     commands: | ||||
|       - for i in {1..5}; do docker stack rm ${CI_REPO_NAME}-staging && break || sleep 10; done | ||||
|       - docker compose -f docker-compose.staging.yml down | ||||
|       - docker compose -f docker-compose.staging.yml rm -f | ||||
|     when: | ||||
|       event: push | ||||
| 
 | ||||
|   build-push-production: | ||||
|     name: build-push-production | ||||
|     image: woodpeckerci/plugin-docker-buildx | ||||
|     environment: | ||||
|       REGISTRY_USER: | ||||
|         from_secret: REGISTRY_USER | ||||
|       REGISTRY_PASSWORD: | ||||
|         from_secret: REGISTRY_PASSWORD | ||||
|     volumes: | ||||
|       - /var/run/docker.sock:/var/run/docker.sock | ||||
|     commands: | ||||
|       - echo "Building application for production branch" | ||||
|       - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us | ||||
|       - echo compose build | ||||
|       - docker compose -f docker-compose.production.yml pull --ignore-buildable | ||||
|       - docker compose -f docker-compose.production.yml build --pull | ||||
|       - docker compose -f docker-compose.production.yml push | ||||
|     when: | ||||
|       branch: main | ||||
|       event: push | ||||
| 
 | ||||
|   deploy-production: | ||||
|     name: deploy-production | ||||
|     image: woodpeckerci/plugin-docker-buildx | ||||
|     environment: | ||||
|       REGISTRY_USER: | ||||
|         from_secret: REGISTRY_USER | ||||
|       REGISTRY_PASSWORD: | ||||
|         from_secret: REGISTRY_PASSWORD | ||||
|     volumes: | ||||
|       - /var/run/docker.sock:/var/run/docker.sock | ||||
|     commands: | ||||
|       - echo "$${REGISTRY_PASSWORD}" | docker login -u "$${REGISTRY_USER}" --password-stdin git.nixc.us | ||||
|       - docker stack deploy --with-registry-auth -c ./stack.production.yml $${CI_REPO_NAME} | ||||
|     when: | ||||
|       branch: main | ||||
|       event: push | ||||
| 
 | ||||
|   post-deploy-smoke-tests-git-nixc-us: | ||||
|     name: run-post-deploy-smoke-tests-git-nixc-us | ||||
|     image: git.nixc.us/colin/playwright:latest | ||||
|     environment: | ||||
|       BASE_URL: "https://git.nixc.us" | ||||
|     when: | ||||
|       branch: production | ||||
|       event: push | ||||
|  | @ -1,68 +0,0 @@ | |||
| # CSS and JavaScript Optimization Guide | ||||
| 
 | ||||
| This document explains how to eliminate unused CSS and JavaScript from the ShowerLoop website. | ||||
| 
 | ||||
| ## Quick Start | ||||
| 
 | ||||
| 1. Install dependencies: | ||||
|    ```bash | ||||
|    npm install --legacy-peer-deps | ||||
|    ``` | ||||
| 
 | ||||
| 2. Build the production version with optimizations: | ||||
|    ```bash | ||||
|    ./build-production.sh | ||||
|    ``` | ||||
| 
 | ||||
| ## What This Does | ||||
| 
 | ||||
| ### CSS Optimization | ||||
| 
 | ||||
| The build script optimizes CSS in two ways: | ||||
| 
 | ||||
| 1. **CSSO Optimization**: Uses CSSO to remove unused selectors, merge similar rules, and minify the CSS. | ||||
|    - Typically reduces CSS by 5-30% depending on the file | ||||
|    - Maintains full functionality while eliminating bloat | ||||
|    - Works without requiring a running website | ||||
| 
 | ||||
| 2. **File Path Updates**: Automatically updates all HTML files to reference the optimized CSS versions. | ||||
| 
 | ||||
| ### JavaScript Optimization | ||||
| 
 | ||||
| JavaScript optimization happens through: | ||||
| 
 | ||||
| 1. **Tree Shaking**: Rollup analyzes your code to detect which parts are actually used and removes dead code. | ||||
|    - Eliminates unused imports and functions | ||||
|    - Reduces JavaScript file sizes significantly | ||||
| 
 | ||||
| 2. **Code Splitting**: JavaScript is split into separate bundles: | ||||
|    - app.modern.min.js - Main application code | ||||
|    - video-init.modern.min.js - Video player initialization | ||||
|    - skip-to-content.modern.min.js - Accessibility features | ||||
| 
 | ||||
| 3. **Minification**: All JavaScript is minified to reduce file size. | ||||
| 
 | ||||
| ## Optimization Results | ||||
| 
 | ||||
| In our tests, the optimization achieves: | ||||
| 
 | ||||
| | File Type | Size Reduction | | ||||
| |-----------|---------------| | ||||
| | CSS       | 5-30%         | | ||||
| | JavaScript| 30-60%        | | ||||
| 
 | ||||
| ## How to Keep It Optimized | ||||
| 
 | ||||
| 1. **Maintain Source Files**: Keep original JavaScript source files in `src/js/` | ||||
|     | ||||
| 2. **Run Build Before Deployment**: Always run `./build-production.sh` before deploying to production | ||||
| 
 | ||||
| 3. **Add New Components Wisely**: When adding new CSS/JS, consider if it's truly needed or if existing code can be reused | ||||
| 
 | ||||
| ## Troubleshooting | ||||
| 
 | ||||
| - **Missing Styles**: If elements look unstyled after optimization, check the original CSS files and update your HTML accordingly. | ||||
| 
 | ||||
| - **JavaScript Errors**: If functionality breaks, check the browser console for errors and ensure all required code is in your source files. | ||||
| 
 | ||||
| - **Build Errors**: Make sure all dependencies are installed with `npm install --legacy-peer-deps` before running the build scripts.  | ||||
							
								
								
									
										21
									
								
								LICENSE
								
								
								
								
							
							
						
						|  | @ -1,21 +0,0 @@ | |||
| MIT License | ||||
| 
 | ||||
| Copyright (c) 2020 Shower-Loop | ||||
| 
 | ||||
| Permission is hereby granted, free of charge, to any person obtaining a copy | ||||
| of this software and associated documentation files (the "Software"), to deal | ||||
| in the Software without restriction, including without limitation the rights | ||||
| to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||||
| copies of the Software, and to permit persons to whom the Software is | ||||
| furnished to do so, subject to the following conditions: | ||||
| 
 | ||||
| The above copyright notice and this permission notice shall be included in all | ||||
| copies or substantial portions of the Software. | ||||
| 
 | ||||
| THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||||
| IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||||
| FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||||
| AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||||
| LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||||
| OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||||
| SOFTWARE. | ||||
							
								
								
									
										35
									
								
								README.md
								
								
								
								
							
							
						
						|  | @ -1,27 +1,34 @@ | |||
| # website | ||||
| https://showerloop.cc new site for https://showerloop.org [planned] | ||||
| # ShowerLoop Website | ||||
| 
 | ||||
| ## Local Development | ||||
| This repository contains the source code for the ShowerLoop website, built with Hugo and served via Docker. | ||||
| 
 | ||||
| To test this site locally: | ||||
| ## Repository Structure | ||||
| 
 | ||||
| 1. Make sure you have [Hugo](https://gohugo.io/installation/) installed on your system | ||||
| 2. Clone this repository | ||||
| 3. Run the development server using the script: | ||||
| - `/docker/showerloop/` - Container configuration for the website | ||||
|   - `/docker/showerloop/public/` - Hugo website source files | ||||
|   - `/docker/showerloop/Dockerfile` - Docker image definition | ||||
|   - `/docker/showerloop/Caddyfile` - Caddy server configuration | ||||
| 
 | ||||
| ## Development | ||||
| 
 | ||||
| To run the Hugo development server: | ||||
| 
 | ||||
| ```bash | ||||
| cd docker/showerloop/public | ||||
| ./run-hugo-server.sh | ||||
| ``` | ||||
| 
 | ||||
| Or run Hugo directly: | ||||
| The development server will be available at http://localhost:1313/ | ||||
| 
 | ||||
| ## Building | ||||
| 
 | ||||
| To build the production site: | ||||
| 
 | ||||
| ```bash | ||||
| hugo server -D --disableFastRender | ||||
| cd docker/showerloop/public | ||||
| ./build-production.sh | ||||
| ``` | ||||
| 
 | ||||
| 4. Open your browser and go to http://localhost:1313/ to view the site | ||||
| 5. The site will automatically reload when you make changes to the content or templates | ||||
| ## Deployment | ||||
| 
 | ||||
| ### Notes | ||||
| - The `-D` flag enables draft content to be visible in the development environment | ||||
| - `--disableFastRender` ensures full rebuilds for more reliable preview | ||||
| The site is automatically built and deployed using Woodpecker CI when changes are pushed to the master branch.  | ||||
|  | @ -1 +0,0 @@ | |||
| <?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Categories on</title><link>http://localhost:54386/categories/</link><description>Recent content in Categories on</description><generator>Hugo</generator><language>en-us</language><atom:link href="http://localhost:54386/categories/index.xml" rel="self" type="application/rss+xml"/></channel></rss> | ||||
|  | @ -1,29 +0,0 @@ | |||
| <!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=53498&path=livereload" data-no-instant defer></script><title>Support the Project | ShowerLoop</title> | ||||
| <meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="ShowerLoop - Real-time filtration, purification, recycling & heat recovery system for showers. Open source and sustainable water conservation technology."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><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><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><script type=module> | ||||
|    | ||||
|   import * as utils from '/js/utils.modern.min.js'; | ||||
|   window.utilsModule = utils;  | ||||
| </script><script type=module src=/js/app.modern.min.js defer></script><script type=module src=/js/skip-to-content.modern.min.js defer></script><script type=module src=/js/material.modern.min.js defer></script><script nomodule src=/js/app.min.js defer></script><script nomodule src=/js/skip-to-content.min.js defer></script><script nomodule src=/js/material.min.js defer></script><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><body class=page><div class=skip-to-content role=button tabindex=0>Skip to Content</div><style>.mdl-navigation .mdl-button.mdl-navigation__link{display:flex;align-items:center;justify-content:center;height:36px;line-height:36px;padding:0 16px;margin:8px 0}.logo{height:50px;width:auto;max-width:150px;transition:none!important}</style><div class="mdl-layout mdl-js-layout | ||||
| mdl-layout--fixed-header"><header class="mdl-layout__header site-header"><div class=mdl-layout__header-row><a href=/ class=mdl-layout-title><img class=logo src=/images/logo2.webp height=50 width=auto alt="ShowerLoop Logo"></a><div class=mdl-layout-spacer></div><nav class="mdl-navigation mdl-layout--large-screen-only"><a class=mdl-navigation__link href=/ title=Home>Home</a> | ||||
| <a class=mdl-navigation__link href=/how-it-works/ title="How It Works">How It Works</a> | ||||
| <a class=mdl-navigation__link href=/research/ title=Research>Research</a> | ||||
| <a class=mdl-navigation__link href=/posts/ title=Posts>Posts</a> | ||||
| <a class=mdl-navigation__link href=/components/ title=Components>Components</a> | ||||
| <a class="mdl-navigation__link mdl-button mdl-js-button mdl-button--raised mdl-button--colored" href=/make-it/ title="Make It">Make It</a></nav></div></header><div class=mdl-layout__drawer><span class=mdl-layout-title><strong>ShowerLoop</strong></span><nav class=mdl-navigation><a class=mdl-navigation__link href=/ title=Home tabindex=0>Home</a> | ||||
| <a class=mdl-navigation__link href=/how-it-works/ title="How It Works" tabindex=0>How It Works</a> | ||||
| <a class=mdl-navigation__link href=/research/ title=Research tabindex=0>Research</a> | ||||
| <a class=mdl-navigation__link href=/posts/ title=Posts tabindex=0>Posts</a> | ||||
| <a class=mdl-navigation__link href=/components/ title=Components tabindex=0>Components</a> | ||||
| <a class="mdl-navigation__link mdl-button mdl-js-button mdl-button--raised mdl-button--colored" href=/make-it/ title="Make It" tabindex=0>Make It</a></nav></div><main aria-role=main><div class=subpage-content><div class=chocolate-container><h1>Support the Project</h1><div class=mdl-grid><section class="mdl-cell mdl-cell--12-col"><p>If you want to support the ShowerLoop project, check out our <a href=/make-it>Make-It Button</a> where you can contribute to our ongoing development.</p><pre><code>    <p>We work with limited resources and recycle many materials. With access to workspace and tools (thank you, Fablab!), we're able to continue developing this sustainable technology.</p> | ||||
|      | ||||
|     <p>We're collaborating with people around the world to bring ShowerLoop to various environments - boats, RVs, houses in remote locations, Pacific Islands, eco-friendly buildings, and historic structures without modern plumbing. The possibilities are endless, and together we hope to realize them all.</p> | ||||
|      | ||||
|     <p>Our team is developing a business model that fosters community around social and environmental ethics within the open source circular economy.</p> | ||||
|      | ||||
|     <div class="mdl-grid" style="justify-content: center; margin-top: 30px;"> | ||||
|         <a class="mdl-button mdl-js-button mdl-button--raised mdl-button--colored" href="/make-it"> | ||||
|             Support the Project | ||||
|         </a> | ||||
|     </div> | ||||
| </section> | ||||
| </code></pre></div></div></div></main><footer class="mdl-mini-footer site-footer"><div class=mdl-mini-footer__left-section>© 2020 Shower Loop | All Rights Reserved</div><div class=mdl-mini-footer__right-section></div></footer></div><script>document.addEventListener("DOMContentLoaded",function(){setTimeout(function(){if(!window.__bfcacheLiveReloadActive){const e=window.location.protocol==="https:"?"wss:":"ws:",t=`${e}//${window.location.host}/__livereload`;try{if(window.OriginalWebSocket){const e=new window.OriginalWebSocket(t);e.onmessage=function(e){e.data==="reload"&&window.location.reload()},window.__bfcacheLiveReloadActive=!0}}catch(e){console.warn("LiveReload connection error:",e)}}},500)})</script></body></html> | ||||
|  | @ -0,0 +1,7 @@ | |||
| services: | ||||
|   showerloop: | ||||
|     build: | ||||
|       context: docker/showerloop | ||||
|       dockerfile: Dockerfile.production | ||||
|       pull: true | ||||
|     image: git.nixc.us/colin/showerloop-cc:production | ||||
|  | @ -0,0 +1,6 @@ | |||
| services: | ||||
|   showerloop: | ||||
|     build: | ||||
|       context: docker/showerloop | ||||
|       pull: true | ||||
|     image: git.nixc.us/colin/showerloop-cc:staging | ||||
|  | @ -0,0 +1 @@ | |||
| FROM git.nixc.us/colin/showerloop-cc:staging | ||||
|  | @ -0,0 +1,87 @@ | |||
| # Template: Caddyfile.override | ||||
| # Purpose: Default configuration for custom containers. | ||||
| # Description: | ||||
| # - Serves static files from /srv. | ||||
| # - Provides a /health endpoint for health checks. | ||||
| # - Designed to run behind a reverse proxy like Træfik, listening only on port 80. | ||||
| # - comes with security headers | ||||
| 
 | ||||
| :80 { | ||||
|     # Health check endpoint | ||||
|     respond /health "OK" 200 | ||||
| 
 | ||||
|     # Enable compression for text-based resources | ||||
|     encode gzip zstd | ||||
| 
 | ||||
|     # Security headers | ||||
|     header { | ||||
|         # Cross-Origin headers | ||||
|         Cross-Origin-Embedder-Policy "require-corp" | ||||
|         Cross-Origin-Opener-Policy "same-origin" | ||||
|         Cross-Origin-Resource-Policy "same-origin" | ||||
|          | ||||
|         # Permissions Policy | ||||
|         Permissions-Policy "camera=(), microphone=(), geolocation=(), interest-cohort=()" | ||||
|          | ||||
|         # Referrer Policy | ||||
|         Referrer-Policy "strict-origin-when-cross-origin" | ||||
|          | ||||
|         # HSTS | ||||
|         Strict-Transport-Security "max-age=31536000; includeSubDomains; preload" | ||||
|          | ||||
|         # Content Type Options | ||||
|         X-Content-Type-Options "nosniff" | ||||
|          | ||||
|         # XSS Protection | ||||
|         X-XSS-Protection "1; mode=block" | ||||
|          | ||||
|         # Frame Options (prevents clickjacking) | ||||
|         X-Frame-Options "SAMEORIGIN" | ||||
|          | ||||
|         # Frame ancestors (prevents embedding in other sites) | ||||
|         Content-Security-Policy "frame-ancestors 'none'" | ||||
|          | ||||
|         # Remove Server header | ||||
|         -Server | ||||
|     } | ||||
| 
 | ||||
|     # Cache control for static assets - images, fonts, etc. | ||||
|     @staticAssets { | ||||
|         path *.jpg *.jpeg *.png *.webp *.avif *.gif *.ico *.svg *.woff *.woff2 *.ttf *.eot | ||||
|         method GET HEAD | ||||
|     } | ||||
|     header @staticAssets Cache-Control "public, max-age=31536000, immutable" | ||||
|     header @staticAssets ?Access-Control-Allow-Origin * | ||||
| 
 | ||||
|     # Special handling for CSS and JS files | ||||
|     @cssAndJs { | ||||
|         path *.css *.js | ||||
|         method GET HEAD | ||||
|     } | ||||
|     header @cssAndJs Cache-Control "public, max-age=31536000, immutable" | ||||
| 
 | ||||
|     # Cache HTML files but for a shorter period | ||||
|     @htmlFiles { | ||||
|         path *.html | ||||
|         method GET HEAD | ||||
|     } | ||||
|     header @htmlFiles Cache-Control "public, max-age=86400, must-revalidate" | ||||
| 
 | ||||
|     # Static file server | ||||
|     file_server { | ||||
|         root /srv # Root directory for serving static files | ||||
|     } | ||||
| 
 | ||||
|     # Restrict allowed methods to only GET and HEAD | ||||
|     @staticRequests { | ||||
|         method GET HEAD | ||||
|     } | ||||
| 
 | ||||
|     handle @staticRequests { | ||||
|         root * /srv | ||||
|         file_server | ||||
|     } | ||||
| 
 | ||||
|     # Handle all other methods | ||||
|     respond "Method Not Allowed" 405 | ||||
| }  | ||||
|  | @ -0,0 +1,42 @@ | |||
| # Stage 1: Build Hugo site | ||||
| FROM alpine:latest AS hugo-builder | ||||
| 
 | ||||
| # Install necessary dependencies (Hugo, Git, Node.js, and npm) | ||||
| RUN apk add --no-cache hugo git nodejs npm | ||||
| 
 | ||||
| # Copy our enhanced Caddyfile | ||||
| COPY Caddyfile.default.template /etc/caddy/Caddyfile.override | ||||
| 
 | ||||
| # Set working directory | ||||
| WORKDIR /site | ||||
| 
 | ||||
| # Copy the Hugo source files | ||||
| COPY public/ /site | ||||
| 
 | ||||
| # Install PostCSS and its dependencies locally and update browserslist | ||||
| RUN cd /site && npm init -y && \ | ||||
|     npm install --save-dev postcss postcss-cli autoprefixer && \ | ||||
|     npm install --save-dev caniuse-lite && \ | ||||
|     npm update caniuse-lite browserslist | ||||
| 
 | ||||
| # Build the Hugo site for production with optimizations | ||||
| # Disable GitInfo, enable minification, and set production environment | ||||
| RUN mkdir /public && \ | ||||
|     cd /site && \ | ||||
|     HUGO_ENABLEGITINFO=false \ | ||||
|     HUGO_ENV=production \ | ||||
|     npm run build:prod && \ | ||||
|     cp -r /site/public/* /public/ | ||||
| 
 | ||||
| # Stage 2: Production image with prebuilt static files | ||||
| FROM git.nixc.us/colin/container-base:production-nixiusstatic | ||||
| 
 | ||||
| # Copy the built site from the first stage | ||||
| COPY --from=hugo-builder /public /srv | ||||
| 
 | ||||
| # Copy our enhanced Caddyfile for development | ||||
| COPY Caddyfile.default.template /etc/caddy/Caddyfile.override | ||||
| 
 | ||||
| # Add health check endpoint for production monitoring | ||||
| HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ | ||||
|     CMD wget -q --spider http://localhost/healthcheck.txt || exit 1 | ||||
|  | @ -0,0 +1 @@ | |||
| <?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Categories on</title><link>http://localhost:1313/categories/</link><description>Recent content in Categories on</description><generator>Hugo</generator><language>en-us</language><atom:link href="http://localhost:1313/categories/index.xml" rel="self" type="application/rss+xml"/></channel></rss> | ||||
|  | @ -1,4 +1,4 @@ | |||
| <!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=54386&path=livereload" data-no-instant defer></script><title>Components | ShowerLoop</title> | ||||
| <!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script><title>Components | ShowerLoop</title> | ||||
| <meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="ShowerLoop - Real-time filtration, purification, recycling & heat recovery system for showers. Open source and sustainable water conservation technology."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><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><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><script type=module> | ||||
|    | ||||
|   import * as utils from '/js/utils.modern.min.js'; | ||||
| Before Width: | Height: | Size: 730 KiB After Width: | Height: | Size: 730 KiB | 
| Before Width: | Height: | Size: 141 KiB After Width: | Height: | Size: 141 KiB | 
| Before Width: | Height: | Size: 898 KiB After Width: | Height: | Size: 898 KiB | 
|  | @ -1,4 +1,4 @@ | |||
| <!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=54386&path=livereload" data-no-instant defer></script><title>How It Works | ShowerLoop</title> | ||||
| <!doctype html><html lang=en><head><script src="/livereload.js?mindelay=10&v=2&port=1313&path=livereload" data-no-instant defer></script><title>How It Works | ShowerLoop</title> | ||||
| <meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><meta name=description content="Learn how the ShowerLoop system filters, purifies and recycles shower water in real-time, saving up to 90% of water and energy."><script>let liveReloadSocket=null;const OriginalWebSocket=window.WebSocket;window.WebSocket=function(e,t){if(e.includes("/__livereload")){if(document.readyState==="complete"){liveReloadSocket=new OriginalWebSocket(e,t);const n={onmessage:null,onclose:null};return Object.defineProperty(liveReloadSocket,"onmessage",{set:function(e){n.onmessage=e},get:function(){return n.onmessage}}),Object.defineProperty(liveReloadSocket,"onclose",{set:function(e){n.onclose=e},get:function(){return n.onclose}}),liveReloadSocket.addEventListener("message",function(e){n.onmessage&&n.onmessage(e)}),liveReloadSocket.addEventListener("close",function(e){liveReloadSocket=null,n.onclose&&n.onclose(e)}),liveReloadSocket}return{send:function(){},close:function(){},addEventListener:function(){},removeEventListener:function(){},set onmessage(e){},set onclose(e){}}}return new OriginalWebSocket(e,t)};for(const e in OriginalWebSocket)OriginalWebSocket.hasOwnProperty(e)&&(window.WebSocket[e]=OriginalWebSocket[e]);window.WebSocket.prototype=OriginalWebSocket.prototype,document.addEventListener("pageshow",function(e){if(e.persisted){console.log("Page restored from bfcache");const e=window.location.protocol==="https:"?"wss:":"ws:";liveReloadSocket=new OriginalWebSocket(`${e}//${window.location.host}/__livereload`),liveReloadSocket.onmessage=function(e){e.data==="reload"&&window.location.reload()}}}),window.addEventListener("pagehide",function(){liveReloadSocket&&(liveReloadSocket.onclose=null,liveReloadSocket.close(),liveReloadSocket=null)})</script><link rel=preload href=/css/vendor/material-icons.css as=style><link rel=preload href=/images/logo2.webp as=image><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><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><script type=module> | ||||
|    | ||||
|   import * as utils from '/js/utils.modern.min.js'; | ||||
| Before Width: | Height: | Size: 270 KiB After Width: | Height: | Size: 270 KiB | 
| Before Width: | Height: | Size: 86 KiB After Width: | Height: | Size: 86 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 213 KiB After Width: | Height: | Size: 213 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB | 
| Before Width: | Height: | Size: 70 KiB After Width: | Height: | Size: 70 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 13 KiB After Width: | Height: | Size: 13 KiB | 
| Before Width: | Height: | Size: 60 KiB After Width: | Height: | Size: 60 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 14 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 23 KiB After Width: | Height: | Size: 23 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB | 
| Before Width: | Height: | Size: 101 KiB After Width: | Height: | Size: 101 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 194 KiB After Width: | Height: | Size: 194 KiB | 
| Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 68 KiB | 
| Before Width: | Height: | Size: 18 KiB After Width: | Height: | Size: 18 KiB | 
| Before Width: | Height: | Size: 22 KiB After Width: | Height: | Size: 22 KiB | 
| Before Width: | Height: | Size: 16 KiB After Width: | Height: | Size: 16 KiB | 
| Before Width: | Height: | Size: 31 KiB After Width: | Height: | Size: 31 KiB | 
| Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 53 KiB | 
| Before Width: | Height: | Size: 28 KiB After Width: | Height: | Size: 28 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB | 
| Before Width: | Height: | Size: 988 KiB After Width: | Height: | Size: 988 KiB | 
| Before Width: | Height: | Size: 78 KiB After Width: | Height: | Size: 78 KiB | 
| Before Width: | Height: | Size: 56 KiB After Width: | Height: | Size: 56 KiB | 
| Before Width: | Height: | Size: 1.0 MiB After Width: | Height: | Size: 1.0 MiB | 
| Before Width: | Height: | Size: 76 KiB After Width: | Height: | Size: 76 KiB | 
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 121 KiB After Width: | Height: | Size: 121 KiB | 
| Before Width: | Height: | Size: 82 KiB After Width: | Height: | Size: 82 KiB | 
| Before Width: | Height: | Size: 488 KiB After Width: | Height: | Size: 488 KiB | 
| Before Width: | Height: | Size: 156 KiB After Width: | Height: | Size: 156 KiB | 
| Before Width: | Height: | Size: 221 KiB After Width: | Height: | Size: 221 KiB | 
 Your Name
						Your Name