From d9961a76ad79f3feacb90fe75c505ec476281392 Mon Sep 17 00:00:00 2001 From: "Dr. Jones" Date: Wed, 8 Jul 2026 06:18:17 +0000 Subject: [PATCH] Initial commit: site code, layouts, content (images added separately) Fixes duplicate placeholder cover image on the 3 newest blog posts, broken aspect-ratio classes on the albums listing page, and a non-responsive fixed sidebar on the chat page. --- .gitignore | 5 + IMPLEMENTATION_SUMMARY.md | 138 + MEMBERSHIP_SETUP.md | 122 + README.md | 29 + RECOVERY.md | 36 + archetypes/default.md | 5 + assets/css/main.css | 21 + content/about.md | 40 + content/admin.md | 7 + content/albums/_index.md | 4 + content/albums/commercial-lettuce.md | 30 + content/albums/medical-dwc.md | 30 + content/albums/my-garden.md | 338 ++ content/blog/_index.md | 4 + content/blog/post-1.md | 12 + content/blog/post-10.md | 22 + content/blog/post-11.md | 30 + content/blog/post-2.md | 12 + content/blog/post-3.md | 12 + content/blog/post-4.md | 13 + content/blog/post-5.md | 10 + content/blog/post-6.md | 10 + content/blog/post-7.md | 11 + content/blog/post-8.md | 11 + content/blog/post-9.md | 22 + content/chat.md | 11 + content/contact.md | 61 + content/earnings.md | 10 + content/library/_index.md | 4 + content/membership.md | 7 + content/offerings.md | 36 + content/services/1-consultation.md | 19 + content/services/2-design.md | 19 + content/services/3-mentorship.md | 19 + content/services/_index.md | 4 + content/testimonials.md | 6 + data/library.yaml | 514 +++ db/members.db | Bin 0 -> 45056 bytes hugo.yaml | 74 + layouts/_default/baseof.html | 207 ++ layouts/_default/list.html | 42 + layouts/_default/single.html | 13 + layouts/admin/single.html | 360 ++ layouts/albums/list.html | 35 + layouts/albums/single.html | 98 + layouts/chat/single.html | 498 +++ layouts/contact/single.html | 40 + layouts/earnings/single.html | 308 ++ layouts/index.html | 121 + layouts/library/list.html | 86 + layouts/membership/single.html | 221 ++ layouts/services/list.html | 34 + layouts/testimonials/single.html | 159 + library.yaml.backup | 628 ++++ package-lock.json | 2916 +++++++++++++++++ package.json | 20 + postcss.config.js | 6 + public | 1 + requirements.txt | 8 + scripts/CHAT_API_README.md | 112 + scripts/README.md | 171 + scripts/__pycache__/chat_api.cpython-310.pyc | Bin 0 -> 9073 bytes .../generate_blog_post.cpython-310.pyc | Bin 0 -> 8055 bytes .../membership_api.cpython-310.pyc | Bin 0 -> 23788 bytes scripts/chat_api.py | 348 ++ scripts/generate_blog_post.py | 320 ++ scripts/membership_api.py | 478 +++ scripts/start_hugo_server.sh | 42 + static/js/membership.js | 403 +++ static/pdfs/cleaning.pdf | 0 static/pdfs/deficiencies.pdf | 0 static/pdfs/dutch-buckets.pdf | 0 static/pdfs/dwc-basics.pdf | 0 static/pdfs/lighting.pdf | 0 static/pdfs/nft-guide.pdf | 0 static/pdfs/pests.pdf | 0 static/pdfs/ph-ec-mastery.pdf | 0 static/pdfs/seeds.pdf | 0 static/pdfs/sterile-res.pdf | 0 static/robots.txt | 5 + 80 files changed, 9438 insertions(+) create mode 100644 .gitignore create mode 100644 IMPLEMENTATION_SUMMARY.md create mode 100644 MEMBERSHIP_SETUP.md create mode 100644 README.md create mode 100644 RECOVERY.md create mode 100644 archetypes/default.md create mode 100644 assets/css/main.css create mode 100644 content/about.md create mode 100644 content/admin.md create mode 100644 content/albums/_index.md create mode 100644 content/albums/commercial-lettuce.md create mode 100644 content/albums/medical-dwc.md create mode 100644 content/albums/my-garden.md create mode 100644 content/blog/_index.md create mode 100644 content/blog/post-1.md create mode 100644 content/blog/post-10.md create mode 100644 content/blog/post-11.md create mode 100644 content/blog/post-2.md create mode 100644 content/blog/post-3.md create mode 100644 content/blog/post-4.md create mode 100644 content/blog/post-5.md create mode 100644 content/blog/post-6.md create mode 100644 content/blog/post-7.md create mode 100644 content/blog/post-8.md create mode 100644 content/blog/post-9.md create mode 100644 content/chat.md create mode 100644 content/contact.md create mode 100644 content/earnings.md create mode 100644 content/library/_index.md create mode 100644 content/membership.md create mode 100644 content/offerings.md create mode 100644 content/services/1-consultation.md create mode 100644 content/services/2-design.md create mode 100644 content/services/3-mentorship.md create mode 100644 content/services/_index.md create mode 100644 content/testimonials.md create mode 100644 data/library.yaml create mode 100644 db/members.db create mode 100644 hugo.yaml create mode 100644 layouts/_default/baseof.html create mode 100644 layouts/_default/list.html create mode 100644 layouts/_default/single.html create mode 100644 layouts/admin/single.html create mode 100644 layouts/albums/list.html create mode 100644 layouts/albums/single.html create mode 100644 layouts/chat/single.html create mode 100644 layouts/contact/single.html create mode 100644 layouts/earnings/single.html create mode 100644 layouts/index.html create mode 100644 layouts/library/list.html create mode 100644 layouts/membership/single.html create mode 100644 layouts/services/list.html create mode 100644 layouts/testimonials/single.html create mode 100644 library.yaml.backup create mode 100644 package-lock.json create mode 100644 package.json create mode 100644 postcss.config.js create mode 120000 public create mode 100644 requirements.txt create mode 100644 scripts/CHAT_API_README.md create mode 100644 scripts/README.md create mode 100644 scripts/__pycache__/chat_api.cpython-310.pyc create mode 100644 scripts/__pycache__/generate_blog_post.cpython-310.pyc create mode 100644 scripts/__pycache__/membership_api.cpython-310.pyc create mode 100755 scripts/chat_api.py create mode 100755 scripts/generate_blog_post.py create mode 100644 scripts/membership_api.py create mode 100755 scripts/start_hugo_server.sh create mode 100644 static/js/membership.js create mode 100644 static/pdfs/cleaning.pdf create mode 100644 static/pdfs/deficiencies.pdf create mode 100644 static/pdfs/dutch-buckets.pdf create mode 100644 static/pdfs/dwc-basics.pdf create mode 100644 static/pdfs/lighting.pdf create mode 100644 static/pdfs/nft-guide.pdf create mode 100644 static/pdfs/pests.pdf create mode 100644 static/pdfs/ph-ec-mastery.pdf create mode 100644 static/pdfs/seeds.pdf create mode 100644 static/pdfs/sterile-res.pdf create mode 100644 static/robots.txt diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..450f34f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +node_modules/ +public/ +resources/ +.hugo_build.lock +hugo_stats.json diff --git a/IMPLEMENTATION_SUMMARY.md b/IMPLEMENTATION_SUMMARY.md new file mode 100644 index 0000000..5e6a0aa --- /dev/null +++ b/IMPLEMENTATION_SUMMARY.md @@ -0,0 +1,138 @@ +# Membership Paywall System - Implementation Summary + +## Files Created + +### Backend +- `scripts/membership_api.py` - Flask API server for membership management +- `requirements.txt` - Python dependencies + +### Frontend +- `static/js/membership.js` - Client-side membership functionality +- `layouts/membership/single.html` - Membership signup/login page +- `layouts/testimonials/single.html` - Testimonials page with paywall +- `layouts/admin/single.html` - Admin approval panel +- `layouts/contact/single.html` - Contact page layout + +### Content +- `content/membership.md` - Membership page content +- `content/testimonials.md` - Testimonials page content +- `content/admin.md` - Admin panel page + +### Documentation +- `MEMBERSHIP_SETUP.md` - Setup and configuration guide + +## Files Modified + +- `layouts/_default/baseof.html` - Added membership.js, auth UI, navigation updates +- `layouts/library/list.html` - Added paywall and download tracking +- `content/contact.md` - Paywalled email, secure form +- `hugo.yaml` - Added membership and testimonials to menu + +## Key Features Implemented + +1. **User Registration & Authentication** + - Email/password registration + - JWT-based session management + - Password hashing with bcrypt + +2. **Download Tracking** + - IP-based tracking (1 free download) + - Unlimited downloads for members + - Automatic tracking on download clicks + +3. **Paywall System** + - Library downloads (1 free, then paywall) + - Contact form (members only) + - Email address (members only) + - Testimonials page (members only) + +4. **Admin Panel** + - Secure admin login + - Approve/deny memberships + - Moderate testimonials + - View pending members + +5. **Email Notifications** + - Signup notifications to admin + - Approval confirmations to users + - Contact form submissions + +6. **Buy Me a Coffee Integration** + - Prominent $5 donation button + - Users donate first, then sign up + - Manual verification by admin + +## Configuration Required + +1. **Environment Variables** (set before running API): + ``` + ADMIN_PASSWORD=your-strong-password + ADMIN_EMAIL=indiana.holmes8@gmail.com + JWT_SECRET=random-secret-key + MEMBERSHIP_PORT=5001 + SMTP_HOST=smtp.gmail.com (optional) + SMTP_PORT=587 (optional) + SMTP_USER=your-email@gmail.com (optional) + SMTP_PASS=your-app-password (optional) + ``` + +2. **API URL Configuration** (update for production): + - `static/js/membership.js` line 4: `API_BASE` + - `layouts/admin/single.html` line 186: `ADMIN_API_BASE` + +3. **Start the API Server**: + ```bash + python3 /root/hydro-sterile/scripts/membership_api.py + ``` + +## Database + +SQLite database automatically created at: +`/root/hydro-sterile/data/members.db` + +Tables: +- `users` - User accounts and status +- `downloads` - Download tracking +- `testimonials` - User testimonials +- `sessions` - User sessions +- `admin_sessions` - Admin sessions +- `contact_submissions` - Contact form submissions + +## Security Features + +- Password hashing (bcrypt) +- JWT tokens with expiration +- Rate limiting on all endpoints +- Input sanitization +- SQL injection prevention (parameterized queries) +- CSRF protection ready +- Secure admin authentication +- Email privacy (not exposed in frontend) + +## Next Steps + +1. Set environment variables +2. Install dependencies: `pip3 install -r requirements.txt` +3. Start API server: `python3 scripts/membership_api.py` +4. Test registration flow +5. Test admin panel +6. Update API URLs for production +7. Configure SMTP for email notifications +8. Test email sending + +## Testing Checklist + +- [ ] User can register after donation +- [ ] Admin receives signup notification +- [ ] Admin can approve/deny members +- [ ] Approved users receive confirmation email +- [ ] Users can log in +- [ ] Download tracking works (IP-based) +- [ ] Paywall shows after 1 download +- [ ] Members get unlimited downloads +- [ ] Contact form only works for members +- [ ] Email address hidden for non-members +- [ ] Testimonials page paywalled +- [ ] Members can submit testimonials +- [ ] Admin can moderate testimonials + diff --git a/MEMBERSHIP_SETUP.md b/MEMBERSHIP_SETUP.md new file mode 100644 index 0000000..714d38b --- /dev/null +++ b/MEMBERSHIP_SETUP.md @@ -0,0 +1,122 @@ +# Membership System Setup Guide + +## Overview + +This membership system provides: +- User registration and authentication +- IP-based download limiting (1 free download, unlimited for members) +- Paywall for library downloads, contact form, and testimonials +- Admin panel for membership approval +- Buy Me a Coffee integration ($5 minimum donation) + +## Prerequisites + +1. Python 3.7+ with pip +2. Flask and other dependencies (see requirements.txt) +3. SMTP server access for email notifications (optional but recommended) + +## Installation + +1. Install Python dependencies: +```bash +cd /root/hydro-sterile +pip3 install -r requirements.txt +``` + +2. Set environment variables: +```bash +export ADMIN_PASSWORD="your-very-secure-password-here" +export ADMIN_EMAIL="indiana.holmes8@gmail.com" +export JWT_SECRET="$(openssl rand -hex 32)" +export MEMBERSHIP_PORT=5001 + +# Optional: SMTP configuration for email notifications +export SMTP_HOST="smtp.gmail.com" +export SMTP_PORT=587 +export SMTP_USER="your-email@gmail.com" +export SMTP_PASS="your-app-password" +``` + +3. Create a systemd service or run manually: +```bash +python3 /root/hydro-sterile/scripts/membership_api.py +``` + +## Configuration + +### API Endpoints + +The membership API runs on port 5001 by default. Update the API_BASE URL in: +- `/root/hydro-sterile/static/js/membership.js` (line 4) +- `/root/hydro-sterile/layouts/admin/single.html` (line 186) + +For production, change from `http://10.30.20.243:5001` to your production domain. + +### Admin Panel + +Access the admin panel at `/admin/` on your Hugo site. + +The admin password is set via the `ADMIN_PASSWORD` environment variable. Use a strong password! + +### Database + +The SQLite database is automatically created at: +`/root/hydro-sterile/data/members.db` + +Backup this file regularly! + +## User Flow + +1. User visits paywalled page (library, contact, testimonials) +2. Non-members see paywall message directing to membership page +3. User completes Buy Me a Coffee donation ($5 minimum) +4. User signs up with email/password on membership page +5. System sends email notification to admin +6. Admin logs into admin panel and approves/denies membership +7. Approved user receives confirmation email +8. User can now log in and access all paywalled content + +## Security Notes + +- Admin password should be very strong (20+ characters recommended) +- JWT_SECRET should be a random string (use `openssl rand -hex 32`) +- Never commit ADMIN_PASSWORD or JWT_SECRET to version control +- Database file should have restricted permissions (600) +- SMTP credentials should use app passwords, not main account password + +## Email Configuration + +For Gmail: +1. Enable 2-factor authentication +2. Generate an app password +3. Use the app password as SMTP_PASS + +## Troubleshooting + +### API not starting +- Check port 5001 is not in use: `netstat -tuln | grep 5001` +- Check Python dependencies: `pip3 list | grep -E "flask|bcrypt|jwt"` + +### Database errors +- Ensure `/root/hydro-sterile/data/` directory exists and is writable +- Check file permissions: `chmod 600 /root/hydro-sterile/data/members.db` + +### Email not sending +- Check SMTP credentials +- Verify SMTP_HOST and SMTP_PORT are correct +- Check firewall allows outbound SMTP connections +- Email sending failures won't break the system, but admin won't get notifications + +### Frontend not connecting to API +- Check API_BASE URL in membership.js matches your server +- Check CORS settings in membership_api.py +- Check browser console for errors +- Verify API is running: `curl http://localhost:5001/api/health` + +## Maintenance + +- Regularly backup `/root/hydro-sterile/data/members.db` +- Monitor API logs for errors +- Review pending memberships regularly +- Clean up old sessions periodically (optional, sessions expire automatically) + diff --git a/README.md b/README.md new file mode 100644 index 0000000..25a7cf7 --- /dev/null +++ b/README.md @@ -0,0 +1,29 @@ +# Pure Hydroponics Site + +## Setup + +1. **Install Dependencies:** + ```bash + npm install + ``` + +2. **Run Development Server:** + ```bash + # Uses local hugo binary via hugo-bin + ./node_modules/.bin/hugo server + ``` + +3. **Build for Production:** + ```bash + ./node_modules/.bin/hugo --minify + ``` + +## Stack +- **Hugo Extended** (v0.152+) +- **Tailwind CSS** (v4.0) via PostCSS +- **Alpine.js** for interactions + +## Deployment +- **Output Directory:** `public/` +- **Cloudflare Pages:** Set build command to `npm run build` (add script to package.json) or just run hugo. +- **Netlify:** Similar setup. diff --git a/RECOVERY.md b/RECOVERY.md new file mode 100644 index 0000000..3713e9c --- /dev/null +++ b/RECOVERY.md @@ -0,0 +1,36 @@ +# Website Service Management Guide + +This document lists the services that run the website and how to manage them. + +## Services + +| Service Name | Description | Port | +|--------------|-------------|------| +| `hugo-hydrolord.service` | Main Hugo Website | 7575 | +| `hugo-membership-api.service` | Membership & Static API | 5001 | +| `hugo-chat-api.service` | AI Chat API | 5000 | +| `hugo-blog-generator.timer` | Auto Blog Generator | N/A | + +## Management Commands + +### Check Status +```bash +systemctl status \"hugo-*\" +``` + +### View Logs +```bash +# Website logs +journalctl -u hugo-hydrolord.service -f + +# API logs +journalctl -u hugo-membership-api.service -f +``` + +### Restart All +```bash +systemctl restart hugo-hydrolord.service hugo-membership-api.service hugo-chat-api.service +``` + +## Persistence +All services are set to `Restart=always` and are `enabled` to start at boot automatically. diff --git a/archetypes/default.md b/archetypes/default.md new file mode 100644 index 0000000..0d5eebd --- /dev/null +++ b/archetypes/default.md @@ -0,0 +1,5 @@ +--- +date: '{{ .Date }}' +draft: true +title: '{{ replace .File.ContentBaseName "-" " " | title }}' +--- diff --git a/assets/css/main.css b/assets/css/main.css new file mode 100644 index 0000000..4287595 --- /dev/null +++ b/assets/css/main.css @@ -0,0 +1,21 @@ +@import "tailwindcss"; + +@theme { + --color-primary: #059669; + --color-secondary: #0ea5e9; + --color-surface: #1f2937; + --color-background: #111827; + --font-sans: 'Inter', 'system-ui', 'sans-serif'; +} + +@layer base { + body { + background-color: var(--color-background); + color: #e5e7eb; + font-family: var(--font-sans); + } + h1, h2, h3, h4 { + font-weight: 700; + color: #f9fafb; + } +} \ No newline at end of file diff --git a/content/about.md b/content/about.md new file mode 100644 index 0000000..a3aa4bf --- /dev/null +++ b/content/about.md @@ -0,0 +1,40 @@ +--- +title: "About Me" +date: 2024-12-24T12:00:00Z +draft: false +description: "Sterile hydro nerd with 20 years of dialing in award-winning cannabis." +--- + +# About Me + +I'm 35, 20 years deep growing legal weed, and I'm a laid-back stoner who treats hydro like a lab. Every photo here is cannabis because that's my playground—clean water, clean salts, no funk. + +Most growers swear you need microbes and that root rot is just part of the game. + +**They're wrong.** + +I've spent two decades perfecting something most people think is impossible: **completely sterile hydroponics**. No bacteria. No "beneficials." No pathogens. Just roots ripping through clean solution. + +Stop listening to randoms on Reddit. You won't find this on YouTube or in standard grow guides. What I teach are the protocols that make buds so clean people ask if they're even real. + +## My Philosophy: No Excuses Hydro or Go Home + +**No excuses hydro or go home.** Precision beats fairy tales. If something drifts, we fix the variable and keep moving. + +Hydroponics isn't about excuses. It's about control—eliminating every variable that can go wrong so your plants hit their genetic ceiling. + +My background is in clinical microbiology. In the lab, contamination equals failure. I pulled that same mindset into growing and watched plants thrive once every biological hitchhiker was gone. + +The methods I use come from medical sterility protocols. They aren't theories. They're battle-tested and tuned over thousands of hours. + +## Two Decades of Results + +Over the last 20 years, I've helped small home setups, basement grows, and big commercial rooms spanning thousands of square feet. Large gardens, small gardens—I dial them all in. + +I've watched basement growers ditch slime and harvest jars that make commercial facilities jealous. I've seen large operations eliminate whole categories of problems they thought were "just part of the business" and start pulling competition-grade flower that sells itself. + +When commercial operations need results, they call me. When home growers want to level up, they call me. + +If you follow the sterile protocols I've honed over the past two decades, you **will** see the difference: no root rot, faster growth, bigger yields, clean white roots, and plants that aren't fighting anything but the trellis. + +No excuses. Award-winning results. Ready? diff --git a/content/admin.md b/content/admin.md new file mode 100644 index 0000000..3ddbd1e --- /dev/null +++ b/content/admin.md @@ -0,0 +1,7 @@ +--- +title: "Admin Panel" +date: 2024-12-24T12:00:00Z +draft: false +layout: "admin" +--- + diff --git a/content/albums/_index.md b/content/albums/_index.md new file mode 100644 index 0000000..2c06fcc --- /dev/null +++ b/content/albums/_index.md @@ -0,0 +1,4 @@ +--- +title: "Photo Albums" +description: "A visual journey through sterile hydroponic systems." +--- diff --git a/content/albums/commercial-lettuce.md b/content/albums/commercial-lettuce.md new file mode 100644 index 0000000..041b193 --- /dev/null +++ b/content/albums/commercial-lettuce.md @@ -0,0 +1,30 @@ +--- +title: "Commercial NFT Lettuce" +description: "A 2000 sq ft facility designed for zero-waste production." +cover: "/images/IMG_2871.jpeg" +images: + - url: "/images/IMG_9463.jpeg" + caption: "IMG 9463.jpeg" + - url: "/images/69844719615__6435DF53-250E-4D10-B453-4DBE2ECC44F1.jpeg" + caption: "69844719615 6435DF53 250E 4D10 B453 4DBE2ECC44F1.jpeg" + - url: "/images/IMG_0081.jpeg" + caption: "IMG 0081.jpeg" + - url: "/images/IMG_9313.jpeg" + caption: "IMG 9313.jpeg" + - url: "/images/IMG_8923.jpeg" + caption: "IMG 8923.jpeg" + - url: "/images/IMG_8405.jpeg" + caption: "IMG 8405.jpeg" + - url: "/images/IMG_9198.jpeg" + caption: "IMG 9198.jpeg" + - url: "/images/IMG_1859.jpeg" + caption: "IMG 1859.jpeg" + - url: "/images/IMG_1436.jpeg" + caption: "IMG 1436.jpeg" + - url: "/images/433320B3-7D36-4965-9E58-DEC07BE6827C.jpg" + caption: "433320B3 7D36 4965 9E58 DEC07BE6827C.jpg" + - url: "/images/IMG_1837.jpeg" + caption: "IMG 1837.jpeg" + - url: "/images/IMG_8894.jpeg" + caption: "IMG 8894.jpeg" +--- diff --git a/content/albums/medical-dwc.md b/content/albums/medical-dwc.md new file mode 100644 index 0000000..890e5d9 --- /dev/null +++ b/content/albums/medical-dwc.md @@ -0,0 +1,30 @@ +--- +title: "Medical RDWC Build" +description: "High-precision Recirculating Deep Water Culture for medicinal crops." +cover: "/images/IMG_0165 2.jpeg" +images: + - url: "/images/IMG_1439.jpeg" + caption: "IMG 1439.jpeg" + - url: "/images/IMG_1436.jpeg" + caption: "IMG 1436.jpeg" + - url: "/images/IMG_0081.jpeg" + caption: "IMG 0081.jpeg" + - url: "/images/IMG_1534.jpeg" + caption: "IMG 1534.jpeg" + - url: "/images/IMG_8377.jpeg" + caption: "IMG 8377.jpeg" + - url: "/images/IMG_1126.jpeg" + caption: "IMG 1126.jpeg" + - url: "/images/IMG_7684.jpeg" + caption: "IMG 7684.jpeg" + - url: "/images/IMG_2838.jpeg" + caption: "IMG 2838.jpeg" + - url: "/images/IMG_9328 3.jpeg" + caption: "IMG 9328 3.jpeg" + - url: "/images/IMG_9328.jpeg" + caption: "IMG 9328.jpeg" + - url: "/images/IMG_6475.jpeg" + caption: "IMG 6475.jpeg" + - url: "/images/E142B0A8-8F28-4288-9B10-770518A3C43B.webp" + caption: "E142B0A8 8F28 4288 9B10 770518A3C43B.webp" +--- diff --git a/content/albums/my-garden.md b/content/albums/my-garden.md new file mode 100644 index 0000000..bf39915 --- /dev/null +++ b/content/albums/my-garden.md @@ -0,0 +1,338 @@ +--- +title: "My Hydroponic Garden" +date: 2024-03-25T12:00:00-05:00 +description: "A comprehensive gallery of my hydroponic plants, setups, and results." +images: + - url: "/images/13AC76BC-B45E-42F1-A9C3-A9E961B3C496.webp" + caption: "13AC76BC B45E 42F1 A9C3 A9E961B3C496.webp" + - url: "/images/20012743-FFB5-4AFB-9DE6-5BE122236EA8.webp" + caption: "20012743 FFB5 4AFB 9DE6 5BE122236EA8.webp" + - url: "/images/433320B3-7D36-4965-9E58-DEC07BE6827C.jpg" + caption: "433320B3 7D36 4965 9E58 DEC07BE6827C.jpg" + - url: "/images/54d957ebf6c9447c8c3214fe371c303a.MOV" + caption: "54d957ebf6c9447c8c3214fe371c303a.MOV" + - url: "/images/64634090387__02F28F99-0128-4628-9F5A-AA3BF5043B30.jpeg" + caption: "64634090387 02F28F99 0128 4628 9F5A AA3BF5043B30.jpeg" + - url: "/images/64792744957__B54FD5A8-4237-4141-8090-E2BED845456C.jpeg" + caption: "64792744957 B54FD5A8 4237 4141 8090 E2BED845456C.jpeg" + - url: "/images/65331164721__D4F8AED9-9B4A-4E75-905D-5027A2D8E440.jpeg" + caption: "65331164721 D4F8AED9 9B4A 4E75 905D 5027A2D8E440.jpeg" + - url: "/images/65804214832__CCBE50A0-8F17-4B36-817F-D1FBBA1A4B3F.jpeg" + caption: "65804214832 CCBE50A0 8F17 4B36 817F D1FBBA1A4B3F.jpeg" + - url: "/images/65804215588__E8E13980-3188-4726-AFE4-0C038A6422C8.jpeg" + caption: "65804215588 E8E13980 3188 4726 AFE4 0C038A6422C8.jpeg" + - url: "/images/69844719615__6435DF53-250E-4D10-B453-4DBE2ECC44F1.jpeg" + caption: "69844719615 6435DF53 250E 4D10 B453 4DBE2ECC44F1.jpeg" + - url: "/images/69844720921__44164E36-77D1-4DBA-BD06-26F8BF86C541.jpeg" + caption: "69844720921 44164E36 77D1 4DBA BD06 26F8BF86C541.jpeg" + - url: "/images/707F7B39-B98D-44CC-AE45-9EF2126F5887.webp" + caption: "707F7B39 B98D 44CC AE45 9EF2126F5887.webp" + - url: "/images/7D196C8E-A924-4600-922D-52FDB61C41D2.png" + caption: "7D196C8E A924 4600 922D 52FDB61C41D2.png" + - url: "/images/BF9E2340-832A-49CD-BED3-4A1801C35E97.jpg" + caption: "BF9E2340 832A 49CD BED3 4A1801C35E97.jpg" + - url: "/images/BF9F1085-AB99-47FA-97E4-56B5658E42FE.webp" + caption: "BF9F1085 AB99 47FA 97E4 56B5658E42FE.webp" + - url: "/images/C56C173F-1C51-4F3D-9E86-AA24B06EEC19.webp" + caption: "C56C173F 1C51 4F3D 9E86 AA24B06EEC19.webp" + - url: "/images/ComfyUI_00012_.png" + caption: "ComfyUI 00012 .png" + - url: "/images/ComfyUI_00013_.png" + caption: "ComfyUI 00013 .png" + - url: "/images/ComfyUI_00014_.png" + caption: "ComfyUI 00014 .png" + - url: "/images/ComfyUI_00015_.png" + caption: "ComfyUI 00015 .png" + - url: "/images/ComfyUI_00023_.png" + caption: "ComfyUI 00023 .png" + - url: "/images/D57F541F-52FA-4B45-9716-758F67F55D79.webp" + caption: "D57F541F 52FA 4B45 9716 758F67F55D79.webp" + - url: "/images/E142B0A8-8F28-4288-9B10-770518A3C43B.webp" + caption: "E142B0A8 8F28 4288 9B10 770518A3C43B.webp" + - url: "/images/E8211B06-A9E4-4A51-A941-757CE191BC57.jpg" + caption: "E8211B06 A9E4 4A51 A941 757CE191BC57.jpg" + - url: "/images/E9DFB924-05F8-4079-9AFD-269567B514D8.jpg" + caption: "E9DFB924 05F8 4079 9AFD 269567B514D8.jpg" + - url: "/images/IMG_0009.jpeg" + caption: "IMG 0009.jpeg" + - url: "/images/IMG_0081.jpeg" + caption: "IMG 0081.jpeg" + - url: "/images/IMG_0113.jpeg" + caption: "IMG 0113.jpeg" + - url: "/images/IMG_0165 2.jpeg" + caption: "IMG 0165 2.jpeg" + - url: "/images/IMG_0165.jpeg" + caption: "IMG 0165.jpeg" + - url: "/images/IMG_0172.jpeg" + caption: "IMG 0172.jpeg" + - url: "/images/IMG_0179 2.jpeg" + caption: "IMG 0179 2.jpeg" + - url: "/images/IMG_0179 3.jpeg" + caption: "IMG 0179 3.jpeg" + - url: "/images/IMG_0179 4.jpeg" + caption: "IMG 0179 4.jpeg" + - url: "/images/IMG_0179.jpeg" + caption: "IMG 0179.jpeg" + - url: "/images/IMG_0332.jpeg" + caption: "IMG 0332.jpeg" + - url: "/images/IMG_0333.jpeg" + caption: "IMG 0333.jpeg" + - url: "/images/IMG_0334.jpeg" + caption: "IMG 0334.jpeg" + - url: "/images/IMG_0599.jpeg" + caption: "IMG 0599.jpeg" + - url: "/images/IMG_0784.jpeg" + caption: "IMG 0784.jpeg" + - url: "/images/IMG_0801.jpeg" + caption: "IMG 0801.jpeg" + - url: "/images/IMG_0904.jpeg" + caption: "IMG 0904.jpeg" + - url: "/images/IMG_0CFF7F7D-C8F8-4051-BCA5-B98C40D9D387.jpeg" + caption: "IMG 0CFF7F7D C8F8 4051 BCA5 B98C40D9D387.jpeg" + - url: "/images/IMG_1011 2.HEIC" + caption: "IMG 1011 2.HEIC" + - url: "/images/IMG_1011.HEIC" + caption: "IMG 1011.HEIC" + - url: "/images/IMG_1020 2.jpeg" + caption: "IMG 1020 2.jpeg" + - url: "/images/IMG_1020.jpeg" + caption: "IMG 1020.jpeg" + - url: "/images/IMG_1021.jpeg" + caption: "IMG 1021.jpeg" + - url: "/images/IMG_1058.jpeg" + caption: "IMG 1058.jpeg" + - url: "/images/IMG_1062 2.jpeg" + caption: "IMG 1062 2.jpeg" + - url: "/images/IMG_1062.jpeg" + caption: "IMG 1062.jpeg" + - url: "/images/IMG_1104 2.jpeg" + caption: "IMG 1104 2.jpeg" + - url: "/images/IMG_1104.jpeg" + caption: "IMG 1104.jpeg" + - url: "/images/IMG_1126.jpeg" + caption: "IMG 1126.jpeg" + - url: "/images/IMG_1160.jpeg" + caption: "IMG 1160.jpeg" + - url: "/images/IMG_1435.jpeg" + caption: "IMG 1435.jpeg" + - url: "/images/IMG_1436.jpeg" + caption: "IMG 1436.jpeg" + - url: "/images/IMG_1439.jpeg" + caption: "IMG 1439.jpeg" + - url: "/images/IMG_1534.jpeg" + caption: "IMG 1534.jpeg" + - url: "/images/IMG_1767.jpeg" + caption: "IMG 1767.jpeg" + - url: "/images/IMG_1768.jpeg" + caption: "IMG 1768.jpeg" + - url: "/images/IMG_1837.jpeg" + caption: "IMG 1837.jpeg" + - url: "/images/IMG_1859.jpeg" + caption: "IMG 1859.jpeg" + - url: "/images/IMG_1864.jpeg" + caption: "IMG 1864.jpeg" + - url: "/images/IMG_2822.jpeg" + caption: "IMG 2822.jpeg" + - url: "/images/IMG_2826.jpeg" + caption: "IMG 2826.jpeg" + - url: "/images/IMG_2828.jpeg" + caption: "IMG 2828.jpeg" + - url: "/images/IMG_2835.jpeg" + caption: "IMG 2835.jpeg" + - url: "/images/IMG_2838.jpeg" + caption: "IMG 2838.jpeg" + - url: "/images/IMG_2870.jpeg" + caption: "IMG 2870.jpeg" + - url: "/images/IMG_2871.jpeg" + caption: "IMG 2871.jpeg" + - url: "/images/IMG_3528.jpeg" + caption: "IMG 3528.jpeg" + - url: "/images/IMG_4029.jpeg" + caption: "IMG 4029.jpeg" + - url: "/images/IMG_4674.JPG" + caption: "IMG 4674.JPG" + - url: "/images/IMG_5008.jpeg" + caption: "IMG 5008.jpeg" + - url: "/images/IMG_5218.jpeg" + caption: "IMG 5218.jpeg" + - url: "/images/IMG_5261.jpeg" + caption: "IMG 5261.jpeg" + - url: "/images/IMG_5262.jpeg" + caption: "IMG 5262.jpeg" + - url: "/images/IMG_5374.jpeg" + caption: "IMG 5374.jpeg" + - url: "/images/IMG_5698.jpeg" + caption: "IMG 5698.jpeg" + - url: "/images/IMG_6108.jpeg" + caption: "IMG 6108.jpeg" + - url: "/images/IMG_6180.jpeg" + caption: "IMG 6180.jpeg" + - url: "/images/IMG_6389.jpeg" + caption: "IMG 6389.jpeg" + - url: "/images/IMG_6426.jpeg" + caption: "IMG 6426.jpeg" + - url: "/images/IMG_6475.jpeg" + caption: "IMG 6475.jpeg" + - url: "/images/IMG_6477.jpeg" + caption: "IMG 6477.jpeg" + - url: "/images/IMG_6501.jpeg" + caption: "IMG 6501.jpeg" + - url: "/images/IMG_6574.jpeg" + caption: "IMG 6574.jpeg" + - url: "/images/IMG_6919.jpeg" + caption: "IMG 6919.jpeg" + - url: "/images/IMG_6920.jpeg" + caption: "IMG 6920.jpeg" + - url: "/images/IMG_7077.jpeg" + caption: "IMG 7077.jpeg" + - url: "/images/IMG_7078.jpeg" + caption: "IMG 7078.jpeg" + - url: "/images/IMG_7159.jpeg" + caption: "IMG 7159.jpeg" + - url: "/images/IMG_7374.jpeg" + caption: "IMG 7374.jpeg" + - url: "/images/IMG_7447.jpeg" + caption: "IMG 7447.jpeg" + - url: "/images/IMG_7677.jpeg" + caption: "IMG 7677.jpeg" + - url: "/images/IMG_7684.jpeg" + caption: "IMG 7684.jpeg" + - url: "/images/IMG_7712.JPG" + caption: "IMG 7712.JPG" + - url: "/images/IMG_7999.jpeg" + caption: "IMG 7999.jpeg" + - url: "/images/IMG_8093.jpeg" + caption: "IMG 8093.jpeg" + - url: "/images/IMG_8095.jpeg" + caption: "IMG 8095.jpeg" + - url: "/images/IMG_8377.jpeg" + caption: "IMG 8377.jpeg" + - url: "/images/IMG_8405.jpeg" + caption: "IMG 8405.jpeg" + - url: "/images/IMG_8483.jpeg" + caption: "IMG 8483.jpeg" + - url: "/images/IMG_8586.jpeg" + caption: "IMG 8586.jpeg" + - url: "/images/IMG_8594.jpeg" + caption: "IMG 8594.jpeg" + - url: "/images/IMG_8894.jpeg" + caption: "IMG 8894.jpeg" + - url: "/images/IMG_8923.jpeg" + caption: "IMG 8923.jpeg" + - url: "/images/IMG_8926.jpeg" + caption: "IMG 8926.jpeg" + - url: "/images/IMG_9103.jpeg" + caption: "IMG 9103.jpeg" + - url: "/images/IMG_9104.jpeg" + caption: "IMG 9104.jpeg" + - url: "/images/IMG_9112.jpeg" + caption: "IMG 9112.jpeg" + - url: "/images/IMG_9129.jpeg" + caption: "IMG 9129.jpeg" + - url: "/images/IMG_9135.jpeg" + caption: "IMG 9135.jpeg" + - url: "/images/IMG_9166.jpeg" + caption: "IMG 9166.jpeg" + - url: "/images/IMG_9167.jpeg" + caption: "IMG 9167.jpeg" + - url: "/images/IMG_9195.jpeg" + caption: "IMG 9195.jpeg" + - url: "/images/IMG_9196 2.jpeg" + caption: "IMG 9196 2.jpeg" + - url: "/images/IMG_9196 3.jpeg" + caption: "IMG 9196 3.jpeg" + - url: "/images/IMG_9196 4.jpeg" + caption: "IMG 9196 4.jpeg" + - url: "/images/IMG_9196.jpeg" + caption: "IMG 9196.jpeg" + - url: "/images/IMG_9198.jpeg" + caption: "IMG 9198.jpeg" + - url: "/images/IMG_9202.MOV" + caption: "IMG 9202.MOV" + - url: "/images/IMG_9242.jpeg" + caption: "IMG 9242.jpeg" + - url: "/images/IMG_9255.jpeg" + caption: "IMG 9255.jpeg" + - url: "/images/IMG_9269.jpeg" + caption: "IMG 9269.jpeg" + - url: "/images/IMG_9308.jpeg" + caption: "IMG 9308.jpeg" + - url: "/images/IMG_9313.jpeg" + caption: "IMG 9313.jpeg" + - url: "/images/IMG_9316.jpeg" + caption: "IMG 9316.jpeg" + - url: "/images/IMG_9328 2.jpeg" + caption: "IMG 9328 2.jpeg" + - url: "/images/IMG_9328 3.jpeg" + caption: "IMG 9328 3.jpeg" + - url: "/images/IMG_9328.jpeg" + caption: "IMG 9328.jpeg" + - url: "/images/IMG_9335.jpeg" + caption: "IMG 9335.jpeg" + - url: "/images/IMG_9357.PNG" + caption: "IMG 9357.PNG" + - url: "/images/IMG_9367.jpeg" + caption: "IMG 9367.jpeg" + - url: "/images/IMG_9385.jpeg" + caption: "IMG 9385.jpeg" + - url: "/images/IMG_9410.jpeg" + caption: "IMG 9410.jpeg" + - url: "/images/IMG_9411.jpeg" + caption: "IMG 9411.jpeg" + - url: "/images/IMG_9413.jpeg" + caption: "IMG 9413.jpeg" + - url: "/images/IMG_9419.jpeg" + caption: "IMG 9419.jpeg" + - url: "/images/IMG_9428.jpeg" + caption: "IMG 9428.jpeg" + - url: "/images/IMG_9429.jpeg" + caption: "IMG 9429.jpeg" + - url: "/images/IMG_9432.jpeg" + caption: "IMG 9432.jpeg" + - url: "/images/IMG_9446.jpeg" + caption: "IMG 9446.jpeg" + - url: "/images/IMG_9448.jpeg" + caption: "IMG 9448.jpeg" + - url: "/images/IMG_9449.jpeg" + caption: "IMG 9449.jpeg" + - url: "/images/IMG_9454.jpeg" + caption: "IMG 9454.jpeg" + - url: "/images/IMG_9463.jpeg" + caption: "IMG 9463.jpeg" + - url: "/images/IMG_9519.jpeg" + caption: "IMG 9519.jpeg" + - url: "/images/IMG_9520.jpeg" + caption: "IMG 9520.jpeg" + - url: "/images/IMG_9564.jpeg" + caption: "IMG 9564.jpeg" + - url: "/images/IMG_9612 2.jpeg" + caption: "IMG 9612 2.jpeg" + - url: "/images/IMG_9612.jpeg" + caption: "IMG 9612.jpeg" + - url: "/images/IMG_9613 2.jpeg" + caption: "IMG 9613 2.jpeg" + - url: "/images/IMG_9613 3.jpeg" + caption: "IMG 9613 3.jpeg" + - url: "/images/IMG_9613.jpeg" + caption: "IMG 9613.jpeg" + - url: "/images/IMG_9768.jpeg" + caption: "IMG 9768.jpeg" + - url: "/images/IMG_9769.jpeg" + caption: "IMG 9769.jpeg" + - url: "/images/Screenshot 2024-03-25 195708.png" + caption: "Screenshot 2024 03 25 195708.png" + - url: "/images/WBYS8305.JPEG" + caption: "WBYS8305.JPEG" + - url: "/images/f130260032.jpeg" + caption: "f130260032.jpeg" + - url: "/images/f130303808.jpeg" + caption: "f130303808.jpeg" + - url: "/images/f131874624.jpeg" + caption: "f131874624.jpeg" + - url: "/images/f139364160.jpeg" + caption: "f139364160.jpeg" + - url: "/images/f153047488.jpeg" + caption: "f153047488.jpeg" + - url: "/images/f157200512.jpeg" + caption: "f157200512.jpeg" +--- diff --git a/content/blog/_index.md b/content/blog/_index.md new file mode 100644 index 0000000..0a033ee --- /dev/null +++ b/content/blog/_index.md @@ -0,0 +1,4 @@ +--- +title: "Grow Journal" +description: "Notes from the field." +--- diff --git a/content/blog/post-1.md b/content/blog/post-1.md new file mode 100644 index 0000000..2b0569a --- /dev/null +++ b/content/blog/post-1.md @@ -0,0 +1,12 @@ +--- +title: "Why I Stopped Using Organic Nutrients in Hydro" +date: 2024-01-10T10:00:00Z +image: "/images/69844720921__44164E36-77D1-4DBA-BD06-26F8BF86C541.jpeg" +summary: "Biofilm, clogs, and unstable pH. Why organics belong in soil, not in your reservoir." +--- + +I wanted to believe in "organic hydroponics." Clean water plus natural nutrients sounded dreamy. After three straight failures, I had to face the music. + +Organic nutrients are liquid compost. In soil, microbes break them down. In a sterile reservoir they just turn to slime—clogged emitters, coated roots, suffocated plants. + +Switching back to mineral salts wasn't giving up; it was waking up. Yields doubled and cleaning went from hours to minutes. diff --git a/content/blog/post-10.md b/content/blog/post-10.md new file mode 100644 index 0000000..9e19a1d --- /dev/null +++ b/content/blog/post-10.md @@ -0,0 +1,22 @@ +--- +title: "Alright folks, let’s talk hydro! After two decades wrestling with cannabis in sterile environment..." +date: 2025-12-26T10:50:30Z +image: "/images/IMG_1534.jpeg" +summary: "First off – “sterile” matters when we're talking hydroponics with cannabis because root health IS everything and disease spreads fast in water based s..." +--- + +First off – “sterile” matters when we're talking hydroponics with cannabis because root health IS everything and disease spreads fast in water based systems without proper controls.. We want to minimize the microbial load so plants focus on *growing*, not fighting stuff off constantly . Think consistent sterilization of reservoirs, tubing & grow media. + +Let’s break down some common sterile-friendly options: +* **Deep Water Culture (DWC):** The simplest! Roots dangle in oxygenated nutrient solution; good for beginners and scaling up is relatively easy with multiple buckets/reservoirs.. Sterilization? Easy – fully replace the reservoir weekly, or biweekly depending on plant size. Keep that air pump running strong though - dissolved O2 critical here +* **Recirculating Deep Water Culture (RDWC):** DWC but linked together! One central oxygenated nutrient res feeds multiple smaller containers with individual net pots.. More complex setup and requires a little more monitoring, especially for pH drift across the system. Sterilization is still largely reservoir based – though you’ll want to flush all connecting lines weekly when changing out solution +* **Drain-to-Waste (DTW):** My personal workhorse! Nutrient flows *through*, not recirculates.. Less risk of disease buildup because old nutrient gets flushed with each watering cycle. You need a bit more nute and water, but the consistency in uptake is worth it for consistent results . Sterilization focuses on initial reservoir prep & ensuring clean tubing/media +* **Rockwool Slab / Coco Coir Systems:** Technically *hydroponic*, even though they use an inert growing medium.. Excellent control over root zone environment. Requires careful pH and EC management, but are very forgiving – especially coco coirs which can handle minor fluctuations much better than rock wool . Sterilization of the slabs/bricks before planting is key! + +**Troubleshooting 101:** Don’t panic when things go sideways (and they will!). Here's what I see most: +* **Root Rot**: Usually caused by Pythium or Fusarium. Often from low dissolved oxygen, high temperatures and not enough sterilization.. Check your water temp; aim for under 75F/24C , bump up aeration & replace that solution! Consider a hydrogen peroxide addition to the reservoir (3% H₂O₂, ~1 tsp per gallon) +* **pH Drift:** Common in recirculating systems. Calibrate you pH meter regularly, and check it at multiple points within your system.. Check for algae growth too – light hitting nutrient solutions encourages them & throws off readings! + ***Nutrient Lockout**: Happens when salts build up or a specific element is unavailable to the plant . Flush with plain PH balanced water. If that doesn’t work, reassess you EC/PPM and make sure your ratios are correct for what growth stage they're in + + +Bottom line? Sterile hydroponics takes some attention but yields amazing results! Don’t be afraid experiment & document everything - it pays dividends later down the road when scaling up or optimizing a system. diff --git a/content/blog/post-11.md b/content/blog/post-11.md new file mode 100644 index 0000000..d678bf9 --- /dev/null +++ b/content/blog/post-11.md @@ -0,0 +1,30 @@ +--- +title: "Alright, let’s talk roots – specifically how getting oxygen *to* those cannabis roots is the sing..." +date: 2025-12-26T16:48:36Z +image: "/images/IMG_8377.jpeg" +summary: "Everyone focuses so much on lights and nutrient formulas – crucial stuff sure - but if your root zone is suffocating in anaerobic conditions you're le..." +--- + +Everyone focuses so much on lights and nutrient formulas – crucial stuff sure - but if your root zone is suffocating in anaerobic conditions you're leaving serious potential untapped.. Cannabis *loves* oxygen, especially when it’s pulling nutrients directly from the water column instead of sifting through soil. + + + +**So how do we maximize that vital O2? Here are a few techniques I swear by:** + +1.**Consistent Water Temperature is Key**: Aim for between 68-74°F (around ~20 Celsius). Warmer temps hold less dissolved oxygen, colder slows nutrient uptake.. A simple aquarium heater with an inline thermostat will do the trick. Don’t cheap out here; stability matters more than fancy features + +2.**Air Stone Power – But Size Matters**: Everyone uses air stones in DWC! Great start,. The mistake I see a lot is using too *few* or ones that are clogged.. Get enough to create consistent, but not violent turbulence. Think gentle bubbling throughout the entire reservoir - it should look like its lightly simmering + +3.**Hydrogen Peroxide (H2O2) – A Sterile Boost**: I use H₂0₂, at a low dose of 1-2 tsp per gallon every few days,. It breaks down into water and oxygen, giving those roots an extra boost. Plus helps keeps the system cleaner reducing root rot.. Just don’t overdo it - too much can shock them! + +4.**Water Change Frequency**: A lot depends on your plant size & nutrient uptake but I always aim for a complete reservoir change every 7-10 days, especially during peak bloom,. This replenishes oxygen and prevents salt buildup. Don't just top off – *replace* the water completely.. It’s more work upfront that yields big results + +5.**Root Mass Inspection:** When you do your changes pull out some roots! Healthy cannabis root are bright white & fluffy - a sign of healthy O2 uptake . Brown, slimy mush is an indicator something'a amiss + + + +These aren’t groundbreaking secrets,. but they *are* often overlooked. Sterile hydroponics isn't just about the nutrients; it requires active management to create and maintain that ideal oxygen-rich environment for your cannabis roots.. Nail this part down ,and you will see a significant difference in vigor, growth rate & ultimately – bud density! + + + +I’ll dive deeper into sterile system sanitation next time. Until then happy growing folks!. diff --git a/content/blog/post-2.md b/content/blog/post-2.md new file mode 100644 index 0000000..4b39298 --- /dev/null +++ b/content/blog/post-2.md @@ -0,0 +1,12 @@ +--- +title: "The Truth About H2O2 (Hydrogen Peroxide)" +date: 2024-02-15T10:00:00Z +image: "/images/IMG_9769.jpeg" +summary: "It's a powerful tool, but it's not a long-term solution. Here is why." +--- + +Hydrogen Peroxide is the flamethrower of the hydro world. It kills everything—good and bad—on contact. + +Its half-life in a nutrient solution is tiny. Within hours it's just water and oxygen. If you've got a dirty pump or line feeding pathogens, H2O2 will clear it for a moment and the colony will be back by morning. + +I use H2O2 for *cleaning equipment between runs*. For daily maintenance, I prefer Hypochlorous Acid. diff --git a/content/blog/post-3.md b/content/blog/post-3.md new file mode 100644 index 0000000..07778c9 --- /dev/null +++ b/content/blog/post-3.md @@ -0,0 +1,12 @@ +--- +title: "My Battle with Pythium (and How I Won)" +date: 2024-03-01T10:00:00Z +image: "/images/IMG_6475.jpeg" +summary: "Root rot isn't a death sentence if you catch it early and treat it aggressively." +--- + +It started as slight discoloration on the root tips. I ignored it. Two days later the whole reservoir smelled like a swamp. Pythium moves fast. + +My mistake was trusting "beneficial bacteria" in a system running too warm (75°F). Pythium loves warm, low-oxygen soup. + +The fix? A water chiller. Dropping to 66°F was the turning point. Cold water holds more oxygen, and Pythium hates that. diff --git a/content/blog/post-4.md b/content/blog/post-4.md new file mode 100644 index 0000000..12ee96c --- /dev/null +++ b/content/blog/post-4.md @@ -0,0 +1,13 @@ +--- +title: "VPD Explained for Complete Beginners" +date: 2024-03-20T10:00:00Z +image: "/images/IMG_9449.jpeg" +summary: "Temperature and humidity are connected. Learn how to hit the sweet spot." +--- + +VPD stands for Vapor Pressure Deficit. Think of it as the air's drying power. + +If the air is too dry (high VPD), plants sweat too much and slam their stomata to save water. Growth stops. +If the air is too humid (low VPD), they barely sweat. No water movement means no calcium uptake. + +You want the Goldilocks zone. For early veg, aim around 0.8 kPa. For flower, push 1.2–1.4 kPa. diff --git a/content/blog/post-5.md b/content/blog/post-5.md new file mode 100644 index 0000000..7fcd1d6 --- /dev/null +++ b/content/blog/post-5.md @@ -0,0 +1,10 @@ +--- +title: "Review: The Best pH Pen Under $100" +date: 2024-04-05T10:00:00Z +image: "/images/IMG_9413.jpeg" +summary: "Stop buying the yellow $10 pens. They are costing you your harvest." +--- + +I've used BlueLab, Hanna, and the cheap yellow generics. The value winner? The Apera PH60. + +It has a replaceable probe, decent calibration stability, and it's waterproof. The most important feature of a pH pen is trust. If you can't trust the reading (and keep it calibrated), you're flying blind. diff --git a/content/blog/post-6.md b/content/blog/post-6.md new file mode 100644 index 0000000..74cd8a3 --- /dev/null +++ b/content/blog/post-6.md @@ -0,0 +1,10 @@ +--- +title: "Why Your Reservoir Smells Bad" +date: 2024-04-20T10:00:00Z +image: "/images/IMG_2870.jpeg" +summary: "Healthy roots smell like fresh rain or cut potatoes. Swamp smell means trouble." +--- + +If you open your res and it smells like sulfur or rotten eggs, you have anaerobic bacteria. These thrive in low-oxygen water. + +The immediate fix? Add more air. Get a bigger air pump and more stones. You can't really have "too much" dissolved oxygen in a home setup. Then figure out why the system got that stale in the first place. diff --git a/content/blog/post-7.md b/content/blog/post-7.md new file mode 100644 index 0000000..a584117 --- /dev/null +++ b/content/blog/post-7.md @@ -0,0 +1,11 @@ +--- +title: "Rockwool vs. Coco Coir: A Sterile Perspective" +date: 2024-05-10T10:00:00Z +image: "/images/IMG_9612 2.jpeg" +summary: "Why I prefer inert media for precise control." +--- + +Coco is great, but it's organic. It comes with its own cation exchange capacity (CEC), meaning it holds onto Calcium and Magnesium. That's an extra variable in your math. + +Rockwool is basalt rock spun into fiber. It is chemically inert. It holds nothing. If I pour 100ppm of Nitrogen in, 100ppm is available to the plant. For scientific growing, Rockwool wins. +If you want sterile, rockwool plays nice; coco fights back. diff --git a/content/blog/post-8.md b/content/blog/post-8.md new file mode 100644 index 0000000..f815c36 --- /dev/null +++ b/content/blog/post-8.md @@ -0,0 +1,11 @@ +--- +title: "Automating pH: Is it Worth It?" +date: 2024-06-01T10:00:00Z +image: "/images/IMG_9768.jpeg" +summary: "Dosers are cool, but they can fail. Should you trust a robot with your crop?" +--- + +I love automation, but I always tell new growers: **Don't automate what you don't understand.** + +If you don't know *why* your pH is drifting up (plants eating nitrates) or down (root rot or ammonia), a doser will just mask the problem until it's too late. Master manual control first. Then get the robot. +Once you can predict the drift before it happens, then let the robot babysit. diff --git a/content/blog/post-9.md b/content/blog/post-9.md new file mode 100644 index 0000000..bbf41a7 --- /dev/null +++ b/content/blog/post-9.md @@ -0,0 +1,22 @@ +--- +title: "Dialing EC and pH in Sterile Hydro" +date: 2025-12-26T09:22:47Z +image: "/images/IMG_1439.jpeg" +summary: "How I set EC and pH, what the drifts mean, and how to log it without losing your mind." +--- + +Sterile growing means zero buffer. EC and pH are your steering wheel. + +**EC (electrical conductivity):** total salts/ppm. I keep veg around 1.6–2.0 EC and push 2.4–3.0 in flower depending on strain and light intensity. Don't chase numbers blind—watch the leaves. Light green with stalled growth? You probably underfed. Dark, clawed tips? Back off. + +**pH:** Cannabis likes slightly acidic solution. My sweet spot is 5.8–6.2 in the reservoir. Calibrate your meter often; cheap meters can burn you faster than a bad dab. + +**Common screw-up:** adjusting pH without checking EC first. If the soup is too hot or too weak, pH tweaks are lipstick on a pig. + +**Practical tips:** +- Start low and creep up. Easier to add salts than undo burn. +- Check pH daily. Check EC every couple days or any time the plants look different. +- Track everything—reservoir swaps, nutrient adds, readings. Your logbook will solve next week's mystery smell. + +Happy growing, +Dr Jones diff --git a/content/chat.md b/content/chat.md new file mode 100644 index 0000000..af75c0d --- /dev/null +++ b/content/chat.md @@ -0,0 +1,11 @@ +--- +title: "Chat with Dr. Jones AI" +date: 2024-12-26T12:00:00Z +draft: false +layout: "chat" +--- + +# Chat with Dr. Jones AI + +Get instant answers about sterile hydroponics, cannabis cultivation, and growing techniques from an AI trained on 20 years of experience. + diff --git a/content/contact.md b/content/contact.md new file mode 100644 index 0000000..e502479 --- /dev/null +++ b/content/contact.md @@ -0,0 +1,61 @@ +--- +title: "Contact" +date: 2024-12-24T12:00:00Z +draft: false +--- + +No assistant. No gatekeeper. If you email me, you're talking to the same dude cleaning his reservoirs. + +### How to Reach Me + + + +
+**Email:** Available to members only +
+ +If you're looking for a consultation, hit the form below or email me directly (members only). I usually reply within 24 hours on weekdays unless I'm mid-harvest. + +**Quick questions:** Check the [Blog](/blog) first. I might have already ranted about it there. + +--- + + + + + + diff --git a/content/earnings.md b/content/earnings.md new file mode 100644 index 0000000..9482f5c --- /dev/null +++ b/content/earnings.md @@ -0,0 +1,10 @@ +--- +title: "Potential Earnings Calculator" +date: 2024-12-24T12:00:00Z +draft: false +layout: "earnings" +--- + +# Discover Your Potential Earnings + +Play with this calculator to see what a sterile run can net you. My baseline: **1.5lb of cannabis from a 2x2.5x7 space**. It's a sanity check, not a lottery ticket. diff --git a/content/library/_index.md b/content/library/_index.md new file mode 100644 index 0000000..22d18dd --- /dev/null +++ b/content/library/_index.md @@ -0,0 +1,4 @@ +--- +title: "Library" +layout: "library" +--- diff --git a/content/membership.md b/content/membership.md new file mode 100644 index 0000000..3c6d8c3 --- /dev/null +++ b/content/membership.md @@ -0,0 +1,7 @@ +--- +title: "Membership" +date: 2024-12-24T12:00:00Z +draft: false +layout: "membership" +--- + diff --git a/content/offerings.md b/content/offerings.md new file mode 100644 index 0000000..653a3a2 --- /dev/null +++ b/content/offerings.md @@ -0,0 +1,36 @@ +--- +title: "What I Bring to the Table" +date: 2024-12-24T12:00:00Z +draft: false +--- + +# What I Bring to the Table + +I'm not here to feed you Reddit folklore. I'm a mellow science nerd who's been running sterile hydro in a legal state for two decades and smoking the proof. I grow the cleanest, loudest plants I can and teach the same way. + +## The Real Deal + +I don't hand out generic templates. I give you the guts and know-how to trust your own room. DIY tutorials tuned to your exact space and gear so you can grow anything without guessing. + +No more random advice that costs you harvests. You get a direct line to someone who's seen the weird problems, fixed them, and knows what your plants actually need. + +## Sterile Hydroponics: The Methodology + +The foundation is **sterile hydroponics**. It's a mindset for your grow room: clean inputs, clean plumbing, and no biological wildcards. + +The secrets are in the details I've refined over twenty years—timing, dosing, cleaning cadence—the little moves that separate award-winning harvests from "good enough." + +Those details are what you get when we work together: the protocols, the timing, the tricks that turn a decent grow into something special. + +## What You Actually Get + +- **Personalized guidance** built for your setup +- **DIY walkthroughs** that match your space and gear +- **Real fixes** to real problems, not theory for theory's sake +- **Hard-earned playbooks** from 20 years hands-on +- **Confidence** to run clean without second-guessing +- **A direct line** to someone who lives this every day + +I'm not here to babysit every step. I'm here to hand you the tools and courage to run a sterile, high-output grow on your own. + +The secrets are real. The results are real. Ready to stop guessing and start growing on purpose? diff --git a/content/services/1-consultation.md b/content/services/1-consultation.md new file mode 100644 index 0000000..41cdb1e --- /dev/null +++ b/content/services/1-consultation.md @@ -0,0 +1,19 @@ +--- +title: "One-on-One Tutoring" +date: 2024-12-24T01:00:00Z +price: "$20" +price_period: "/ hour" +cta: "Book Session" +foxy_link: "https://example.foxycart.com/cart?name=Tutoring&price=20" +--- + +A 60-minute video call to untangle your exact situation and leave you with a plan. + +**What's Included:** +- Quick diagnosis of deficiencies, pests, or pathogens. +- Sterile nutrient recipe tune-up for your reservoir. +- Protocol check on how you're cleaning and running the system. +- Session recording so you don't miss the details. + +**Who it's for:** +Home growers stuck on a specific problem who want straight answers instead of forum guesses. diff --git a/content/services/2-design.md b/content/services/2-design.md new file mode 100644 index 0000000..7f98900 --- /dev/null +++ b/content/services/2-design.md @@ -0,0 +1,19 @@ +--- +title: "System Design Review" +date: 2024-12-24T02:00:00Z +price: "$200" +price_period: "/ project" +cta: "Submit Design" +foxy_link: "https://example.foxycart.com/cart?name=DesignReview&price=200" +--- + +Before you build, let me sanity-check your engineering so it stays sterile and easy to maintain. + +**What's Included:** +- Full schematic audit (plumbing, lighting, HVAC). +- Parts and compatibility check. +- Sterility risk map. +- Written report with 3-5 actionable fixes. + +**Who it's for:** +Growers planning a new room or major retrofit who want to avoid expensive rework and keep everything lab-clean from day one. diff --git a/content/services/3-mentorship.md b/content/services/3-mentorship.md new file mode 100644 index 0000000..3f2f708 --- /dev/null +++ b/content/services/3-mentorship.md @@ -0,0 +1,19 @@ +--- +title: "Ongoing Mentorship" +date: 2024-12-24T03:00:00Z +price: "$500" +price_period: "/ month" +cta: "Request Mentorship" +foxy_link: "https://example.foxycart.com/cart?name=Mentorship&price=500&sub_frequency=1m" +--- + +I become your fractional Master Grower for an entire crop cycle. + +**What's Included:** +- Weekly 30-min check-in calls. +- Unlimited email/text support (within business hours). +- Real-time crop steering on EC, pH, and canopy calls. +- Harvest and curing guidance. + +**Who it's for:** +Serious growers chasing competition-quality results who want a safety net and someone to geek out with along the way. diff --git a/content/services/_index.md b/content/services/_index.md new file mode 100644 index 0000000..e7b34c6 --- /dev/null +++ b/content/services/_index.md @@ -0,0 +1,4 @@ +--- +title: "Services" +description: "Professional Hydroponic Consulting" +--- diff --git a/content/testimonials.md b/content/testimonials.md new file mode 100644 index 0000000..4547f5d --- /dev/null +++ b/content/testimonials.md @@ -0,0 +1,6 @@ +--- +title: "Testimonials" +date: 2024-12-24T12:00:00Z +draft: false +layout: "testimonials" +--- \ No newline at end of file diff --git a/data/library.yaml b/data/library.yaml new file mode 100644 index 0000000..4091fbc --- /dev/null +++ b/data/library.yaml @@ -0,0 +1,514 @@ +- title: 'Hydroponics Garden: The Complete DIY Guide For Beginners To Learn How To + Build A System To Grow Plants' + filename: Hydroponics Garden The Complete DIY Guide For Beginners To Learn How To + Build A System To Grow Plants.pdf + description: 'Educational resource: Hydroponics Garden: The Complete DIY Guide For + Beginners To Learn How To Build A System To Grow Plants' + category: Systems + date: '2024-03-25' + file: /library/Hydroponics Garden The Complete DIY Guide For Beginners To Learn + How To Build A System To Grow Plants.pdf +- title: Beginner's Guide to Growing Cannabis and Making Your Own Healing Remedies + filename: Beginner's Guide to Growing Cannabis and Making Your Own Healing Remedies.epub + description: 'Educational resource: Beginner''s Guide to Growing Cannabis and Making + Your Own Healing Remedies' + category: Cultivation + date: '2024-03-25' + file: /library/Beginner's Guide to Growing Cannabis and Making Your Own Healing + Remedies.epub +- title: Totally Organic Hydroponics + filename: Totally Organic Hydroponics.pdf + description: 'Educational resource: Totally Organic Hydroponics' + category: Systems + date: '2024-03-25' + file: /library/Totally Organic Hydroponics.pdf +- title: 'Clearing the Smoke: The Science of Cannabis' + filename: Clearing the Smoke The Science of Cannabis.mp4 + description: 'Educational resource: Clearing the Smoke: The Science of Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Clearing the Smoke The Science of Cannabis.mp4 +- title: Gardening For Beginners 2024 + filename: Gardening For Beginners 2024.pdf + description: 'Educational resource: Gardening For Beginners 2024' + category: General + date: '2024-03-25' + file: /library/Gardening For Beginners 2024.pdf +- title: Encyclopedia of Garden Plants for Every Location + filename: Encyclopedia of Garden Plants for Every Location.pdf + description: 'Educational resource: Encyclopedia of Garden Plants for Every Location' + category: General + date: '2024-03-25' + file: /library/Encyclopedia of Garden Plants for Every Location.pdf +- title: 'Cannabis: A Complete Guide' + filename: Cannabis A Complete Guide.pdf + description: 'Educational resource: Cannabis: A Complete Guide' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis A Complete Guide.pdf +- title: Guide to Hydroponics + filename: Guide to Hydroponics.epub + description: 'Educational resource: Guide to Hydroponics' + category: Systems + date: '2024-03-25' + file: /library/Guide to Hydroponics.epub +- title: 'Marijuana Harvest: How to Maximize Quality and Yield in Your Cannabis Garden' + filename: Marijuana Harvest How to Maximize Quality and Yield in Your Cannabis Garden.epub + description: 'Educational resource: Marijuana Harvest: How to Maximize Quality and + Yield in Your Cannabis Garden' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Harvest How to Maximize Quality and Yield in Your Cannabis + Garden.epub +- title: The Ecology of Plants + filename: The Ecology of Plants.pdf + description: 'Educational resource: The Ecology of Plants' + category: Science + date: '2024-03-25' + file: /library/The Ecology of Plants.pdf +- title: 'Hydroponic Food Production: A Definitive Guidebook for the Commercial Hydroponic + Grower' + filename: Hydroponic Food Production A Definitive Guidebook for the Commercial Hydroponic + Grower.pdf + description: 'Educational resource: Hydroponic Food Production: A Definitive Guidebook + for the Commercial Hydroponic Grower' + category: Systems + date: '2024-03-25' + file: /library/Hydroponic Food Production A Definitive Guidebook for the Commercial + Hydroponic Grower.pdf +- title: 'Grow Great Weed: Personal & Medical Marijuana Indoor Outdoor Grower Big + Bud Bible' + filename: Grow Great Weed Personal & Medical Marijuana Indoor Outdoor Grower Big + Bud Bible.pdf + description: 'Educational resource: Grow Great Weed: Personal & Medical Marijuana + Indoor Outdoor Grower Big Bud Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Grow Great Weed Personal & Medical Marijuana Indoor Outdoor Grower + Big Bud Bible.pdf +- title: Scanned Image + filename: Scanned Image.pdf + description: 'Educational resource: Scanned Image' + category: General + date: '2024-03-25' + file: /library/Scanned Image.pdf +- title: 100 Best Cannabis Strains + filename: 100 Best Cannabis Strains.pdf + description: 'Educational resource: 100 Best Cannabis Strains' + category: Cultivation + date: '2024-03-25' + file: /library/100 Best Cannabis Strains.pdf +- title: Downmagaz.net + filename: Downmagaz.net.pdf + description: 'Educational resource: Downmagaz.net' + category: General + date: '2024-03-25' + file: /library/Downmagaz.net.pdf +- title: The Cannabis Grow Bible + filename: The Cannabis Grow Bible.pdf + description: 'Educational resource: The Cannabis Grow Bible' + category: Cultivation + date: '2024-03-25' + file: /library/The Cannabis Grow Bible.pdf +- title: Diagnosing Hemp and Cannabis Crop Diseases + filename: Diagnosing Hemp and Cannabis Crop Diseases.pdf + description: 'Educational resource: Diagnosing Hemp and Cannabis Crop Diseases' + category: Cultivation + date: '2024-03-25' + file: /library/Diagnosing Hemp and Cannabis Crop Diseases.pdf +- title: Guide to Hydroponics + filename: Guide to Hydroponics.pdf + description: 'Educational resource: Guide to Hydroponics' + category: Systems + date: '2024-03-25' + file: /library/Guide to Hydroponics.pdf +- title: 'Hydroponics Garden: The Complete DIY Guide For Beginners To Learn How To + Build A System To Grow Plants' + filename: Hydroponics Garden The Complete DIY Guide For Beginners To Learn How To + Build A System To Grow Plants.pdf + description: 'Educational resource: Hydroponics Garden: The Complete DIY Guide For + Beginners To Learn How To Build A System To Grow Plants' + category: Systems + date: '2024-03-25' + file: /library/Hydroponics Garden The Complete DIY Guide For Beginners To Learn + How To Build A System To Grow Plants.pdf +- title: 'How to Grow Marijuana: 3 Books in 1 - The Complete Guide to Growing Top-Quality + Weed Indoors and Outdoors' + filename: How to Grow Marijuana 3 Books in 1 - The Complete Guide to Growing Top-Quality + Weed Indoors and Outdoors.pdf + description: 'Educational resource: How to Grow Marijuana: 3 Books in 1 - The Complete + Guide to Growing Top-Quality Weed Indoors and Outdoors' + category: Cultivation + date: '2024-03-25' + file: /library/How to Grow Marijuana 3 Books in 1 - The Complete Guide to Growing + Top-Quality Weed Indoors and Outdoors.pdf +- title: Hydroponics Gardening Guide + filename: Hydroponics Gardening Guide.pdf + description: 'Educational resource: Hydroponics Gardening Guide' + category: Cultivation + date: '2024-03-25' + file: /library/Hydroponics Gardening Guide.pdf +- title: Jesse Ventura's Marijuana Manifesto + filename: Jesse Ventura's Marijuana Manifesto.pdf + description: 'Educational resource: Jesse Ventura''s Marijuana Manifesto' + category: Cultivation + date: '2024-03-25' + file: /library/Jesse Ventura's Marijuana Manifesto.pdf +- title: True Living Organics + filename: True Living Organics.pdf + description: 'Educational resource: True Living Organics' + category: General + date: '2024-03-25' + file: /library/True Living Organics.pdf +- title: 'DIY Home Hydroponics For Beginners: The Essential Guide To Turn Your Backyard + Into A Farm' + filename: DIY Home Hydroponics For Beginners The Essential Guide To Turn Your Backyard + Into A Farm.pdf + description: 'Educational resource: DIY Home Hydroponics For Beginners: The Essential + Guide To Turn Your Backyard Into A Farm' + category: Systems + date: '2024-03-25' + file: /library/DIY Home Hydroponics For Beginners The Essential Guide To Turn Your + Backyard Into A Farm.pdf +- title: 'DIY Home Hydroponics For Beginners: The Essential Guide To Turn Your Backyard + Into A Farm' + filename: DIY Home Hydroponics For Beginners The Essential Guide To Turn Your Backyard + Into A Farm.pdf + description: 'Educational resource: DIY Home Hydroponics For Beginners: The Essential + Guide To Turn Your Backyard Into A Farm' + category: Systems + date: '2024-03-25' + file: /library/DIY Home Hydroponics For Beginners The Essential Guide To Turn Your + Backyard Into A Farm.pdf +- title: What a Plant Knows + filename: What a Plant Knows.pdf + description: 'Educational resource: What a Plant Knows' + category: General + date: '2024-03-25' + file: /library/What a Plant Knows.pdf +- title: 'The Biology of Marijuana: From Gene to Behavior' + filename: The Biology of Marijuana From Gene to Behavior.pdf + description: 'Educational resource: The Biology of Marijuana: From Gene to Behavior' + category: Cultivation + date: '2024-03-25' + file: /library/The Biology of Marijuana From Gene to Behavior.pdf +- title: 'The Cannabis Breeder''s Bible: The Definitive Guide to Marijuana Genetics, + Cannabis Botany and Creating Strains for the Seed Market' + filename: The Cannabis Breeder's Bible The Definitive Guide to Marijuana Genetics, + Cannabis Botany and Creating Strains for the Seed Market.epub + description: 'Educational resource: The Cannabis Breeder''s Bible: The Definitive + Guide to Marijuana Genetics, Cannabis Botany and Creating Strains for the Seed + Market' + category: Cultivation + date: '2024-03-25' + file: /library/The Cannabis Breeder's Bible The Definitive Guide to Marijuana Genetics, + Cannabis Botany and Creating Strains for the Seed Market.epub +- title: 'Cannabis Alchemy: The Art of Modern Hashmaking' + filename: Cannabis Alchemy The Art of Modern Hashmaking.pdf + description: 'Educational resource: Cannabis Alchemy: The Art of Modern Hashmaking' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis Alchemy The Art of Modern Hashmaking.pdf +- title: 'Marijuana Botany: An Advanced Study - The Propagation and Breeding of Distinctive + Cannabis' + filename: Marijuana Botany An Advanced Study - The Propagation and Breeding of Distinctive + Cannabis.pdf + description: 'Educational resource: Marijuana Botany: An Advanced Study - The Propagation + and Breeding of Distinctive Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Botany An Advanced Study - The Propagation and Breeding + of Distinctive Cannabis.pdf +- title: DIY Home Hydroponics For Beginners + filename: DIY Home Hydroponics For Beginners.epub + description: 'Educational resource: DIY Home Hydroponics For Beginners' + category: Systems + date: '2024-03-25' + file: /library/DIY Home Hydroponics For Beginners.epub +- title: Diagnosing Hemp and Cannabis Crop Diseases + filename: Diagnosing Hemp and Cannabis Crop Diseases.pdf + description: 'Educational resource: Diagnosing Hemp and Cannabis Crop Diseases' + category: Cultivation + date: '2024-03-25' + file: /library/Diagnosing Hemp and Cannabis Crop Diseases.pdf +- title: Handbook of Cannabis + filename: Handbook of Cannabis.pdf + description: 'Educational resource: Handbook of Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Handbook of Cannabis.pdf +- title: 'Hydroponics: A Standard Methodology for Plant Biological Researches' + filename: Hydroponics A Standard Methodology for Plant Biological Researches.pdf + description: 'Educational resource: Hydroponics: A Standard Methodology for Plant + Biological Researches' + category: Systems + date: '2024-03-25' + file: /library/Hydroponics A Standard Methodology for Plant Biological Researches.pdf +- title: 'Cannabis Sativa Volume 3: The Essential Guide to the World''s Finest Marijuana + Strains' + filename: Cannabis Sativa Volume 3 The Essential Guide to the World's Finest Marijuana + Strains.epub + description: 'Educational resource: Cannabis Sativa Volume 3: The Essential Guide + to the World''s Finest Marijuana Strains' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis Sativa Volume 3 The Essential Guide to the World's Finest + Marijuana Strains.epub +- title: 'Growing Marijuana Mastery: The Easiest and Complete Handbook to Growing + Weed Indoor and Outdoor' + filename: Growing Marijuana Mastery The Easiest and Complete Handbook to Growing + Weed Indoor and Outdoor.pdf + description: 'Educational resource: Growing Marijuana Mastery: The Easiest and Complete + Handbook to Growing Weed Indoor and Outdoor' + category: Cultivation + date: '2024-03-25' + file: /library/Growing Marijuana Mastery The Easiest and Complete Handbook to Growing + Weed Indoor and Outdoor.pdf +- title: Gardening Indoors with Soil & Hydroponics, Fifth Edition + filename: Gardening Indoors with Soil & Hydroponics, Fifth Edition.pdf + description: 'Educational resource: Gardening Indoors with Soil & Hydroponics, Fifth + Edition' + category: Systems + date: '2024-03-25' + file: /library/Gardening Indoors with Soil & Hydroponics, Fifth Edition.pdf +- title: 'Cannabis Chemistry and Biology: Fundamentals' + filename: Cannabis Chemistry and Biology Fundamentals.pdf + description: 'Educational resource: Cannabis Chemistry and Biology: Fundamentals' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis Chemistry and Biology Fundamentals.pdf +- title: Nutrients for Hydroponics Tissue Culture + filename: Nutrients for Hydroponics Tissue Culture.pdf + description: 'Educational resource: Nutrients for Hydroponics Tissue Culture' + category: Systems + date: '2024-03-25' + file: /library/Nutrients for Hydroponics Tissue Culture.pdf +- title: 'Marijuana Grow Basics: The Easy Guide for Cannabis Aficionados' + filename: Marijuana Grow Basics The Easy Guide for Cannabis Aficionados.pdf + description: 'Educational resource: Marijuana Grow Basics: The Easy Guide for Cannabis + Aficionados' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Grow Basics The Easy Guide for Cannabis Aficionados.pdf +- title: 'DIY Home Hydroponics For Beginners: The Essential Guide To Turn Your Backyard + Into A Farm' + filename: DIY Home Hydroponics For Beginners The Essential Guide To Turn Your Backyard + Into A Farm.epub + description: 'Educational resource: DIY Home Hydroponics For Beginners: The Essential + Guide To Turn Your Backyard Into A Farm' + category: Systems + date: '2024-03-25' + file: /library/DIY Home Hydroponics For Beginners The Essential Guide To Turn Your + Backyard Into A Farm.epub +- title: '100 Best Cannabis Strains: A Pocket Guide for Medicinal and Recreational + Use' + filename: 100 Best Cannabis Strains A Pocket Guide for Medicinal and Recreational + Use.epub + description: 'Educational resource: 100 Best Cannabis Strains: A Pocket Guide for + Medicinal and Recreational Use' + category: Cultivation + date: '2024-03-25' + file: /library/100 Best Cannabis Strains A Pocket Guide for Medicinal and Recreational + Use.epub +- title: Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible + filename: '#1 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: Marijuana Horticulture - The Indoor-Outdoor Medical + Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/#1 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible + filename: '#2 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: Marijuana Horticulture - The Indoor-Outdoor Medical + Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/#2 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible + filename: '#3 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: Marijuana Horticulture - The Indoor-Outdoor Medical + Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/#3 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible + filename: '#4 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: Marijuana Horticulture - The Indoor-Outdoor Medical + Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/#4 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible + filename: '#5 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: Marijuana Horticulture - The Indoor-Outdoor Medical + Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/#5 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible + filename: '#6 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: Marijuana Horticulture - The Indoor-Outdoor Medical + Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/#6 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: Jesse Ventura's Marijuana Manifesto + filename: Jesse Ventura's Marijuana Manifesto.epub + description: 'Educational resource: Jesse Ventura''s Marijuana Manifesto' + category: Cultivation + date: '2024-03-25' + file: /library/Jesse Ventura's Marijuana Manifesto.epub +- title: 'Grow Great Weed: Personal & Medical Marijuana Indoor Outdoor Grower Big + Bud Bible' + filename: Grow Great Weed Personal & Medical Marijuana Indoor Outdoor Grower Big + Bud Bible.pdf + description: 'Educational resource: Grow Great Weed: Personal & Medical Marijuana + Indoor Outdoor Grower Big Bud Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Grow Great Weed Personal & Medical Marijuana Indoor Outdoor Grower + Big Bud Bible.pdf +- title: Hydroponics Gardening Guide + filename: Hydroponics Gardening Guide.epub + description: 'Educational resource: Hydroponics Gardening Guide' + category: Systems + date: '2024-03-25' + file: /library/Hydroponics Gardening Guide.epub +- title: 'How to Grow Marijuana: 3 Books in 1' + filename: How to Grow Marijuana 3 Books in 1.pdf + description: 'Educational resource: How to Grow Marijuana: 3 Books in 1' + category: Cultivation + date: '2024-03-25' + file: /library/How to Grow Marijuana 3 Books in 1.pdf +- title: 'Cannabis Grower''s Handbook: The Complete Guide to Marijuana and Hemp Cultivation' + filename: Cannabis Grower's Handbook The Complete Guide to Marijuana and Hemp Cultivation.pdf + description: 'Educational resource: Cannabis Grower''s Handbook: The Complete Guide + to Marijuana and Hemp Cultivation' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis Grower's Handbook The Complete Guide to Marijuana and Hemp + Cultivation.pdf +- title: 'The Big Book of Buds: More Marijuana Varieties from the World''s Greatest + Seed Breeders' + filename: The Big Book of Buds More Marijuana Varieties from the World's Greatest + Seed Breeders.epub + description: 'Educational resource: The Big Book of Buds: More Marijuana Varieties + from the World''s Greatest Seed Breeders' + category: Cultivation + date: '2024-03-25' + file: /library/The Big Book of Buds More Marijuana Varieties from the World's Greatest + Seed Breeders.epub +- title: 'Marijuana Hydroponics: High-Tech Water Culture' + filename: Marijuana Hydroponics High-Tech Water Culture.epub + description: 'Educational resource: Marijuana Hydroponics: High-Tech Water Culture' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Hydroponics High-Tech Water Culture.epub +- title: 'Closet Cultivator: Indoor Marijuana Cultivation Made Easy' + filename: Closet Cultivator Indoor Marijuana Cultivation Made Easy.pdf + description: 'Educational resource: Closet Cultivator: Indoor Marijuana Cultivation + Made Easy' + category: Cultivation + date: '2024-03-25' + file: /library/Closet Cultivator Indoor Marijuana Cultivation Made Easy.pdf +- title: 'What a Plant Knows: A Field Guide to the Senses' + filename: What a Plant Knows A Field Guide to the Senses.epub + description: 'Educational resource: What a Plant Knows: A Field Guide to the Senses' + category: General + date: '2024-03-25' + file: /library/What a Plant Knows A Field Guide to the Senses.epub +- title: Handbook of Cannabis Production in Controlled Environments + filename: Handbook of Cannabis Production in Controlled Environments.pdf + description: 'Educational resource: Handbook of Cannabis Production in Controlled + Environments' + category: Cultivation + date: '2024-03-25' + file: /library/Handbook of Cannabis Production in Controlled Environments.pdf +- title: The Ecology of Plants, 3rd Edition + filename: The Ecology of Plants, 3rd Edition.pdf + description: 'Educational resource: The Ecology of Plants, 3rd Edition' + category: Science + date: '2024-03-25' + file: /library/The Ecology of Plants, 3rd Edition.pdf +- title: 'Growing Marijuana Mastery: The Advanced Guide to Grow Cannabis' + filename: Growing Marijuana Mastery The Advanced Guide to Grow Cannabis.epub + description: 'Educational resource: Growing Marijuana Mastery: The Advanced Guide + to Grow Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Growing Marijuana Mastery The Advanced Guide to Grow Cannabis.epub +- title: 'Marijuana Botany: An Advanced Study - The Propagation and Breeding of Distinctive + Cannabis' + filename: Marijuana Botany An Advanced Study - The Propagation and Breeding of Distinctive + Cannabis.pdf + description: 'Educational resource: Marijuana Botany: An Advanced Study - The Propagation + and Breeding of Distinctive Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Botany An Advanced Study - The Propagation and Breeding + of Distinctive Cannabis.pdf +- title: 'Cannabis Grower''s Handbook: The Complete Guide to Marijuana and Hemp Cultivation' + filename: Cannabis Grower's Handbook The Complete Guide to Marijuana and Hemp Cultivation.epub + description: 'Educational resource: Cannabis Grower''s Handbook: The Complete Guide + to Marijuana and Hemp Cultivation' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis Grower's Handbook The Complete Guide to Marijuana and Hemp + Cultivation.epub +- title: 'True Living Organics: The Ultimate Guide to Growing All-Natural Marijuana + Indoors' + filename: True Living Organics The Ultimate Guide to Growing All-Natural Marijuana + Indoors.epub + description: 'Educational resource: True Living Organics: The Ultimate Guide to + Growing All-Natural Marijuana Indoors' + category: Cultivation + date: '2024-03-25' + file: /library/True Living Organics The Ultimate Guide to Growing All-Natural Marijuana + Indoors.epub +- title: Marijuana Chemistry + filename: Marijuana Chemistry.pdf + description: 'Educational resource: Marijuana Chemistry' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Chemistry.pdf +- title: 'Plant Science: An Introduction to Botany' + filename: Plant Science An Introduction to Botany.pdf + description: 'Educational resource: Plant Science: An Introduction to Botany' + category: Science + date: '2024-03-25' + file: /library/Plant Science An Introduction to Botany.pdf +- title: 'Hydroponic Food Production: A Definitive Guidebook, 7th Edition' + filename: Hydroponic Food Production A Definitive Guidebook, 7th Edition.pdf + description: 'Educational resource: Hydroponic Food Production: A Definitive Guidebook, + 7th Edition' + category: Systems + date: '2024-03-25' + file: /library/Hydroponic Food Production A Definitive Guidebook, 7th Edition.pdf +- title: 'Higher: The Lore, Legends, and Legacy of Cannabis' + filename: Higher The Lore, Legends, and Legacy of Cannabis.epub + description: 'Educational resource: Higher: The Lore, Legends, and Legacy of Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Higher The Lore, Legends, and Legacy of Cannabis.epub +- title: Marijuana Horticulture Complete + filename: Marijuana Horticulture Complete.pdf + description: 'Educational resource: Marijuana Horticulture Complete' + category: General + date: '2024-03-25' + file: /library/Marijuana Horticulture Complete.pdf +- title: Commercial Hydroponics, 3rd Edition + filename: Commercial Hydroponics, 3rd Edition.pdf + description: 'Educational resource: Commercial Hydroponics, 3rd Edition' + category: Systems + date: '2024-03-25' + file: /library/Commercial Hydroponics, 3rd Edition.pdf +- title: 'DIY Aquaponics Marijuana Growing at Home: Learn to Grow Most Potent THC + Cannabis Ever!' + filename: DIY Aquaponics Marijuana Growing at Home Learn to Grow Most Potent THC + Cannabis Ever!.epub + description: 'Educational resource: DIY Aquaponics Marijuana Growing at Home: Learn + to Grow Most Potent THC Cannabis Ever!' + category: Cultivation + date: '2024-03-25' + file: /library/DIY Aquaponics Marijuana Growing at Home Learn to Grow Most Potent + THC Cannabis Ever!.epub diff --git a/db/members.db b/db/members.db new file mode 100644 index 0000000000000000000000000000000000000000..8648b5d14406f1a311128d0618dcb5f6e3d18074 GIT binary patch literal 45056 zcmeI*-)`dA9S3kbBqU^Z;@y?Ds3S$=YG!r@?Sx=EK$2CZA;cjj0TN=EBsa)lCm8&P zV6z0Y)s##-cfDPykI@(CU3;_YUDX%pP1OgedbQevGenTdY?6kZRkdFS;^V&@e~x{O z|NYq8-PT==J+hmo;<5tuF-6nV3znrQlBB46am{l4VKRQP53KJ#B{ z*59S(K4<=R|L-3i&;B`(oBhMg^T4O1CmIMq00I#BnF+jJoShB|0{v#nRmz4|u`O4r zxFx4mHg(6*ZOgf$*S%z3OvoZDC)Tz__KFc^*~`r{U1g=LET+UfyOWnPiTnY(B_6Pe zf}E4GWPTYjD?fjCI=1C#&C<}s*__N~3)|aQjcX~U#>(P88RG}W(o97+t{=_R97j36 z!TdZc*VWV#*>}rQM%G`c7yZnS&$ z>`VW2aDJZto#YV>a0cz^5tc#UF@oG?o&6E;?zn<2PWyw&`S;NexJuk0HS_xOsp;U8 zC-fV}<90R2)lJ*d6~h@$`$yP^hx|nJ{fbMo$ToXjjX_qts@*Kzta_<5HRW!VfzoI+ z?XNVIUCZUR#YFa^Dv8{&WY21o)4_)i>EA!{3QD!xmSHQZbCH-DDWZ$sV=N!NK{j5M zZ1>Lf?3Xs9s}}x9H?&eiajVzQZLl;Ss-E8Q`Ga44K_8q~LFYA?qn)%gtD?QF&<358 zBkY$3KI^Y`&&jBv*N^GxU@%C(dFsux&*OOiCPt?BdX2HjhW3{?sMZ^b_ZD^LNTgOkYS}(@85svJiuKK9K)>6K22^IAUgWpk$gh4Vgn(~Lop7a9ma00Izz00bZa0SG_<0uX=z1jZoH`-GmQxZc#G zc|o8|%`9up7uu;}HViFXvCYSP`7y^o?sRJ>TT$MUUv`^$HNCuDh;C(a-P4G2+)C&4 zmx=Ux_qZi3W{;P15xK$XalWR@I`c7;z9)g< z$C~c>eDi_9Q&J?qQ`AROzK4OqgDB1n?F$CH2LaCS|NoX^zWp$Zg-?M11Rwwb2tWV= z5P$##AOHafK;TXS0_~?BQi}q|$HNiw$1NIqxv4Zei~Umr;f8uNdjEfvU*-7oDW=jT zkq3XpW;$%cP%M}9YLf8%x}_LKhb2)@n zG3u=|i3uP80SG_<0uX=z1Rwwb2tWV=5Ev5Z{gPan5A!>%5txJFr*V(Mm^JKSsQ zhx^rb|9W<|d%)+`GtrzZb&ht!k@{LztG2B=mn`k(>S`sQTIZY{Cu*;ycGosjJJIq{ zv7C}Cwf4pB>v8WYHcx)OF1!-wBg^sLgNboxPK=Vw)vLwgR>a6|$Am&kN*&g1cR@}r z)lSxg9YHV(wN!gq=p05}{&MEXz4a^Mcr>~!tn?nxSisuwmT|EehV-f4GZxVZYkcs{_mUnnqq!C_Z?}W zfdB*`009U<00Izz00bZa0SJs;phr!f`RJQo`u-74DEP*ngWH}pn0Q49ZhZCsT7ui3 z890CbANT){edWdsApijgKmY;|fB*y_009U<00JWdU-x7EJ>q(w2}6a^*dOl2`IFeH@)ZgB$DXuEn19{2Sfy;YT-L2>7x4hk T0| + + + + + + + + + + + {{ if .IsHome }}{{ .Site.Title }}{{ else }}{{ .Title }} | {{ .Site.Title }}{{ end }} + + + + + + + + + + + + {{ if .Params.image }} + + {{ else }} + + {{ end }} + + + + + + + + {{ if .Params.image }} + + {{ else }} + + {{ end }} + + + {{ if .IsHome }} + + {{ else if .IsPage }} + + {{ end }} + + {{ $styles := resources.Get "css/main.css" | css.PostCSS }} + + + + + + + + + + + + + + + + + + + +
+ {{ block "main" . }}{{ end }} +
+ + + + + + diff --git a/layouts/_default/list.html b/layouts/_default/list.html new file mode 100644 index 0000000..a0e7e36 --- /dev/null +++ b/layouts/_default/list.html @@ -0,0 +1,42 @@ +{{ define "main" }} +
+
+
+

{{ .Section }}

+

{{ .Title }}

+

{{ .Description }}

+
+ +
+ {{ range .Paginator.Pages }} +
+
+ {{ .Title }} +
+
+ +
+
+ +
+
+
+
+ {{ end }} +
+ + +
+ {{ template "_internal/pagination.html" . }} +
+
+
+{{ end }} \ No newline at end of file diff --git a/layouts/_default/single.html b/layouts/_default/single.html new file mode 100644 index 0000000..724e7ab --- /dev/null +++ b/layouts/_default/single.html @@ -0,0 +1,13 @@ +{{ define "main" }} +
+
+

{{ .Title }}

+
+ {{ if .Date }}Published on {{ .Date.Format "January 2, 2006" }}{{ end }} +
+
+ {{ .Content }} +
+
+
+{{ end }} \ No newline at end of file diff --git a/layouts/admin/single.html b/layouts/admin/single.html new file mode 100644 index 0000000..83104e6 --- /dev/null +++ b/layouts/admin/single.html @@ -0,0 +1,360 @@ +{{ define "main" }} +
+
+ +
+
+

Admin Login

+
+
+ + +
+
+

+
+ +
+
+
+ + +
+
+

Admin Dashboard

+ +
+ + +
+ + +
+ + +
+
+ Loading... +
+
+

+
+
+ +
+

No pending memberships

+
+
+
+ + +
+
+ Loading... +
+
+

+
+
+ +
+

No testimonials

+
+
+
+
+
+
+ + +{{ end }} + diff --git a/layouts/albums/list.html b/layouts/albums/list.html new file mode 100644 index 0000000..3be2121 --- /dev/null +++ b/layouts/albums/list.html @@ -0,0 +1,35 @@ +{{ define "main" }} + +{{ end }} \ No newline at end of file diff --git a/layouts/albums/single.html b/layouts/albums/single.html new file mode 100644 index 0000000..e285ec4 --- /dev/null +++ b/layouts/albums/single.html @@ -0,0 +1,98 @@ +{{ define "main" }} +
+ +
+
+ ← Back to Albums +

{{ .Title }}

+

{{ .Description }}

+
+ + +
+ +
+
+ + + +
+{{ end }} \ No newline at end of file diff --git a/layouts/chat/single.html b/layouts/chat/single.html new file mode 100644 index 0000000..8d3db25 --- /dev/null +++ b/layouts/chat/single.html @@ -0,0 +1,498 @@ +{{ define "main" }} +
+
+ +
+

+ Chat with Dr. Jones AI +

+

+ Get instant answers about sterile hydroponics, cannabis cultivation, and growing techniques. Drag library files into the chat to ask questions about specific documents. +

+
+ + +
+ +
+
+

+ + + + Library Files +

+

Drag files into chat to reference them

+
+ + +
+ +
+ + +
+ + + + +
+ +
+
+
+ + +
+ +
+ + + + + + + + +
+ + +
+ +
+
Files to include in this message:
+
+ +
+
+ +
+
+ + +
+ +
+

+ Powered by Ollama • Responses are AI-generated and should be verified +

+
+
+
+ + +
+

About This Chat

+
+
+

What I Can Help With

+
    +
  • Sterile hydroponic protocols
  • +
  • Nutrient management (EC, pH)
  • +
  • System design and setup
  • +
  • Problem diagnosis
  • +
  • Questions about library documents
  • +
+
+
+

How to Use

+
    +
  • Drag files from the sidebar into chat
  • +
  • Ask questions about specific documents
  • +
  • Up to 3 files can be referenced per message
  • +
  • Files are processed securely on the server
  • +
+
+
+
+
+
+ + +{{ end }} diff --git a/layouts/contact/single.html b/layouts/contact/single.html new file mode 100644 index 0000000..91ff732 --- /dev/null +++ b/layouts/contact/single.html @@ -0,0 +1,40 @@ +{{ define "main" }} +
+
+

{{ .Title }}

+
+ {{ .Content }} +
+
+
+ + +{{ end }} + diff --git a/layouts/earnings/single.html b/layouts/earnings/single.html new file mode 100644 index 0000000..e4599a8 --- /dev/null +++ b/layouts/earnings/single.html @@ -0,0 +1,308 @@ +{{ define "main" }} +
+
+ +
+

+ Discover Your Potential Earnings +

+

+ See what you could earn with my sterile hydroponics methodology. Based on proven results: 1.5lb of cannabis from a 2x2.5x7 space. +

+
+ + +
+

Grow Room Specifications

+ +
+ +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +
+ + +

Always set to Hydroponics

+
+
+
+ + +
+ + +
+

Your Potential Results

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + + +
MetricValue
Reservoir Size Needed + + gallons +
Total Yield (Grams) + + grams +
Price per Gram + $10.00 +
Grow Cycle Duration + 2.5 + months +
+
+
+ + +
+
+

+ 🎉 Potential Earnings 🎉 +

+
+

Total Revenue

+

+

+
+ +
+

+ WOAH! You could make that much! +

+

+ This is what's possible with my sterile hydroponics methodology. + Ready to make it happen? +

+ + Work With Me + +
+
+
+
+ + +
+

Enter your grow room specifications above to see your potential earnings!

+
+
+
+ + +{{ end }} + diff --git a/layouts/index.html b/layouts/index.html new file mode 100644 index 0000000..1bef17e --- /dev/null +++ b/layouts/index.html @@ -0,0 +1,121 @@ +{{ define "main" }} + +
+
+
+
+
+

+ Unlock the Power of + Sterile Hydroponics +

+

+ Disease-free roots, accelerated growth, and maximum yields. Learn how a clinical approach to gardening transforms your harvest. +

+ +
+
+
+
+
+ Sterile Hydroponics Setup +
+
+ + +
+
+
+

Why Sterile?

+

+ A Better Way to Grow +

+

+ Eliminate pathogens before they start. Our sterile methodology ensures your plants spend energy on growth, not fighting disease. +

+
+ +
+
+
+
+
+ + + + +
+

Zero Pathogens

+
+
+ By maintaining a sterile reservoir, you prevent Pythium (root rot) and other fungal issues completely. +
+
+ +
+
+
+ + + +
+

Maximum Uptake

+
+
+ Clean water allows for optimal nutrient absorption without biofilm interference. +
+
+ +
+
+
+ + + +
+

Clinical Precision

+
+
+ Data-driven gardening using precise measurements and sterile tools for predictable success. +
+
+
+
+
+
+ + +
+
+

Recent from the Journal

+
+ {{ range first 3 (where .Site.RegularPages "Section" "blog") }} + + {{ end }} +
+
+
+{{ end }} \ No newline at end of file diff --git a/layouts/library/list.html b/layouts/library/list.html new file mode 100644 index 0000000..f72d999 --- /dev/null +++ b/layouts/library/list.html @@ -0,0 +1,86 @@ +{{ define "main" }} +
+
+
+

Resource Library

+

+ Download guides, checklists, and protocols to upgrade your hydroponic game. +
+ For educational purposes only. +

+
+ + + + + +
+

+ Free users: 1 download allowed. + Sign up for membership for unlimited downloads. +

+
+ + + +
+ {{ $categories := slice "All" }} + {{ range .Site.Data.library }} + {{ $categories = $categories | append .category }} + {{ end }} + {{ range ($categories | uniq) }} + + {{ end }} +
+ + +
+ {{ range .Site.Data.library }} +
+
+
+ + {{ .category }} + + {{ .date }} +
+
+ + + +

{{ .title }}

+
+

{{ .description }}

+
+ +
+ {{ end }} +
+
+
+{{ end }} \ No newline at end of file diff --git a/layouts/membership/single.html b/layouts/membership/single.html new file mode 100644 index 0000000..0de2ae8 --- /dev/null +++ b/layouts/membership/single.html @@ -0,0 +1,221 @@ +{{ define "main" }} +
+
+
+

Become a Member

+

+ Unlock exclusive access to premium content, unlimited downloads, and direct communication with Dr. Jones. +

+
+ + +
+

Member Benefits

+
+
+
+ + + +
+
+

Unlimited Downloads

+

Access and download all resources from the library without limits.

+
+
+
+
+ + + +
+
+

Direct Email Access

+

Get my direct email address and submit contact forms for personalized help.

+
+
+
+
+ + + +
+
+

Member Testimonials

+

Read and share success stories from other growers using the sterile method.

+
+
+
+
+ + + +
+
+

Priority Support

+

Members get priority responses and access to exclusive content updates.

+
+
+
+
+ + +
+

Support the Method

+

+ Membership requires a $5 minimum donation via Buy Me a Coffee. This helps support continued development of sterile hydroponic protocols and resources. +

+ + + + + Donate $5 Minimum + +

After completing your donation, return here to sign up for membership.

+
+ + +
+
+ + +
+ +
+ +
+
+
+ + +
+
+ + +
+
+

+
+ +
+
+ + +
+
+
+ + +
+
+ + +
+
+ + +

Must be at least 8 characters

+
+
+

+
+
+

+
+ +
+
+
+
+ + +
+

+
+
+
+ + +{{ end }} + diff --git a/layouts/services/list.html b/layouts/services/list.html new file mode 100644 index 0000000..bb1a792 --- /dev/null +++ b/layouts/services/list.html @@ -0,0 +1,34 @@ +{{ define "main" }} +
+
+

Work With Me

+

+ Professional guidance to fast-track your success. +

+
+ +
+ {{ range .Pages }} +
+
+

{{ .Title }}

+

+ {{ .Params.price }} + {{ if .Params.price_period }}{{ .Params.price_period }}{{ end }} +

+
+ {{ .Content }} +
+
+ +
+ {{ end }} +
+
+{{ end }} \ No newline at end of file diff --git a/layouts/testimonials/single.html b/layouts/testimonials/single.html new file mode 100644 index 0000000..8b83db1 --- /dev/null +++ b/layouts/testimonials/single.html @@ -0,0 +1,159 @@ +{{ define "main" }} +
+
+
+

Member Testimonials

+

+ Real stories from growers who have transformed their gardens using the sterile hydroponic method. +

+
+ + + + + +
+ + + + +
+
+ Loading testimonials... +
+
+

+
+ +
+

No testimonials yet. Be the first to share your success story!

+
+
+
+
+
+ + +{{ end }} + diff --git a/library.yaml.backup b/library.yaml.backup new file mode 100644 index 0000000..dea2534 --- /dev/null +++ b/library.yaml.backup @@ -0,0 +1,628 @@ +- title: HYDROPONICS GARDEN - The Complete Diy Guide For Beginners To Learn How To + Build A System To Grow Plants + filename: HYDROPONICS GARDEN - The Complete Diy Guide For Beginners To Learn How + To Build A System To Grow Plants.pdf + description: 'Educational resource: HYDROPONICS GARDEN - The Complete Diy Guide + For Beginners To Learn How To Build A System To Grow Plants' + category: Systems + date: '2024-03-25' + file: /library/HYDROPONICS GARDEN - The Complete Diy Guide For Beginners To Learn + How To Build A System To Grow Plants.pdf +- title: Tammi Sweet - Beginner's Guide to Growing Cannabis and Making Your Own Healing + Remedies Learn about the Plant's Medicinal Properties Grow O + filename: Tammi Sweet - Beginner's Guide to Growing Cannabis and Making Your Own + Healing Remedies_ Learn about the Plant's Medicinal Properties_ Grow O - libgen.li.epub + description: 'Educational resource: Tammi Sweet - Beginner''s Guide to Growing Cannabis + and Making Your Own Healing Remedies Learn about the Plant''s Medicinal Properties Grow + O' + category: Cultivation + date: '2024-03-25' + file: /library/Tammi Sweet - Beginner's Guide to Growing Cannabis and Making Your + Own Healing Remedies_ Learn about the Plant's Medicinal Properties_ Grow O - libgen.li.epub +- title: '[Using the Sea of Green Process] Paul Wright - Totally organic hydroponics + (2004, Tin Alley Productions)' + filename: '[Using the Sea of Green Process] Paul Wright - Totally organic hydroponics + (2004, Tin Alley Productions) - libgen.li.pdf' + description: 'Educational resource: [Using the Sea of Green Process] Paul Wright + - Totally organic hydroponics (2004, Tin Alley Productions)' + category: Systems + date: '2024-03-25' + file: /library/[Using the Sea of Green Process] Paul Wright - Totally organic hydroponics + (2004, Tin Alley Productions) - libgen.li.pdf +- title: Cannabis Collection - PBS - Clearing the Smoke- The Science of Cannabis + filename: Cannabis Collection - PBS - Clearing the Smoke- The Science of Cannabis.mp4 + description: 'Educational resource: Cannabis Collection - PBS - Clearing the Smoke- + The Science of Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis Collection - PBS - Clearing the Smoke- The Science of Cannabis.mp4 +- title: Amateur Gardening Easy Guide To - Gardening For Beginners 2024 + filename: Amateur Gardening Easy Guide To - Gardening For Beginners 2024.pdf + description: 'Educational resource: Amateur Gardening Easy Guide To - Gardening + For Beginners 2024' + category: General + date: '2024-03-25' + file: /library/Amateur Gardening Easy Guide To - Gardening For Beginners 2024.pdf +- title: Encyclopedia of Garden Plants for Every Location - Featuring More Than 3,000 + Plants By DK + filename: Encyclopedia of Garden Plants for Every Location - Featuring More Than + 3,000 Plants By DK.pdf + description: 'Educational resource: Encyclopedia of Garden Plants for Every Location + - Featuring More Than 3,000 Plants By DK' + category: General + date: '2024-03-25' + file: /library/Encyclopedia of Garden Plants for Every Location - Featuring More + Than 3,000 Plants By DK.pdf +- title: Cannabis - A Complete Guide (2016) + filename: Cannabis - A Complete Guide (2016).pdf + description: 'Educational resource: Cannabis - A Complete Guide (2016)' + category: Cultivation + date: '2024-03-25' + file: /library/Cannabis - A Complete Guide (2016).pdf +- title: GUIDE TO HYDROPONICS By James Shelby PH.D + filename: GUIDE TO HYDROPONICS By James Shelby PH.D.epub + description: 'Educational resource: GUIDE TO HYDROPONICS By James Shelby PH.D' + category: Systems + date: '2024-03-25' + file: /library/GUIDE TO HYDROPONICS By James Shelby PH.D.epub +- title: Ed Rosenthal David Downs - Marijuana Harvest How to Maximize Quality and + Yield in Your Cannabis Garden (2017, Quick American Archives) + filename: Ed Rosenthal_ David Downs - Marijuana Harvest_ How to Maximize Quality + and Yield in Your Cannabis Garden (2017, Quick American Archives) - libgen.li.epub + description: 'Educational resource: Ed Rosenthal David Downs - Marijuana Harvest How + to Maximize Quality and Yield in Your Cannabis Garden (2017, Quick American Archives)' + category: Cultivation + date: '2024-03-25' + file: /library/Ed Rosenthal_ David Downs - Marijuana Harvest_ How to Maximize Quality + and Yield in Your Cannabis Garden (2017, Quick American Archives) - libgen.li.epub +- title: Ecology of Plants, The - Jessica Gurevitch, Samuel M. Scheiner, Gordon A. + Fox + filename: Ecology of Plants, The - Jessica Gurevitch, Samuel M. Scheiner, Gordon + A. Fox.pdf + description: 'Educational resource: Ecology of Plants, The - Jessica Gurevitch, + Samuel M. Scheiner, Gordon A. Fox' + category: Science + date: '2024-03-25' + file: /library/pdfs/Jessica Gurevitch, Samuel M. Scheiner, Gordon A. Fox/Ecology + of Plants, The/Ecology of Plants, The - Jessica Gurevitch, Samuel M. Scheiner, + Gordon A. Fox.pdf +- title: Hydroponic Food Production - A Definitive Guid Commercial Hydroponic Grower + - Howard M. Resh + filename: Hydroponic Food Production - A Definitive Guid Commercial Hydroponic Grower + - Howard M. Resh.pdf + description: 'Educational resource: Hydroponic Food Production - A Definitive Guid + Commercial Hydroponic Grower - Howard M. Resh' + category: Systems + date: '2024-03-25' + file: /library/pdfs/Resh,rd M_/Hydroponic Food Production - A Definitive Grdener + and the Commercial Hydroponic Grower/Hydroponic Food Production - A Definitive + Guid Commercial Hydroponic Grower - Howard M. Resh.pdf +- title: GROW GREAT WEED Personal & Medical Marijuana Indoor Outdoor Grower Big Bud + Bible - Dale Denton + filename: GROW GREAT WEED_ Personal & Medical Marijuana Indoor_Outdoor Grower Big + Bud Bible - Dale Denton.pdf + description: 'Educational resource: GROW GREAT WEED Personal & Medical Marijuana + Indoor Outdoor Grower Big Bud Bible - Dale Denton' + category: Cultivation + date: '2024-03-25' + file: /library/pdfs/Denton, Dale/GROW GREAT WEED_ Personal & Medical Marijuana Indoor_Outdoor + Grower Big Bud Bible/GROW GREAT WEED_ Personal & Medical Marijuana Indoor_Outdoor + Grower Big Bud Bible - Dale Denton.pdf +- title: Scanned Image - drjones + filename: Scanned Image - drjones.pdf + description: 'Educational resource: Scanned Image - drjones' + category: General + date: '2024-03-25' + file: /library/pdfs/drjones/Scanned Image/Scanned Image - drjones.pdf +- title: 100 Best Cannabis Strains - Michael Blood + filename: 100 Best Cannabis Strains - Michael Blood.pdf + description: 'Educational resource: 100 Best Cannabis Strains - Michael Blood' + category: Cultivation + date: '2024-03-25' + file: /library/pdfs/Blood, Michael/100 Best Cannabis Strains/100 Best Cannabis Strains + - Michael Blood.pdf +- title: downmagaz.net - downmagaz.net + filename: downmagaz.net - downmagaz.net.pdf + description: 'Educational resource: downmagaz.net - downmagaz.net' + category: General + date: '2024-03-25' + file: /library/pdfs/downmagaz.net/downmagaz.net/downmagaz.net - downmagaz.net.pdf +- title: Cannabis Grow Bible, The - Greg Green + filename: Cannabis Grow Bible, The - Greg Green.pdf + description: 'Educational resource: Cannabis Grow Bible, The - Greg Green' + category: Cultivation + date: '2024-03-25' + file: /library/pdfs/Green, Greg/Cannabis Grow Bible, The/Cannabis Grow Bible, The + - Greg Green.pdf +- title: Diagnosing Hemp and Cannabis Crop Diseases by Shouhua Wang - Unknown + filename: Diagnosing Hemp and Cannabis Crop Diseases by Shouhua Wang - Unknown.pdf + description: 'Educational resource: Diagnosing Hemp and Cannabis Crop Diseases by + Shouhua Wang - Unknown' + category: Cultivation + date: '2024-03-25' + file: /library/pdfs/Unknown/Diagnosing Hemp and Cannabis Crop Diseases by Shouhua + Wang/Diagnosing Hemp and Cannabis Crop Diseases by Shouhua Wang - Unknown.pdf +- title: GUIDE TO HYDROPONICS - James Shelby PH.D + filename: GUIDE TO HYDROPONICS - James Shelby PH.D.pdf + description: 'Educational resource: GUIDE TO HYDROPONICS - James Shelby PH.D' + category: Systems + date: '2024-03-25' + file: /library/pdfs/Shelby PH.D, James/GUIDE TO HYDROPONICS/GUIDE TO HYDROPONICS + - James Shelby PH.D.pdf +- title: HYDROPONICS GARDEN - Water, Tyler + filename: HYDROPONICS GARDEN - Water, Tyler.pdf + description: 'Educational resource: HYDROPONICS GARDEN - Water, Tyler' + category: Systems + date: '2024-03-25' + file: /library/pdfs/Water, Tyler/HYDROPONICS GARDEN/HYDROPONICS GARDEN - Water, + Tyler.pdf +- title: How to Grow Marijuana 3 Books in 1 - The Complality Weed Indoors and Outdoors + - Whistler, Tom + filename: How to Grow Marijuana_ 3 Books in 1 - The Complality Weed Indoors and + Outdoors - Whistler, Tom.pdf + description: 'Educational resource: How to Grow Marijuana 3 Books in 1 - The Complality + Weed Indoors and Outdoors - Whistler, Tom' + category: Cultivation + date: '2024-03-25' + file: /library/pdfs/Whis Tom/How to Grow Marijuana_ 3 Books in 1 - The Cowing Top-Quality + Weed Indoors and Outdoors/How to Grow Marijuana_ 3 Books in 1 - The Complality + Weed Indoors and Outdoors - Whistler, Tom.pdf +- title: Hydroponics Hydroponics Gardening Guide - frg, horticulture, Cannabis) - + Thatcher, Thomas + filename: Hydroponics_ Hydroponics Gardening Guide - frg, horticulture, Cannabis) + - Thatcher, Thomas.pdf + description: 'Educational resource: Hydroponics Hydroponics Gardening Guide - frg, + horticulture, Cannabis) - Thatcher, Thomas' + category: Cultivation + date: '2024-03-25' + file: /library/pdfs/Thatchomas/Hydroponics_ Hydroponics Gardening Guide - fresteading, + Gardening, horticulture, Cannabis)/Hydroponics_ Hydroponics Gardening Guide - + frg, horticulture, Cannabis) - Thatcher, Thomas.pdf +- title: Jesse Ventura's Marijuana Manifesto - Jesse Ventura + filename: Jesse Ventura's Marijuana Manifesto - Jesse Ventura.pdf + description: 'Educational resource: Jesse Ventura''s Marijuana Manifesto - Jesse + Ventura' + category: Cultivation + date: '2024-03-25' + file: /library/pdfs/Ventura, Jesse/Jesse Ventura's Marijuana Manifesto/Jesse Ventura's + Marijuana Manifesto - Jesse Ventura.pdf +- title: True Living Organics - The Rev + filename: True Living Organics - The Rev.pdf + description: 'Educational resource: True Living Organics - The Rev' + category: General + date: '2024-03-25' + file: /library/pdfs/Rev, The/True Living Organics/True Living Organics - The Rev.pdf +- title: DIY Home Hydroponics For Beginners The Essentiao Turn Your Backyard Into + A Farm - Sally R. Ball (1) + filename: DIY Home Hydroponics For Beginners_ The Essentiao Turn Your Backyard Into + A Farm - Sally R. Ball (1).pdf + description: 'Educational resource: DIY Home Hydroponics For Beginners The Essentiao + Turn Your Backyard Into A Farm - Sally R. Ball (1)' + category: Systems + date: '2024-03-25' + file: /library/pdfs/Ball, lly R_/DIY Home Hydroponics For Beginners_ The Guide + To Turn Your Backyard Into A Farm/DIY Home Hydroponics For Beginners_ The Essentiao + Turn Your Backyard Into A Farm - Sally R. Ball (1).pdf +- title: DIY Home Hydroponics For Beginners The Essentiao Turn Your Backyard Into + A Farm - Sally R. Ball + filename: DIY Home Hydroponics For Beginners_ The Essentiao Turn Your Backyard Into + A Farm - Sally R. Ball.pdf + description: 'Educational resource: DIY Home Hydroponics For Beginners The Essentiao + Turn Your Backyard Into A Farm - Sally R. Ball' + category: Systems + date: '2024-03-25' + file: /library/pdfs/Ball, lly R_/DIY Home Hydroponics For Beginners_ The Guide + To Turn Your Backyard Into A Farm/DIY Home Hydroponics For Beginners_ The Essentiao + Turn Your Backyard Into A Farm - Sally R. Ball.pdf +- title: What a Plant Knows - Daniel Chamovitz + filename: What a Plant Knows - Daniel Chamovitz.pdf + description: 'Educational resource: What a Plant Knows - Daniel Chamovitz' + category: General + date: '2024-03-25' + file: /library/pdfs/Chamovitz, Daniel/What a Plant Knows/What a Plant Knows - Daniel + Chamovitz.pdf +- title: Emmanuel S. Onaivi - The Biology of Marijuana From Gene to Behavior (2002, + CRC Press) + filename: Emmanuel S. Onaivi - The Biology of Marijuana_ From Gene to Behavior (2002, + CRC Press) - libgen.li.pdf + description: 'Educational resource: Emmanuel S. Onaivi - The Biology of Marijuana From + Gene to Behavior (2002, CRC Press)' + category: Cultivation + date: '2024-03-25' + file: /library/Emmanuel S. Onaivi - The Biology of Marijuana_ From Gene to Behavior + (2002, CRC Press) - libgen.li.pdf +- title: Greg Green - The Cannabis Breeder's Bible The Definitive Guide to Marijuana + Genetics, Cannabis Botany and Creating Strains for the Seed Mark (2005, Green + Candy Press) + filename: Greg Green - The Cannabis Breeder's Bible_ The Definitive Guide to Marijuana + Genetics, Cannabis Botany and Creating Strains for the Seed Mark (2005, Green + Candy Press) - libgen.li.epub + description: 'Educational resource: Greg Green - The Cannabis Breeder''s Bible The + Definitive Guide to Marijuana Genetics, Cannabis Botany and Creating Strains for + the Seed Mark (2005, Green Candy Press)' + category: Cultivation + date: '2024-03-25' + file: /library/Greg Green - The Cannabis Breeder's Bible_ The Definitive Guide to + Marijuana Genetics, Cannabis Botany and Creating Strains for the Seed Mark (2005, + Green Candy Press) - libgen.li.epub +- title: '[20th Century Alchemist] D. Gold (David Hoye) - Cannabis Alchemy The Art + of Modern Hashmaking (1993, Ronin Publishing)' + filename: '[20th Century Alchemist] D. Gold (David Hoye) - Cannabis Alchemy_ The + Art of Modern Hashmaking (1993, Ronin Publishing) - libgen.li.pdf' + description: 'Educational resource: [20th Century Alchemist] D. Gold (David Hoye) + - Cannabis Alchemy The Art of Modern Hashmaking (1993, Ronin Publishing)' + category: Cultivation + date: '2024-03-25' + file: /library/[20th Century Alchemist] D. Gold (David Hoye) - Cannabis Alchemy_ + The Art of Modern Hashmaking (1993, Ronin Publishing) - libgen.li.pdf +- title: Marijuana Botany - An Advanced Study - The Propagation and Breeding of Distinctive + Cannabis + filename: Marijuana Botany - An Advanced Study - The Propagation and Breeding of + Distinctive Cannabis.pdf + description: 'Educational resource: Marijuana Botany - An Advanced Study - The Propagation + and Breeding of Distinctive Cannabis' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Botany - An Advanced Study - The Propagation and Breeding + of Distinctive Cannabis.pdf +- title: 'DIY Home Hydroponics For Beginners ' + filename: DIY Home Hydroponics For Beginners .epub + description: 'Educational resource: DIY Home Hydroponics For Beginners ' + category: Systems + date: '2024-03-25' + file: /library/DIY Home Hydroponics For Beginners .epub +- title: Diagnosing Hemp and Cannabis Crop Diseases by Shouhua Wang + filename: Diagnosing Hemp and Cannabis Crop Diseases by Shouhua Wang.pdf + description: 'Educational resource: Diagnosing Hemp and Cannabis Crop Diseases by + Shouhua Wang' + category: Cultivation + date: '2024-03-25' + file: /library/Diagnosing Hemp and Cannabis Crop Diseases by Shouhua Wang.pdf +- title: '[Handbooks in psychopharmacology] Pertwee, Roger G - Handbook of cannabis + (2016, Oxford University Press)' + filename: '[Handbooks in psychopharmacology] Pertwee, Roger G - Handbook of cannabis + (2016, Oxford University Press) - libgen.li.pdf' + description: 'Educational resource: [Handbooks in psychopharmacology] Pertwee, Roger + G - Handbook of cannabis (2016, Oxford University Press)' + category: Cultivation + date: '2024-03-25' + file: /library/[Handbooks in psychopharmacology] Pertwee, Roger G - Handbook of + cannabis (2016, Oxford University Press) - libgen.li.pdf +- title: T. Asao - Hydroponics -A Standard Methodology for Plant Biological Researches-Intech + (2012) + filename: T. Asao - Hydroponics -A Standard Methodology for Plant Biological Researches-Intech + (2012).pdf + description: 'Educational resource: T. Asao - Hydroponics -A Standard Methodology + for Plant Biological Researches-Intech (2012)' + category: Systems + date: '2024-03-25' + file: /library/T. Asao - Hydroponics -A Standard Methodology for Plant Biological + Researches-Intech (2012).pdf +- title: Oner, S. T - Cannabis Sativa. Volume 3, The essential guide to the world's + finest marijuana strains (2014, Green Candy Press,Perseus Books, LLC) + filename: Oner, S. T - Cannabis Sativa. Volume 3, The essential guide to the world's + finest marijuana strains (2014, Green Candy Press,Perseus Books, LLC) - libgen.li.epub + description: 'Educational resource: Oner, S. T - Cannabis Sativa. Volume 3, The + essential guide to the world''s finest marijuana strains (2014, Green Candy Press,Perseus + Books, LLC)' + category: Cultivation + date: '2024-03-25' + file: /library/Oner, S. T - Cannabis Sativa. Volume 3, The essential guide to the + world's finest marijuana strains (2014, Green Candy Press,Perseus Books, LLC) + - libgen.li.epub +- title: Growing Marijuana Mastery The Easiest and Complete Handbook to Growing Weed + Indoor and Outdoor – Your Weed Growers Guide With Secrets for Big Bug Harvest + filename: Growing Marijuana Mastery_ The Easiest and Complete Handbook to Growing + Weed Indoor and Outdoor – Your Weed Growers Guide With Secrets for Big Bug Harvest.pdf + description: 'Educational resource: Growing Marijuana Mastery The Easiest and Complete + Handbook to Growing Weed Indoor and Outdoor – Your Weed Growers Guide With Secrets + for Big Bug Harvest' + category: Cultivation + date: '2024-03-25' + file: /library/Growing Marijuana Mastery_ The Easiest and Complete Handbook to Growing + Weed Indoor and Outdoor – Your Weed Growers Guide With Secrets for Big Bug Harvest.pdf +- title: George Van Patten - Gardening Indoors with Soil & Hydroponics , Fifth Edition + (2007, Van Patten Publishing) + filename: George Van Patten - Gardening Indoors with Soil & Hydroponics , Fifth + Edition (2007, Van Patten Publishing) - libgen.li.pdf + description: 'Educational resource: George Van Patten - Gardening Indoors with Soil + & Hydroponics , Fifth Edition (2007, Van Patten Publishing)' + category: Systems + date: '2024-03-25' + file: /library/George Van Patten - Gardening Indoors with Soil & Hydroponics , Fifth + Edition (2007, Van Patten Publishing) - libgen.li.pdf +- title: Mahmoud A. ElSohly (editor) - Cannabis Chemistry and Biology Fundamentals + (2023, De Gruyter) [10.1515 9783110718362] + filename: Mahmoud A. ElSohly (editor) - Cannabis Chemistry and Biology_ Fundamentals + (2023, De Gruyter) [10.1515_9783110718362] - libgen.li.pdf + description: 'Educational resource: Mahmoud A. ElSohly (editor) - Cannabis Chemistry + and Biology Fundamentals (2023, De Gruyter) [10.1515 9783110718362]' + category: Cultivation + date: '2024-03-25' + file: /library/Mahmoud A. ElSohly (editor) - Cannabis Chemistry and Biology_ Fundamentals + (2023, De Gruyter) [10.1515_9783110718362] - libgen.li.pdf +- title: Basil John Mason, Lyn Morgan, Peta Jinnath Abdul, Marie Beerman - Nutrients + for Hydroponics Tissue Culture (2018) + filename: Basil John Mason, Lyn Morgan, Peta Jinnath Abdul, Marie Beerman - Nutrients + for Hydroponics Tissue Culture (2018) - libgen.li.pdf + description: 'Educational resource: Basil John Mason, Lyn Morgan, Peta Jinnath Abdul, + Marie Beerman - Nutrients for Hydroponics Tissue Culture (2018)' + category: Systems + date: '2024-03-25' + file: /library/Basil John Mason, Lyn Morgan, Peta Jinnath Abdul, Marie Beerman - + Nutrients for Hydroponics Tissue Culture (2018) - libgen.li.pdf +- title: Cervantes, Jorge - Marijuana Grow Basics The Easy Guide for Cannabis Aficionados + (2009, Van Patten Publishing) + filename: Cervantes, Jorge - Marijuana Grow Basics_ The Easy Guide for Cannabis + Aficionados (2009, Van Patten Publishing) - libgen.li.pdf + description: 'Educational resource: Cervantes, Jorge - Marijuana Grow Basics The + Easy Guide for Cannabis Aficionados (2009, Van Patten Publishing)' + category: Cultivation + date: '2024-03-25' + file: /library/Cervantes, Jorge - Marijuana Grow Basics_ The Easy Guide for Cannabis + Aficionados (2009, Van Patten Publishing) - libgen.li.pdf +- title: DIY Home Hydroponics For Beginners - The Essential Guide To Turn Your Backyard + Into A Farm + filename: DIY Home Hydroponics For Beginners - The Essential Guide To Turn Your + Backyard Into A Farm.epub + description: 'Educational resource: DIY Home Hydroponics For Beginners - The Essential + Guide To Turn Your Backyard Into A Farm' + category: Systems + date: '2024-03-25' + file: /library/DIY Home Hydroponics For Beginners - The Essential Guide To Turn + Your Backyard Into A Farm.epub +- title: 100 Best Cannabis Strains - A Pocket Guide for Medicinal and Recreational + Use + filename: 100 Best Cannabis Strains - A Pocket Guide for Medicinal and Recreational + Use.epub + description: 'Educational resource: 100 Best Cannabis Strains - A Pocket Guide for + Medicinal and Recreational Use' + category: Cultivation + date: '2024-03-25' + file: /library/100 Best Cannabis Strains - A Pocket Guide for Medicinal and Recreational + Use.epub +- title: '#4 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible' + filename: '#4 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: #4 Marijuana Horticulture - The Indoor-Outdoor + Medical Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Horticulture - The Indoor & Outdoor Medical Growers Bible/#4 + Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: '#5 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible' + filename: '#5 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: #5 Marijuana Horticulture - The Indoor-Outdoor + Medical Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Horticulture - The Indoor & Outdoor Medical Growers Bible/#5 + Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: '#6 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible' + filename: '#6 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: #6 Marijuana Horticulture - The Indoor-Outdoor + Medical Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Horticulture - The Indoor & Outdoor Medical Growers Bible/#6 + Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: '#1 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible' + filename: '#1 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: #1 Marijuana Horticulture - The Indoor-Outdoor + Medical Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Horticulture - The Indoor & Outdoor Medical Growers Bible/#1 + Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: '#2 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible' + filename: '#2 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: #2 Marijuana Horticulture - The Indoor-Outdoor + Medical Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Horticulture - The Indoor & Outdoor Medical Growers Bible/#2 + Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: '#3 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible' + filename: '#3 Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf' + description: 'Educational resource: #3 Marijuana Horticulture - The Indoor-Outdoor + Medical Growers Bible' + category: Cultivation + date: '2024-03-25' + file: /library/Marijuana Horticulture - The Indoor & Outdoor Medical Growers Bible/#3 + Marijuana Horticulture - The Indoor-Outdoor Medical Growers Bible.pdf +- title: Jesse Ventura's Marijuana Manifesto + filename: Jesse Ventura's Marijuana Manifesto.epub + description: 'Educational resource: Jesse Ventura''s Marijuana Manifesto' + category: Cultivation + date: '2024-03-25' + file: /library/Jesse Ventura's Marijuana Manifesto.epub +- title: GROW GREAT WEED Personal & Med - Dale Denton + filename: GROW GREAT WEED_ Personal & Med - Dale Denton.pdf + description: 'Educational resource: GROW GREAT WEED Personal & Med - Dale Denton' + category: Cultivation + date: '2024-03-25' + file: /library/GROW GREAT WEED_ Personal & Med - Dale Denton.pdf +- title: Hydroponics Gardening Guide + filename: Hydroponics Gardening Guide.epub + description: 'Educational resource: Hydroponics Gardening Guide' + category: Systems + date: '2024-03-25' + file: /library/Hydroponics Gardening Guide.epub +- title: How to Grow Marijuana - 3 Boo... + filename: How to Grow Marijuana - 3 Boo....pdf + description: 'Educational resource: How to Grow Marijuana - 3 Boo...' + category: Cultivation + date: '2024-03-25' + file: /library/How to Grow Marijuana - 3 Boo....pdf +- title: Ed Rosenthal Robert Flannery Angela Bacca Tommy Chong - Cannabis Grower's + Handbook The Complete Guide to Marijuana and Hemp Cultivation (2021, Quick American + Archives) + filename: Ed Rosenthal_ Robert Flannery_ Angela Bacca_ Tommy Chong - Cannabis Grower's + Handbook_ The Complete Guide to Marijuana and Hemp Cultivation (2021, Quick American + Archives) - libgen.li_ocr.pdf + description: 'Educational resource: Ed Rosenthal Robert Flannery Angela Bacca Tommy + Chong - Cannabis Grower''s Handbook The Complete Guide to Marijuana and Hemp + Cultivation (2021, Quick American Archives)' + category: Cultivation + date: '2024-03-25' + file: /library/Ed Rosenthal_ Robert Flannery_ Angela Bacca_ Tommy Chong - Cannabis + Grower's Handbook_ The Complete Guide to Marijuana and Hemp Cultivation (2021, + Quick American Archives) - libgen.li_ocr.pdf +- title: '[Big of Buds] Ed Rosenthal - The Big Book of Buds More Marijuana Varieties + from the World''s Greatest Seed Breeders (2007, Quick Trading Company Ed Rosenthal)' + filename: '[Big of Buds] Ed Rosenthal - The Big Book of Buds_ More Marijuana Varieties + from the World''s Greatest Seed Breeders (2007, Quick Trading Company_Ed Rosenthal) + - libgen.li.epub' + description: 'Educational resource: [Big of Buds] Ed Rosenthal - The Big Book of + Buds More Marijuana Varieties from the World''s Greatest Seed Breeders (2007, + Quick Trading Company Ed Rosenthal)' + category: Cultivation + date: '2024-03-25' + file: /library/[Big of Buds] Ed Rosenthal - The Big Book of Buds_ More Marijuana + Varieties from the World's Greatest Seed Breeders (2007, Quick Trading Company_Ed + Rosenthal) - libgen.li.epub +- title: Storm - Marijuana Hydroponics High-Tech Water Culture (2016, Ronin Publishing) + filename: Storm - Marijuana Hydroponics_ High-Tech Water Culture (2016, Ronin Publishing) + - libgen.li.epub + description: 'Educational resource: Storm - Marijuana Hydroponics High-Tech Water + Culture (2016, Ronin Publishing)' + category: Cultivation + date: '2024-03-25' + file: /library/Storm - Marijuana Hydroponics_ High-Tech Water Culture (2016, Ronin + Publishing) - libgen.li.epub +- title: Ed Rosenthal - Closet Cultivator Indoor Marijuana Cultivation Made Easy + (1999, Quick American Archives) + filename: Ed Rosenthal - Closet Cultivator_ Indoor Marijuana Cultivation Made Easy + (1999, Quick American Archives) - libgen.li.pdf + description: 'Educational resource: Ed Rosenthal - Closet Cultivator Indoor Marijuana + Cultivation Made Easy (1999, Quick American Archives)' + category: Cultivation + date: '2024-03-25' + file: /library/Ed Rosenthal - Closet Cultivator_ Indoor Marijuana Cultivation Made + Easy (1999, Quick American Archives) - libgen.li.pdf +- title: What a Plant Knows - A Field Guide to the Senses By Daniel Chamovitz + filename: What a Plant Knows - A Field Guide to the Senses By Daniel Chamovitz.epub + description: 'Educational resource: What a Plant Knows - A Field Guide to the Senses + By Daniel Chamovitz' + category: General + date: '2024-03-25' + file: /library/What a Plant Knows - A Field Guide to the Senses By Daniel Chamovitz.epub +- title: Youbin Zheng - Handbook of Cannabis Production in Controlled Environments + (2022, CRC Press) + filename: Youbin Zheng - Handbook of Cannabis Production in Controlled Environments + (2022, CRC Press) - libgen.li.pdf + description: 'Educational resource: Youbin Zheng - Handbook of Cannabis Production + in Controlled Environments (2022, CRC Press)' + category: Cultivation + date: '2024-03-25' + file: /library/Youbin Zheng - Handbook of Cannabis Production in Controlled Environments + (2022, CRC Press) - libgen.li.pdf +- title: Gurevitch J. The Ecology of Plants 3ed 2020 + filename: Gurevitch J. The Ecology of Plants 3ed 2020.pdf + description: 'Educational resource: Gurevitch J. The Ecology of Plants 3ed 2020' + category: Science + date: '2024-03-25' + file: /library/Gurevitch J. The Ecology of Plants 3ed 2020.pdf +- title: alfonso Garcia - Growing Marijuana Mastery The Advanced Guide to grow Cannabis Tips + and Techniques for Advanced Cultivation of Medical Marijua (2020, Alfonso Garcia) + filename: alfonso Garcia - Growing Marijuana Mastery_ The Advanced Guide to grow + Cannabis_ Tips and Techniques for Advanced Cultivation of Medical Marijua (2020, + Alfonso Garcia) - libgen.li.epub + description: 'Educational resource: alfonso Garcia - Growing Marijuana Mastery The + Advanced Guide to grow Cannabis Tips and Techniques for Advanced Cultivation + of Medical Marijua (2020, Alfonso Garcia)' + category: Cultivation + date: '2024-03-25' + file: /library/alfonso Garcia - Growing Marijuana Mastery_ The Advanced Guide to + grow Cannabis_ Tips and Techniques for Advanced Cultivation of Medical Marijua + (2020, Alfonso Garcia) - libgen.li.epub +- title: Robert Connell Clarke - Marijuana Botany - An Advanced Study The Propagation + and Breeding of Distinctive Cannabis (0) + filename: Robert Connell Clarke - Marijuana Botany - An Advanced Study_ The Propagation + and Breeding of Distinctive Cannabis (0) - libgen.li.pdf + description: 'Educational resource: Robert Connell Clarke - Marijuana Botany - An + Advanced Study The Propagation and Breeding of Distinctive Cannabis (0)' + category: Cultivation + date: '2024-03-25' + file: /library/Robert Connell Clarke - Marijuana Botany - An Advanced Study_ The + Propagation and Breeding of Distinctive Cannabis (0) - libgen.li.pdf +- title: Ed Rosenthal Robert Flannery Angela Bacca Tommy Chong - Cannabis Grower's + Handbook The Complete Guide to Marijuana and Hemp Cultivation (2021, Quick American + Archives) + filename: Ed Rosenthal_ Robert Flannery_ Angela Bacca_ Tommy Chong - Cannabis Grower's + Handbook_ The Complete Guide to Marijuana and Hemp Cultivation (2021, Quick American + Archives) - libgen.li.epub + description: 'Educational resource: Ed Rosenthal Robert Flannery Angela Bacca Tommy + Chong - Cannabis Grower''s Handbook The Complete Guide to Marijuana and Hemp + Cultivation (2021, Quick American Archives)' + category: Cultivation + date: '2024-03-25' + file: /library/Ed Rosenthal_ Robert Flannery_ Angela Bacca_ Tommy Chong - Cannabis + Grower's Handbook_ The Complete Guide to Marijuana and Hemp Cultivation (2021, + Quick American Archives) - libgen.li.epub +- title: True Living Organics - The Ultimate Guide to Growing All-Natural Marijuana + Indoors (2012) + filename: True Living Organics - The Ultimate Guide to Growing All-Natural Marijuana + Indoors (2012).epub + description: 'Educational resource: True Living Organics - The Ultimate Guide to + Growing All-Natural Marijuana Indoors (2012)' + category: Cultivation + date: '2024-03-25' + file: /library/True Living Organics - The Ultimate Guide to Growing All-Natural + Marijuana Indoors (2012).epub +- title: Michael Starks - Marijuana Chemistry-Ronin Publishing (1993) + filename: Michael Starks - Marijuana Chemistry-Ronin Publishing (1993).pdf + description: 'Educational resource: Michael Starks - Marijuana Chemistry-Ronin Publishing + (1993)' + category: Cultivation + date: '2024-03-25' + file: /library/Michael Starks - Marijuana Chemistry-Ronin Publishing (1993).pdf +- title: Kleier C. Plant Science. An Introduction to Botany (Course Guidebook) 2017 + filename: Kleier C. Plant Science. An Introduction to Botany (Course Guidebook) + 2017.pdf + description: 'Educational resource: Kleier C. Plant Science. An Introduction to + Botany (Course Guidebook) 2017' + category: Science + date: '2024-03-25' + file: /library/Kleier C. Plant Science. An Introduction to Botany (Course Guidebook) + 2017.pdf +- title: Hydroponic Food Production - A Definitive Guidebook (7th Ed)(gnv64) + filename: Hydroponic Food Production - A Definitive Guidebook (7th Ed)(gnv64).pdf + description: 'Educational resource: Hydroponic Food Production - A Definitive Guidebook + (7th Ed)(gnv64)' + category: Systems + date: '2024-03-25' + file: /library/Hydroponic Food Production - A Definitive Guidebook (7th Ed)(gnv64).pdf +- title: Dan Michaels - Higher The Lore, Legends, and Legacy of Cannabis (2023, Ten + Speed Press) + filename: Dan Michaels - Higher_ The Lore, Legends, and Legacy of Cannabis (2023, + Ten Speed Press) - libgen.li.epub + description: 'Educational resource: Dan Michaels - Higher The Lore, Legends, and + Legacy of Cannabis (2023, Ten Speed Press)' + category: Cultivation + date: '2024-03-25' + file: /library/Dan Michaels - Higher_ The Lore, Legends, and Legacy of Cannabis + (2023, Ten Speed Press) - libgen.li.epub +- title: MM Horticulture complete + filename: MM Horticulture complete.pdf + description: 'Educational resource: MM Horticulture complete' + category: General + date: '2024-03-25' + file: /library/MM Horticulture complete.pdf +- title: John Mason - Commercial Hydroponics 3rd Edition + filename: John Mason - Commercial Hydroponics 3rd Edition.pdf + description: 'Educational resource: John Mason - Commercial Hydroponics 3rd Edition' + category: Systems + date: '2024-03-25' + file: /library/John Mason - Commercial Hydroponics 3rd Edition.pdf +- title: Ross Tillman - DIY Aquaponics Marijuana Growing at Home Learn to Grow Most + Potent THC Cannabis Ever! (2019, Grelot Books Publishing Company) + filename: Ross Tillman - DIY Aquaponics Marijuana Growing at Home Learn to Grow + Most Potent THC Cannabis Ever! (2019, Grelot Books Publishing Company) - libgen.li.epub + description: 'Educational resource: Ross Tillman - DIY Aquaponics Marijuana Growing + at Home Learn to Grow Most Potent THC Cannabis Ever! (2019, Grelot Books Publishing + Company)' + category: Cultivation + date: '2024-03-25' + file: /library/Ross Tillman - DIY Aquaponics Marijuana Growing at Home Learn to + Grow Most Potent THC Cannabis Ever! (2019, Grelot Books Publishing Company) - + libgen.li.epub diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..0d3542e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2916 @@ +{ + "name": "hydro-sterile", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "hydro-sterile", + "version": "1.0.0", + "license": "ISC", + "devDependencies": { + "@tailwindcss/postcss": "^4.1.18", + "autoprefixer": "^10.4.23", + "hugo-bin": "^0.149.2", + "postcss": "^8.5.6", + "postcss-cli": "^11.0.1", + "tailwindcss": "^4.1.18" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@borewit/text-codec": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.1.1.tgz", + "integrity": "sha512-5L/uBxmjaCIX5h8Z+uu+kA9BQLkc/Wl06UGR5ajNRxu+/XjonB5i8JpgFMrPj3LXTCPA0pv8yxUvbUi+QthGGA==", + "dev": true, + "license": "MIT", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.13", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", + "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.0", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/remapping": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz", + "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.5", + "@jridgewell/trace-mapping": "^0.3.24" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", + "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.5.5", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz", + "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==", + "dev": true, + "license": "MIT" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.31", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@sindresorhus/is": { + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-5.6.0.tgz", + "integrity": "sha512-TV7t8GKYaJWsn00tFDqBw8+Uqmr8A0fRU1tvTQhyZzGv0sJCGRQL3JGMI3ucuKo3XIZdUP+Lx7/gh2t3lewy7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sindresorhus/is?sponsor=1" + } + }, + "node_modules/@szmarczak/http-timer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-5.0.1.tgz", + "integrity": "sha512-+PmQX0PiAYPMeVYe237LJAYvOMYW1j2rH5YROyS3b4CTVJum34HfRvKvAzozHAQG0TnHNdUfY9nCeUyRAs//cw==", + "dev": true, + "license": "MIT", + "dependencies": { + "defer-to-connect": "^2.0.1" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/@tailwindcss/node": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.1.18.tgz", + "integrity": "sha512-DoR7U1P7iYhw16qJ49fgXUlry1t4CpXeErJHnQ44JgTSKMaZUdf17cfn5mHchfJ4KRBZRFA/Coo+MUF5+gOaCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/remapping": "^2.3.4", + "enhanced-resolve": "^5.18.3", + "jiti": "^2.6.1", + "lightningcss": "1.30.2", + "magic-string": "^0.30.21", + "source-map-js": "^1.2.1", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.1.18.tgz", + "integrity": "sha512-EgCR5tTS5bUSKQgzeMClT6iCY3ToqE1y+ZB0AKldj809QXk1Y+3jB0upOYZrn9aGIzPtUsP7sX4QQ4XtjBB95A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-arm64": "4.1.18", + "@tailwindcss/oxide-darwin-x64": "4.1.18", + "@tailwindcss/oxide-freebsd-x64": "4.1.18", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.1.18", + "@tailwindcss/oxide-linux-arm64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-arm64-musl": "4.1.18", + "@tailwindcss/oxide-linux-x64-gnu": "4.1.18", + "@tailwindcss/oxide-linux-x64-musl": "4.1.18", + "@tailwindcss/oxide-wasm32-wasi": "4.1.18", + "@tailwindcss/oxide-win32-arm64-msvc": "4.1.18", + "@tailwindcss/oxide-win32-x64-msvc": "4.1.18" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.1.18.tgz", + "integrity": "sha512-dJHz7+Ugr9U/diKJA0W6N/6/cjI+ZTAoxPf9Iz9BFRF2GzEX8IvXxFIi/dZBloVJX/MZGvRuFA9rqwdiIEZQ0Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.1.18.tgz", + "integrity": "sha512-Gc2q4Qhs660bhjyBSKgq6BYvwDz4G+BuyJ5H1xfhmDR3D8HnHCmT/BSkvSL0vQLy/nkMLY20PQ2OoYMO15Jd0A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.1.18.tgz", + "integrity": "sha512-FL5oxr2xQsFrc3X9o1fjHKBYBMD1QZNyc1Xzw/h5Qu4XnEBi3dZn96HcHm41c/euGV+GRiXFfh2hUCyKi/e+yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.1.18.tgz", + "integrity": "sha512-Fj+RHgu5bDodmV1dM9yAxlfJwkkWvLiRjbhuO2LEtwtlYlBgiAT4x/j5wQr1tC3SANAgD+0YcmWVrj8R9trVMA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.1.18.tgz", + "integrity": "sha512-Fp+Wzk/Ws4dZn+LV2Nqx3IilnhH51YZoRaYHQsVq3RQvEl+71VGKFpkfHrLM/Li+kt5c0DJe/bHXK1eHgDmdiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.1.18.tgz", + "integrity": "sha512-S0n3jboLysNbh55Vrt7pk9wgpyTTPD0fdQeh7wQfMqLPM/Hrxi+dVsLsPrycQjGKEQk85Kgbx+6+QnYNiHalnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.1.18.tgz", + "integrity": "sha512-1px92582HkPQlaaCkdRcio71p8bc8i/ap5807tPRDK/uw953cauQBT8c5tVGkOwrHMfc2Yh6UuxaH4vtTjGvHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.1.18.tgz", + "integrity": "sha512-v3gyT0ivkfBLoZGF9LyHmts0Isc8jHZyVcbzio6Wpzifg/+5ZJpDiRiUhDLkcr7f/r38SWNe7ucxmGW3j3Kb/g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.1.18.tgz", + "integrity": "sha512-bhJ2y2OQNlcRwwgOAGMY0xTFStt4/wyU6pvI6LSuZpRgKQwxTec0/3Scu91O8ir7qCR3AuepQKLU/kX99FouqQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-wasm32-wasi": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-wasm32-wasi/-/oxide-wasm32-wasi-4.1.18.tgz", + "integrity": "sha512-LffYTvPjODiP6PT16oNeUQJzNVyJl1cjIebq/rWWBF+3eDst5JGEFSc5cWxyRCJ0Mxl+KyIkqRxk1XPEs9x8TA==", + "bundleDependencies": [ + "@napi-rs/wasm-runtime", + "@emnapi/core", + "@emnapi/runtime", + "@tybys/wasm-util", + "@emnapi/wasi-threads", + "tslib" + ], + "cpu": [ + "wasm32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.7.1", + "@emnapi/runtime": "^1.7.1", + "@emnapi/wasi-threads": "^1.1.0", + "@napi-rs/wasm-runtime": "^1.1.0", + "@tybys/wasm-util": "^0.10.1", + "tslib": "^2.4.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.18.tgz", + "integrity": "sha512-HjSA7mr9HmC8fu6bdsZvZ+dhjyGCLdotjVOgLA2vEqxEBZaQo9YTX4kwgEvPCpRh8o4uWc4J/wEoFzhEmjvPbA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.1.18.tgz", + "integrity": "sha512-bJWbyYpUlqamC8dpR7pfjA0I7vdF6t5VpUGMWRkXVE3AXgIZjYUYAK7II1GNaxR8J1SSrSrppRar8G++JekE3Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.1.18.tgz", + "integrity": "sha512-Ce0GFnzAOuPyfV5SxjXGn0CubwGcuDB0zcdaPuCSzAa/2vII24JTkH+I6jcbXLb1ctjZMZZI6OjDaLPJQL1S0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "4.1.18", + "@tailwindcss/oxide": "4.1.18", + "postcss": "^8.4.41", + "tailwindcss": "4.1.18" + } + }, + "node_modules/@tokenizer/inflate": { + "version": "0.2.7", + "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.2.7.tgz", + "integrity": "sha512-MADQgmZT1eKjp06jpI2yozxaU9uVs4GzzgSL+uEq7bVcJ9V1ZXQkeGNql1fsSI0gMy1vhvNTNbUqrx+pZfJVmg==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "fflate": "^0.8.2", + "token-types": "^6.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/@tokenizer/token": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@tokenizer/token/-/token-0.3.0.tgz", + "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", + "dev": true, + "license": "MIT" + }, + "node_modules/@types/http-cache-semantics": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", + "integrity": "sha512-1m0bIFVc7eJWyve9S0RnuRgcQqF/Xd5QsUZAZeQFr1Q3/p9JWoQQEqmVy+DPTNpGXwhgIetAoYF8JSc33q29QA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@xhmikosr/archive-type": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/archive-type/-/archive-type-7.1.0.tgz", + "integrity": "sha512-xZEpnGplg1sNPyEgFh0zbHxqlw5dtYg6viplmWSxUj12+QjU9SKu3U/2G73a15pEjLaOqTefNSZ1fOPUOT4Xgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^20.5.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/bin-check": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-check/-/bin-check-7.1.0.tgz", + "integrity": "sha512-y1O95J4mnl+6MpVmKfMYXec17hMEwE/yeCglFNdx+QvLLtP0yN4rSYcbkXnth+lElBuKKek2NbvOfOGPpUXCvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.1.1", + "isexe": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/bin-wrapper": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/bin-wrapper/-/bin-wrapper-13.2.0.tgz", + "integrity": "sha512-t9U9X0sDPRGDk5TGx4dv5xiOvniVJpXnfTuynVKwHgtib95NYEw4MkZdJqhoSiz820D9m0o6PCqOPMXz0N9fIw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/bin-check": "^7.1.0", + "@xhmikosr/downloader": "^15.2.0", + "@xhmikosr/os-filter-obj": "^3.0.0", + "bin-version-check": "^5.1.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress/-/decompress-10.2.0.tgz", + "integrity": "sha512-MmDBvu0+GmADyQWHolcZuIWffgfnuTo4xpr2I/Qw5Ox0gt+e1Be7oYqJM4te5ylL6mzlcoicnHVDvP27zft8tg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^8.1.0", + "@xhmikosr/decompress-tarbz2": "^8.1.0", + "@xhmikosr/decompress-targz": "^8.1.0", + "@xhmikosr/decompress-unzip": "^7.1.0", + "graceful-fs": "^4.2.11", + "strip-dirs": "^3.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tar": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tar/-/decompress-tar-8.1.0.tgz", + "integrity": "sha512-m0q8x6lwxenh1CrsTby0Jrjq4vzW/QU1OLhTHMQLEdHpmjR1lgahGz++seZI0bXF3XcZw3U3xHfqZSz+JPP2Gg==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^20.5.0", + "is-stream": "^2.0.1", + "tar-stream": "^3.1.7" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-tarbz2": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-tarbz2/-/decompress-tarbz2-8.1.0.tgz", + "integrity": "sha512-aCLfr3A/FWZnOu5eqnJfme1Z1aumai/WRw55pCvBP+hCGnTFrcpsuiaVN5zmWTR53a8umxncY2JuYsD42QQEbw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^20.5.0", + "is-stream": "^2.0.1", + "seek-bzip": "^2.0.0", + "unbzip2-stream": "^1.4.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-targz": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-targz/-/decompress-targz-8.1.0.tgz", + "integrity": "sha512-fhClQ2wTmzxzdz2OhSQNo9ExefrAagw93qaG1YggoIz/QpI7atSRa7eOHv4JZkpHWs91XNn8Hry3CwUlBQhfPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/decompress-tar": "^8.0.1", + "file-type": "^20.5.0", + "is-stream": "^2.0.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/decompress-unzip": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/decompress-unzip/-/decompress-unzip-7.1.0.tgz", + "integrity": "sha512-oqTYAcObqTlg8owulxFTqiaJkfv2SHsxxxz9Wg4krJAHVzGWlZsU8tAB30R6ow+aHrfv4Kub6WQ8u04NWVPUpA==", + "dev": true, + "license": "MIT", + "dependencies": { + "file-type": "^20.5.0", + "get-stream": "^6.0.1", + "yauzl": "^3.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/downloader": { + "version": "15.2.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/downloader/-/downloader-15.2.0.tgz", + "integrity": "sha512-lAqbig3uRGTt0sHNIM4vUG9HoM+mRl8K28WuYxyXLCUT6pyzl4Y4i0LZ3jMEsCYZ6zjPZbO9XkG91OSTd4si7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/archive-type": "^7.1.0", + "@xhmikosr/decompress": "^10.2.0", + "content-disposition": "^0.5.4", + "defaults": "^2.0.2", + "ext-name": "^5.0.0", + "file-type": "^20.5.0", + "filenamify": "^6.0.0", + "get-stream": "^6.0.1", + "got": "^13.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@xhmikosr/os-filter-obj": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@xhmikosr/os-filter-obj/-/os-filter-obj-3.0.0.tgz", + "integrity": "sha512-siPY6BD5dQ2SZPl3I0OZBHL27ZqZvLEosObsZRQ1NUB8qcxegwt0T9eKtV96JMFQpIz1elhkzqOg4c/Ri6Dp9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "arch": "^3.0.0" + }, + "engines": { + "node": "^14.14.0 || >=16.0.0" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arch": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/arch/-/arch-3.0.0.tgz", + "integrity": "sha512-AmIAC+Wtm2AU8lGfTtHsw0Y9Qtftx2YXEEtiBP10xFUtMOA+sHHx6OAddyL52mUKh1vsXQ6/w1mVDptZCyUt4Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/autoprefixer": { + "version": "10.4.23", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.23.tgz", + "integrity": "sha512-YYTXSFulfwytnjAPlw8QHncHJmlvFKtczb8InXaAx9Q0LbfDnfEYDE55omerIJKihhmU61Ft+cAOSzQVaBUmeA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "browserslist": "^4.28.1", + "caniuse-lite": "^1.0.30001760", + "fraction.js": "^5.3.4", + "picocolors": "^1.1.1", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.2.tgz", + "integrity": "sha512-riJjyv1/mHLIPX4RwiK+oW9/4c3TEUeORHKefKAKnZ5kyslbN+HXowtbaVEqt4IMUB7OXlfixcs6gsFeo/jhiQ==", + "dev": true, + "license": "Apache-2.0", + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/baseline-browser-mapping": { + "version": "2.9.11", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.9.11.tgz", + "integrity": "sha512-Sg0xJUNDU1sJNGdfGWhVHX0kkZ+HWcvmVymJbj6NSgZZmW/8S9Y2HQ5euytnIgakgxN6papOAWiwDo1ctFDcoQ==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "baseline-browser-mapping": "dist/cli.js" + } + }, + "node_modules/bin-version": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bin-version/-/bin-version-6.0.0.tgz", + "integrity": "sha512-nk5wEsP4RiKjG+vF+uG8lFsEn4d7Y6FVDamzzftSunXOoOcOOkzcWdKVlGgFFwlUQCj63SgnUkLLGF8v7lufhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "execa": "^5.0.0", + "find-versions": "^5.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bin-version-check": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/bin-version-check/-/bin-version-check-5.1.0.tgz", + "integrity": "sha512-bYsvMqJ8yNGILLz1KP9zKLzQ6YpljV3ln1gqhuLkUtyfGi3qXKGuK2p+U4NAvjVFzDFiBBtOpCOSFNuYYEGZ5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bin-version": "^6.0.0", + "semver": "^7.5.3", + "semver-truncate": "^3.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.28.1", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz", + "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "baseline-browser-mapping": "^2.9.0", + "caniuse-lite": "^1.0.30001759", + "electron-to-chromium": "^1.5.263", + "node-releases": "^2.0.27", + "update-browserslist-db": "^1.2.0" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/cacheable-lookup": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-7.0.0.tgz", + "integrity": "sha512-+qJyx4xiKra8mZrcwhjMRMUhD5NR1R8esPkzIYxX96JiecFoxAXFuz/GpR3+ev4PE1WamHip78wV0vcmPQtp8w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + } + }, + "node_modules/cacheable-request": { + "version": "10.2.14", + "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-10.2.14.tgz", + "integrity": "sha512-zkDT5WAF4hSSoUgyfg5tFIxz8XQK+25W/TLVojJTMKBaxevLBBtLxgqguAuVQB8PVW79FVjHcU+GJ9tVbDZ9mQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/http-cache-semantics": "^4.0.2", + "get-stream": "^6.0.1", + "http-cache-semantics": "^4.1.1", + "keyv": "^4.5.3", + "mimic-response": "^4.0.0", + "normalize-url": "^8.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001761", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001761.tgz", + "integrity": "sha512-JF9ptu1vP2coz98+5051jZ4PwQgd2ni8A+gYSN7EA7dPKIMf0pDlSUxhdmVOaV3/fYK5uWBkgSXJaRLr4+3A6g==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "CC-BY-4.0" + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "license": "MIT" + }, + "node_modules/commander": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", + "integrity": "sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/decompress-response/node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defaults": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-2.0.2.tgz", + "integrity": "sha512-cuIw0PImdp76AOfgkjbW4VhQODRmNNcKR73vdCH5cLd/ifj7aamfoXvYgfGkEAjNJZ3ozMIy9Gu2LutUkGEPbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/defer-to-connect": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", + "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/dependency-graph": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-1.0.0.tgz", + "integrity": "sha512-cW3gggJ28HZ/LExwxP2B++aiKxhJXMSIt9K48FOXQkm+vuG5gyatXnLsONRJdzO/7VfjDIiaOOa/bs4l464Lwg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.5.267", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.267.tgz", + "integrity": "sha512-0Drusm6MVRXSOJpGbaSVgcQsuB4hEkMpHXaVstcPmhu5LIedxs1xNK/nIxmQIU/RPC0+1/o0AVZfBTkTNJOdUw==", + "dev": true, + "license": "ISC" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true, + "license": "MIT" + }, + "node_modules/enhanced-resolve": { + "version": "5.18.4", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.4.tgz", + "integrity": "sha512-LgQMM4WXU3QI+SYgEc2liRgznaD5ojbmY3sb8LxyguVkIg5FxdpTkvk72te2R38/TGKxH634oLxXRGY6d7AP+Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/ext-list": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/ext-list/-/ext-list-2.2.2.tgz", + "integrity": "sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "^1.28.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ext-name": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/ext-name/-/ext-name-5.0.0.tgz", + "integrity": "sha512-yblEwXAbGv1VQDmow7s38W77hzAgJAO50ztBLMcUyUBfxv1HC+LGwtiEN+Co6LtlqT/5uwVOxsD4TNIilWhwdQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ext-list": "^2.0.0", + "sort-keys-length": "^1.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/fflate": { + "version": "0.8.2", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.2.tgz", + "integrity": "sha512-cPJU47OaAoCbg0pBvzsgpTPhmhqI5eJjh/JIu8tPj5q+T7iLvW/JAYUqmE7KOB4R1ZyEhzBaIQpQpardBF5z8A==", + "dev": true, + "license": "MIT" + }, + "node_modules/file-type": { + "version": "20.5.0", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-20.5.0.tgz", + "integrity": "sha512-BfHZtG/l9iMm4Ecianu7P8HRD2tBHLtjXinm4X62XBOYzi7CYA7jyqfJzOvXHqzVrVPYqBo2/GvbARMaaJkKVg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/inflate": "^0.2.6", + "strtok3": "^10.2.0", + "token-types": "^6.0.0", + "uint8array-extras": "^1.4.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/file-type?sponsor=1" + } + }, + "node_modules/filename-reserved-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-3.0.0.tgz", + "integrity": "sha512-hn4cQfU6GOT/7cFHXBqeBg2TbrMBgdD0kcjLhvSQYYwm3s4B6cjvBfb7nBALJLAXqmU5xajSa7X2NnUud/VCdw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/filenamify": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-6.0.0.tgz", + "integrity": "sha512-vqIlNogKeyD3yzrm0yhRMQg8hOVwYcYRfjEoODd49iCprMn4HL85gK3HcykQE53EPIpX3HcAbGA5ELQv216dAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "filename-reserved-regex": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up-simple": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/find-up-simple/-/find-up-simple-1.0.1.tgz", + "integrity": "sha512-afd4O7zpqHeRyg4PfDQsXmlDe2PfdHtJt6Akt8jOWaApLOZk5JXs6VMR29lz03pRe9mpykrRCYIYxaJYcfpncQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/find-versions": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/find-versions/-/find-versions-5.1.0.tgz", + "integrity": "sha512-+iwzCJ7C5v5KgcBuueqVoNiHVoQpwiUK5XFLjf0affFTep+Wcw93tPvmb8tqujDNmzhBDPddnWV/qgWSXgq+Hg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver-regex": "^4.0.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/form-data-encoder": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-2.1.4.tgz", + "integrity": "sha512-yDYSgNMraqvnxiEXO4hi88+YZxaHC6QKzb5N84iRCTDeRO7ZALpir/lVmf/uXUhnwUr2O4HU8s/n6x+yNjQkHw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.17" + } + }, + "node_modules/fraction.js": { + "version": "5.3.4", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz", + "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/rawify" + } + }, + "node_modules/fs-extra": { + "version": "11.3.3", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.3.tgz", + "integrity": "sha512-VWSRii4t0AFm6ixFFmLLx1t7wS1gh+ckoa84aOeapGum0h+EZd1EhEumSB+ZdDLnEPuucsVB9oB7cxJHap6Afg==", + "dev": true, + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=14.14" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/got": { + "version": "13.0.0", + "resolved": "https://registry.npmjs.org/got/-/got-13.0.0.tgz", + "integrity": "sha512-XfBk1CxOOScDcMr9O1yKkNaQyy865NbYs+F7dr4H0LZMVgCj2Le59k6PqbNHoL5ToeaEQUYh6c6yMfVcc6SJxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sindresorhus/is": "^5.2.0", + "@szmarczak/http-timer": "^5.0.1", + "cacheable-lookup": "^7.0.0", + "cacheable-request": "^10.2.8", + "decompress-response": "^6.0.0", + "form-data-encoder": "^2.1.2", + "get-stream": "^6.0.1", + "http2-wrapper": "^2.1.10", + "lowercase-keys": "^3.0.0", + "p-cancelable": "^3.0.0", + "responselike": "^3.0.0" + }, + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sindresorhus/got?sponsor=1" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/http2-wrapper": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-2.2.1.tgz", + "integrity": "sha512-V5nVw1PAOgfI3Lmeaj2Exmeg7fenjhRUgz1lPSezy1CuhPYbgQtbQj4jZfEAEMlaL+vupsvhjqCyjzob0yxsmQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "quick-lru": "^5.1.1", + "resolve-alpn": "^1.2.0" + }, + "engines": { + "node": ">=10.19.0" + } + }, + "node_modules/hugo-bin": { + "version": "0.149.2", + "resolved": "https://registry.npmjs.org/hugo-bin/-/hugo-bin-0.149.2.tgz", + "integrity": "sha512-juLVVFGyGP0j817ND9OCNLSO/wV9CrfSbHBmXMInQR1NnL8OTGdJYnXbrDx14Uyz06Z7PUtL3bobNcFhNVynpg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/XhmikosR" + } + ], + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "@xhmikosr/bin-wrapper": "^13.2.0", + "package-config": "^5.0.0" + }, + "bin": { + "hugo": "bin/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=10.17.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inspect-with-kind": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/inspect-with-kind/-/inspect-with-kind-1.0.5.tgz", + "integrity": "sha512-MAQUJuIo7Xqk8EVNP+6d3CKq9c80hi4tjIbIAT6lmGW9W6WzlHiu9PS8uSuUYU+Do+j1baiFp3H25XEVxDIG2g==", + "dev": true, + "license": "ISC", + "dependencies": { + "kind-of": "^6.0.2" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-plain-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz", + "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-stream": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", + "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/jiti": { + "version": "2.6.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.6.1.tgz", + "integrity": "sha512-ekilCSN1jwRvIbgeg/57YFh8qQDNbwDb9xT/qu2DAHbFFZUicIl4ygVaAvzveMhMVr3LnpSKTNnwt8PoOfmKhQ==", + "dev": true, + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsonfile": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", + "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/keyv": { + "version": "4.5.4", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/lightningcss": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.30.2.tgz", + "integrity": "sha512-utfs7Pr5uJyyvDETitgsaqSyjCb2qNRAtuqUeWIAKztsOYdcACf2KtARYXg2pSvhkt+9NfoaNY7fxjl6nuMjIQ==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^2.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-android-arm64": "1.30.2", + "lightningcss-darwin-arm64": "1.30.2", + "lightningcss-darwin-x64": "1.30.2", + "lightningcss-freebsd-x64": "1.30.2", + "lightningcss-linux-arm-gnueabihf": "1.30.2", + "lightningcss-linux-arm64-gnu": "1.30.2", + "lightningcss-linux-arm64-musl": "1.30.2", + "lightningcss-linux-x64-gnu": "1.30.2", + "lightningcss-linux-x64-musl": "1.30.2", + "lightningcss-win32-arm64-msvc": "1.30.2", + "lightningcss-win32-x64-msvc": "1.30.2" + } + }, + "node_modules/lightningcss-android-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-android-arm64/-/lightningcss-android-arm64-1.30.2.tgz", + "integrity": "sha512-BH9sEdOCahSgmkVhBLeU7Hc9DWeZ1Eb6wNS6Da8igvUwAe0sqROHddIlvU06q3WyXVEOYDZ6ykBZQnjTbmo4+A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.30.2.tgz", + "integrity": "sha512-ylTcDJBN3Hp21TdhRT5zBOIi73P6/W0qwvlFEk22fkdXchtNTOU4Qc37SkzV+EKYxLouZ6M4LG9NfZ1qkhhBWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.30.2.tgz", + "integrity": "sha512-oBZgKchomuDYxr7ilwLcyms6BCyLn0z8J0+ZZmfpjwg9fRVZIR5/GMXd7r9RH94iDhld3UmSjBM6nXWM2TfZTQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.30.2.tgz", + "integrity": "sha512-c2bH6xTrf4BDpK8MoGG4Bd6zAMZDAXS569UxCAGcA7IKbHNMlhGQ89eRmvpIUGfKWNVdbhSbkQaWhEoMGmGslA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.30.2.tgz", + "integrity": "sha512-eVdpxh4wYcm0PofJIZVuYuLiqBIakQ9uFZmipf6LF/HRj5Bgm0eb3qL/mr1smyXIS1twwOxNWndd8z0E374hiA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.30.2.tgz", + "integrity": "sha512-UK65WJAbwIJbiBFXpxrbTNArtfuznvxAJw4Q2ZGlU8kPeDIWEX1dg3rn2veBVUylA2Ezg89ktszWbaQnxD/e3A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.30.2.tgz", + "integrity": "sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.30.2.tgz", + "integrity": "sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.30.2.tgz", + "integrity": "sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.30.2.tgz", + "integrity": "sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.30.2", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.30.2.tgz", + "integrity": "sha512-5g1yc73p+iAkid5phb4oVFMB45417DkRevRbt/El/gKXJk4jid+vPFF/AXbxn05Aky8PapwzZrdJShv5C0avjw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lilconfig": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", + "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/load-json-file": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/load-json-file/-/load-json-file-7.0.1.tgz", + "integrity": "sha512-Gnxj3ev3mB5TkVBGad0JM6dmLiQL+o0t23JPBZ9sd+yvSLk05mFoqKBw5N8gbbkU4TNXyqCgIrl/VM17OgUIgQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lowercase-keys": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-3.0.0.tgz", + "integrity": "sha512-ozCC6gdQ+glXOQsveKD0YsDy8DSQFjDTz4zyzEHNV5+JP5D62LmfDZ6o1cycFx9ouG940M5dE8C8CTewdj2YWQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mimic-response": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-4.0.0.tgz", + "integrity": "sha512-e5ISH9xMYU0DzrT+jl8q2ze9D6eWBto+I8CNpe+VI+K2J/F/k3PdkdTdz4wvGVH4NTpo+NRYTVIuMQEMMcsLqg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.27", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.27.tgz", + "integrity": "sha512-nmh3lCkYZ3grZvqcCH+fjmQ7X+H0OeZgP40OierEaAptX4XofMh5kwNbWh7lBduUzCcV/8kZ+NDLCwm2iorIlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-url": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-8.1.0.tgz", + "integrity": "sha512-X06Mfd/5aKsRHc0O0J5CUedwnPmnDtLF2+nq+KN9KSDlJHkPuh0JUviWjEWMe0SW/9TDdSLVPuk7L5gGTIA1/w==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-cancelable": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-3.0.0.tgz", + "integrity": "sha512-mlVgR3PGuzlo0MmTdk4cXqXWlwQDLnONTAg6sm62XkMJEiRxN3GL3SffkYvqwonbkJBcrI7Uvv5Zh9yjvn2iUw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + } + }, + "node_modules/package-config": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/package-config/-/package-config-5.0.0.tgz", + "integrity": "sha512-GYTTew2slBcYdvRHqjhwaaydVMvn/qrGC323+nKclYioNSLTDUM/lGgtGTgyHVtYcozb+XkE8CNhwcraOmZ9Mg==", + "dev": true, + "license": "MIT", + "dependencies": { + "find-up-simple": "^1.0.0", + "load-json-file": "^7.0.1" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/pend": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", + "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", + "dev": true, + "license": "MIT" + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-cli": { + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-11.0.1.tgz", + "integrity": "sha512-0UnkNPSayHKRe/tc2YGW6XnSqqOA9eqpiRMgRlV1S6HdGi16vwJBx7lviARzbV1HpQHqLLRH3o8vTcB0cLc+5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^1.0.0", + "fs-extra": "^11.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^5.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^5.0.0", + "tinyglobby": "^0.2.12", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-5.1.0.tgz", + "integrity": "sha512-G5AJ+IX0aD0dygOE0yFZQ/huFFMSNneyfp0e3/bT05a8OfPC5FUoZRPfGijUdGOJNMewJiwzcHJXFafFzeKFVA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "lilconfig": "^3.1.1", + "yaml": "^2.4.2" + }, + "engines": { + "node": ">= 18" + }, + "peerDependencies": { + "jiti": ">=1.21.0", + "postcss": ">=8.0.9", + "tsx": "^4.8.1" + }, + "peerDependenciesMeta": { + "jiti": { + "optional": true + }, + "postcss": { + "optional": true + }, + "tsx": { + "optional": true + } + } + }, + "node_modules/postcss-reporter": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.1.0.tgz", + "integrity": "sha512-/eoEylGWyy6/DOiMP5lmFRdmDKThqgn7D6hP2dXKJI/0rJSO1ADFNngZfDzxL0YAxFvws+Rtpuji1YIHj4mySA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/quick-lru": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", + "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "license": "MIT", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/resolve-alpn": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", + "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==", + "dev": true, + "license": "MIT" + }, + "node_modules/responselike": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/responselike/-/responselike-3.0.0.tgz", + "integrity": "sha512-40yHxbNcl2+rzXvZuVkrYohathsSJlMTXKryG5y8uciHv1+xDLHQpgjG64JUO9nrEq2jGLH6IZ8BcZyw3wrweg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lowercase-keys": "^3.0.0" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/seek-bzip": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/seek-bzip/-/seek-bzip-2.0.0.tgz", + "integrity": "sha512-SMguiTnYrhpLdk3PwfzHeotrcwi8bNV4iemL9tx9poR/yeaMYwB9VzR1w7b57DuWpuqR8n6oZboi0hj3AxZxQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "commander": "^6.0.0" + }, + "bin": { + "seek-bunzip": "bin/seek-bunzip", + "seek-table": "bin/seek-bzip-table" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/semver-regex": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/semver-regex/-/semver-regex-4.0.5.tgz", + "integrity": "sha512-hunMQrEy1T6Jr2uEVjrAIqjwWcQTgOAcIM52C8MY1EZSD3DDNft04XzvYKPqjED65bNVVko0YI38nYeEHCX3yw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/semver-truncate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/semver-truncate/-/semver-truncate-3.0.0.tgz", + "integrity": "sha512-LJWA9kSvMolR51oDE6PN3kALBNaUdkxzAGcexw8gjMA8xr5zUqK0JiR3CgARSqanYF3Z1YHvsErb1KDgh+v7Rg==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/slash": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-5.1.0.tgz", + "integrity": "sha512-ZA6oR3T/pEyuqwMgAKT0/hAv8oAXckzbkmR0UkUosQ+Mc4RxGoJkRmwHgHufaenlyAgE1Mxgpdcrf75y6XcnDg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sort-keys": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/sort-keys/-/sort-keys-1.1.2.tgz", + "integrity": "sha512-vzn8aSqKgytVik0iwdBEi+zevbTYZogewTUM6dtpmGwEcdzbub/TX4bCzRhebDCRC3QzXgJsLRKB2V/Oof7HXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-plain-obj": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sort-keys-length": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/sort-keys-length/-/sort-keys-length-1.0.1.tgz", + "integrity": "sha512-GRbEOUqCxemTAk/b32F2xa8wDTs+Z1QHOkbhJDQTvv/6G3ZkbJ+frYWsTcc7cBB3Fu4wy4XlLCuNtJuMn7Gsvw==", + "dev": true, + "license": "MIT", + "dependencies": { + "sort-keys": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "dev": true, + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-dirs": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-dirs/-/strip-dirs-3.0.0.tgz", + "integrity": "sha512-I0sdgcFTfKQlUPZyAqPJmSG3HLO9rWDFnxonnIbskYNM3DwFOeTNB5KzVq3dA1GdRAc/25b5Y7UO2TQfKWw4aQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "inspect-with-kind": "^1.0.5", + "is-plain-obj": "^1.1.0" + } + }, + "node_modules/strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/strtok3": { + "version": "10.3.4", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", + "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@tokenizer/token": "^0.3.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/tailwindcss": { + "version": "4.1.18", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.18.tgz", + "integrity": "sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tapable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.0.tgz", + "integrity": "sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/tar-stream": { + "version": "3.1.7", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/through": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", + "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.15", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.3" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinyglobby/node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/tinyglobby/node_modules/picomatch": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", + "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/token-types": { + "version": "6.1.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.1.tgz", + "integrity": "sha512-kh9LVIWH5CnL63Ipf0jhlBIy0UsrMj/NJDfpsy1SqOXlLKEVyXXYrnFxFT1yOOYVGBSApeVnjPw/sBz5BfEjAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@borewit/text-codec": "^0.1.0", + "@tokenizer/token": "^0.3.0", + "ieee754": "^1.2.1" + }, + "engines": { + "node": ">=14.16" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Borewit" + } + }, + "node_modules/uint8array-extras": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", + "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/unbzip2-stream": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", + "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.2.1", + "through": "^2.3.8" + } + }, + "node_modules/universalify": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", + "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz", + "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "escalade": "^3.2.0", + "picocolors": "^1.1.1" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.2.tgz", + "integrity": "sha512-mplynKqc1C2hTVYxd0PU2xQAc22TI1vShAYGksCCfxbn/dFwnHTNi1bvYsBTkhdUNtGIf5xNOg938rrSSYvS9A==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14.6" + }, + "funding": { + "url": "https://github.com/sponsors/eemeli" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/yauzl": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.2.0.tgz", + "integrity": "sha512-Ow9nuGZE+qp1u4JIPvg+uCiUr7xGQWdff7JQSk5VGYTAZMDe2q8lxJ10ygv10qmSj031Ty/6FNJpLO4o1Sgc+w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer-crc32": "~0.2.3", + "pend": "~1.2.0" + }, + "engines": { + "node": ">=12" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..5db8d6a --- /dev/null +++ b/package.json @@ -0,0 +1,20 @@ +{ + "name": "hydro-sterile", + "version": "1.0.0", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "description": "", + "devDependencies": { + "@tailwindcss/postcss": "^4.1.18", + "autoprefixer": "^10.4.23", + "hugo-bin": "^0.149.2", + "postcss": "^8.5.6", + "postcss-cli": "^11.0.1", + "tailwindcss": "^4.1.18" + } +} diff --git a/postcss.config.js b/postcss.config.js new file mode 100644 index 0000000..dc655aa --- /dev/null +++ b/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + '@tailwindcss/postcss': {}, + autoprefixer: {}, + }, +} \ No newline at end of file diff --git a/public b/public new file mode 120000 index 0000000..f9678d2 --- /dev/null +++ b/public @@ -0,0 +1 @@ +/storage/hydro-sterile/public \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..0d3946f --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +flask>=2.3.0 +flask-cors>=4.0.0 +bcrypt>=4.0.0 +PyJWT>=2.8.0 +ollama>=0.1.0 +pdfplumber>=0.10.0 +pyyaml>=6.0 + diff --git a/scripts/CHAT_API_README.md b/scripts/CHAT_API_README.md new file mode 100644 index 0000000..cfa2706 --- /dev/null +++ b/scripts/CHAT_API_README.md @@ -0,0 +1,112 @@ +# Chat API Server + +This Flask-based API server provides a secure interface for the chat feature, connecting users to the Ollama model. + +## Features + +- **Secure**: Input sanitization, rate limiting, CORS protection +- **Rate Limited**: 10 requests per minute per IP address +- **Context Aware**: Maintains conversation history (last 5 messages) +- **Auto-restart**: Runs as systemd service with automatic restart + +## Configuration + +**Environment Variables:** +- `OLLAMA_HOST`: Ollama endpoint (default: `http://10.30.20.110:11434`) +- `OLLAMA_MODEL`: Model name (default: `drjones-posts-to-much`) +- `PORT`: API server port (default: `5000`) +- `CHAT_API_KEY`: Optional API key for additional security (not set by default) + +## Service Management + +```bash +# Start service +sudo systemctl start hugo-chat-api.service + +# Stop service +sudo systemctl stop hugo-chat-api.service + +# Enable at boot +sudo systemctl enable hugo-chat-api.service + +# Check status +sudo systemctl status hugo-chat-api.service + +# View logs +sudo journalctl -u hugo-chat-api.service -f +``` + +## API Endpoints + +### POST `/api/chat` + +Send a chat message and get AI response. + +**Request:** +```json +{ + "message": "How do I maintain sterile conditions?", + "history": [ + {"role": "user", "content": "Previous message"}, + {"role": "assistant", "content": "Previous response"} + ] +} +``` + +**Response:** +```json +{ + "response": "AI response text...", + "model": "drjones-posts-to-much" +} +``` + +### GET `/api/health` + +Health check endpoint. + +**Response:** +```json +{ + "status": "healthy", + "model": "drjones-posts-to-much", + "ollama_host": "http://10.30.20.110:11434" +} +``` + +## Security Features + +1. **Rate Limiting**: 10 requests per minute per IP address +2. **Input Sanitization**: Removes dangerous characters, limits length to 1000 chars +3. **CORS Protection**: Only allows requests from configured origins +4. **Error Handling**: Graceful error handling with user-friendly messages + +## Installation + +1. Install dependencies: + ```bash + pip3 install flask flask-cors ollama + ``` + +2. Enable and start service: + ```bash + sudo systemctl daemon-reload + sudo systemctl enable hugo-chat-api.service + sudo systemctl start hugo-chat-api.service + ``` + +## Troubleshooting + +**Service won't start:** +- Check logs: `sudo journalctl -u hugo-chat-api.service -n 50` +- Verify Ollama is accessible: `curl http://10.30.20.110:11434/api/tags` +- Check port 5000 is available: `sudo lsof -i :5000` + +**Rate limit errors:** +- Normal behavior - users are limited to 10 requests per minute +- Rate limit resets after 1 minute + +**Connection errors:** +- Verify Ollama endpoint is correct and accessible +- Check firewall rules allow connections to port 5000 + diff --git a/scripts/README.md b/scripts/README.md new file mode 100644 index 0000000..11695e8 --- /dev/null +++ b/scripts/README.md @@ -0,0 +1,171 @@ +# HydroLord Website Automation Scripts + +This directory contains automation scripts for the HydroLord Hugo website. + +## Scripts + +### `generate_blog_post.py` + +Generates a new blog post using Ollama's AI model and adds it to the Hugo blog. + +**Configuration:** +- **Ollama Endpoint:** `http://10.30.20.110:11434` +- **Model:** `drjones-posts-to-much` +- **Output Directory:** `/root/hydro-sterile/content/blog/` +- **Schedule:** Runs automatically every 3 days via systemd timer +- **Randomization:** Each run uses a randomly varied prompt to ensure unique content + +**Features:** +- Automatically finds the next available post number +- Generates title and summary from content +- Creates proper Hugo frontmatter +- Fails quietly if Ollama endpoint is unavailable (will retry in 3 days) + +**Usage:** +```bash +python3 generate_blog_post.py +``` + +**Error Handling:** +If the Ollama endpoint is down, the script exits gracefully with code 0 (no error) to prevent systemd from logging failures. It will automatically retry in 3 days when the timer runs again. + +### `start_hugo_server.sh` + +Starts the Hugo development server for the website. + +**Configuration:** +- **Port:** 1313 (configurable via `HUGO_PORT` env var) +- **Bind Address:** 0.0.0.0 (all interfaces, configurable via `HUGO_BIND` env var) +- **Base URL:** Read from `hugo.yaml` + +**Usage:** +```bash +./start_hugo_server.sh +``` + +## Systemd Services + +### Hugo Website Service + +**Service:** `hugo-hydrolord.service` + +Starts the Hugo server at boot and keeps it running. + +**Commands:** +```bash +# Enable service to start at boot +sudo systemctl enable hugo-hydrolord.service + +# Start service now +sudo systemctl start hugo-hydrolord.service + +# Check status +sudo systemctl status hugo-hydrolord.service + +# View logs +sudo journalctl -u hugo-hydrolord.service -f +``` + +### Blog Post Generator + +**Service:** `hugo-blog-generator.service` +**Timer:** `hugo-blog-generator.timer` + +Automatically generates blog posts every 3 days. + +**Commands:** +```bash +# Enable timer +sudo systemctl enable hugo-blog-generator.timer + +# Start timer +sudo systemctl start hugo-blog-generator.timer + +# Check timer status +sudo systemctl status hugo-blog-generator.timer + +# List all timers +systemctl list-timers hugo-blog-generator.timer + +# Run generator manually (for testing) +sudo systemctl start hugo-blog-generator.service + +# View logs +sudo journalctl -u hugo-blog-generator.service -f +``` + +## Installation + +1. **Install Python dependencies:** + ```bash + pip3 install ollama + ``` + +2. **Make scripts executable:** + ```bash + chmod +x /root/hydro-sterile/scripts/*.py + chmod +x /root/hydro-sterile/scripts/*.sh + ``` + +3. **Enable and start Hugo service:** + ```bash + sudo systemctl daemon-reload + sudo systemctl enable hugo-hydrolord.service + sudo systemctl start hugo-hydrolord.service + ``` + +4. **Enable and start blog generator timer:** + ```bash + sudo systemctl enable hugo-blog-generator.timer + sudo systemctl start hugo-blog-generator.timer + ``` + +5. **Verify everything is running:** + ```bash + sudo systemctl status hugo-hydrolord.service + sudo systemctl status hugo-blog-generator.timer + ``` + +## Troubleshooting + +### Hugo Server Not Starting + +- Check if port 1313 is already in use: `sudo lsof -i :1313` +- Check logs: `sudo journalctl -u hugo-hydrolord.service -n 50` +- Verify Hugo is installed: `which hugo` or check `node_modules/.bin/hugo` + +### Blog Generator Not Running + +- Check if Ollama endpoint is accessible: `curl http://10.30.20.110:11434/api/tags` +- Check timer status: `systemctl list-timers hugo-blog-generator.timer` +- View logs: `sudo journalctl -u hugo-blog-generator.service -n 50` +- Test script manually: `python3 /root/hydro-sterile/scripts/generate_blog_post.py` + +### Ollama Endpoint Down + +The script is designed to fail quietly if Ollama is unavailable. It will exit with code 0 (success) and retry automatically in 3 days. No action needed. + +## Configuration Changes + +To change the Ollama endpoint or model, edit `/root/hydro-sterile/scripts/generate_blog_post.py` and modify the constants at the top: + +```python +OLLAMA_HOST = "http://10.30.20.110:11434" +MODEL = "drjones-posts-to-much" +``` + +**Note:** The script now includes random prompt variations to ensure each blog post is unique. The model generates titles with credentials, which the script automatically extracts and separates from the article content. + +After making changes, restart the timer: +```bash +sudo systemctl restart hugo-blog-generator.timer +``` + +## Files Location + +- **Scripts:** `/root/hydro-sterile/scripts/` +- **Systemd Services:** `/etc/systemd/system/hugo-*.service` +- **Systemd Timers:** `/etc/systemd/system/hugo-*.timer` +- **Blog Posts:** `/root/hydro-sterile/content/blog/` +- **Logs:** `journalctl -u hugo-hydrolord.service` or `journalctl -u hugo-blog-generator.service` + diff --git a/scripts/__pycache__/chat_api.cpython-310.pyc b/scripts/__pycache__/chat_api.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..9442941e229be145f4cbb2f42cb2521724217819 GIT binary patch literal 9073 zcma)B+ix3JdY>yf98#1-%ke#)IEig$ElRST%WAzz99xc+wPaON>NSHbG3FUkBMmvs znL|06l4$^YH(sD_67*%!B!#r-L-Ww5zU^yY+drW5TA-+bJ{BmD-P-+qXGqCPyo-{U zGw1r9+jspLKR28N&El{hfd}~!rzV)gu z-$vDtZ?kIRtp}M_wwhIGj1gEZt7^4!)toGwf!)ei^Q}U)&>E?Z$hu5WY>ie&Wj-5> zwZ^OCGOq=tYALor#R?9zCaM#va!Y9+G!*aP)(5IWfA2Y#^R)Ft%zmh$q-`H&`5jeN zZYyo|t|G^3!IA0_R;V7u*peK(nT};6)uR|++#8>c^3?5PY?O`dXw~B~Kh8?XpYWcS zr2}kYN4uw1Ps-9k_8i(AFUt<0?ghLL<9&)9slLc2sxSFU^<{RndfLl-XHb9E8}-gM z6?W{mDmzXqW+&M5_mygyy~!N*!hN;+3OmJKM9q)bOYCL5&v`F3RCb!3LEEHvcD=&R zvT{>puRPSeSJ+9GXFqzVv2$$lzH(2+3{$dCg-xY>*fg7=mg=;uKR+DHF0hNTen!^6 zy4(LX_PVS;@10?9Y-nGo{CS+wkG+#$Dc@k%^2BGo<4=`-;+@znon61cE_o-}2>aD=kn6Lv*Phhk2{T!J2!*CZMn{TTX;?G3O{Vy_Uf7+IoAR= z+Hj(@XNQNIKJy~SMNPfSJxRuEvrg!@g(M`_Jf}_*pu*z~x9(N!dEuDz^Q8mTg}N+YP_jO}CKtn;dR#xXxK;X1a1=x^jNHGBYzhJ2P|f!bM4O(pdCc#v-RO@cLLAZ^j&A^=JKlJwKuUP zUbY{RC@+@P#8`60T3Jgh?9vl{%S&>UVlLs9^~98ioMgH8QP+z^l3kC&w%^#Ikymam zuY9W}=3VY~B2;9q1isf6@zuHI<(td1PMBWYk~|EY>>cj%EvFsQ1w_IP0*_UkG+)s!^*)h5H=cq;5`CcD67}vw{KluUYuWi7oAz76Led5z#iYDwRH8GBfO7=gNM6y zVikw^k&7c>l_Y=j#*NDhmuuH=uB^sK)`aNHPEGyrxRb)5-@?tSb2s9H&pdD(8;;X| za6nO#!j zIM${|r|Y7~<>D$XtLL!hy_hR-A6;9S7E&e{2MPRqly zRh9yM2Rb2aL)S!dNp4UnXX2NMJ0h@$JlY`StODgByjCah!UhY&Rt1V6erdo&f4bMf zL9ZahA+AAeXJ;N!p#Th8zdUP*S%a~V!I(VLz~%%~Ii^b*|fBzf+& zLgCej{I?1}hW;}7?##2qBdTMJh^9z|50q*$0;08aoi)GnE(k<4Ye74zW>kIx*+=BX z)ZwD+{^(qWPgFwTGvzb2A>EbxR$uGEB$f5}_q3+6qg}-`0&Bjc2xG7Ip87!FF!`h~nZZmSW}jsm*jG;z z+NR21Z7PE?7>T>jJW$9bdZ6_bU+JlTul_YHGrRcc5?1r4H`-Ks%7=DeeSmR?l?UpE z!nL0IrS=hK+q+QM7p*d#S{A67R_9wS(V;++A~X>T@77?l!#0c35p;lweJSuG0V(Ue zd5aE)t}&x%YC+YWWYl)(jtmw_k-tBz9%44-gw=FHgUiZBj64T>6rW&g8MAMANmT9R) zwW!5E-dpP4as9tem*+sgtnmt7d^e?kqE&BXR2=^VjOCpxrbqH)S}@9-n&40UgigR>`D z>28bBVD6fnc7wJ;0)ri^MSV;+G(L-B9nbEM;y3Ce_k9$n5Vr;Pj93aP=Ar)iNMG(7 zdxqY+r7**Qin|A&y=~1X(c!yL81%o5rwMh{Q#+@c%2nmV>wR4qJzbdhG`_*ip1!Hz zok`z4eLKT4EW4xijh?ZcWflz5267g&WUVQ3J@c5-&-60e_6|n&wC%hcWm3D$bBTtpPnkh9+j5FI7?A&sl^%Sp!j7#oiGE9pw$C??rR)M!abZVt+_ zL(H9+9ggq{EV~6Q773VsUbdvzCk8pViAj5opmE!RZzVY@)jEhl&*KYX#^y3%U(jPSx;CsMhbbPczvfa$~CX zsrHzYW!dK&koMXYiZ>w?0;+>lK-?D)e|S(3jvWmQ3d^~z?ip;S5mBOrVOkzw3k1n#?78oDl^N#xYSrUzP8-kc4B?FN<+&6pX zb{@(GV!^9m{6a57%q>QkyagNwpl0E7vA}hKaa8BsZKmvsZ^W~1c!Zx46zR}r4*215C|C=28Kf+nMm@es;#`g zL}47Cr&V019heCd2JBAH zWFto)e~_;CAgLLw3lhSAx!Y4fLQPg=_Kwz8dzrokqh_;Fk`y*ZriP8neCeC4G)wRG z4E{L-R0#8C?5L19>=+ad%GF}ceY=-q&$0Yql_&an%#|0ks^76gy*yU+J9apI_v~%E zmuH8LC^)w~J51-sj(m;S4hHa>p4u<;bpFphwWnjA0`+D`dxc?-_%%_0COP&{?T@hI zqDb{kwO{Pn&^^U|zE>PlPiYUwee`d=k@ZnA)+-W7$xeQ)G3TM)AMK5c@8k`i==@@2Y7X_oW- z@lVD{A2FFD{Q{#-v~CP&9R{t_&!lxm(mK1J*3*>A<@n%r2?VAP;Q;T7D#YH>rRS%K z{|_vrB?q_(^#LNn9(_UH)1hGt;VQtm04xA65L-zW5>wmow&KzGwuBQUGKmWRON`^o zAS+ei;v=|#19MN-Kc2Ad$;nCQN(i`;AWXO#(0!(ZN;@vdwmD!PKzCw;D&sF~`-4@0 zoYDbZb7_SQj}z#_90ETZK;%;FX9&izu->I`CaO5^Bg7E|tNLw3HZ6&Ur2eY}RspR5 zRfU2*=R_T^?l=58f}ZFuLVM>9!XvDsO_P916n1&ti)KHx6V(O)0Ji~-&7g2+tshRfdJqX-~2PXi++9(s7e^hhL+UBc&`|GX4w7nM89vg5+ZTHsnR@6)i#0$f^B$GzL$!Llw z59=eoMI=ftP8IHJTVTc%G33uMi2M_D{wF-qZ;==V0${C#$3R-rk<(%MU>RZcfE|^{ zY1Ay#9o3#!wNDLLLh3Q5ABBa?pw)Ve+KfSVQ`6vJqMSjkVLY)+>VZCw4FuQtquqF% z`QJj0a)2-%kC_vAV?Ko4rN`uoj%D_0#$=6LZ(Nf0b|9`(>G|vL&aLjVv!pk6?JP$j zo{$(4EGX0N!_pE;)K}rb5>`ZLke=#e>D%dBZ$N)S>&AzLL7uDB#pXJERjG@C9kHjZ zs?^1~zV-s^pR?=7!V!5q>?nrIJ@oU&Rsam+O2vmlVS=y7wAhifY zh+>XZy2(9!rcbD+^X5owbU;?q$+aV9BQ`7=KVYse(J7wT$6N$d z_}<-poJFw7E@%A%jhWi>-%>nT`ez3P0VMi956&P?qd19~U4@^PJu>@xz#kJPMLjJ0 z6hCIRR=|${8o-YL2zt8YM{7ODa*`iuCR@_U@7i;-uYaP0ALU!}V<9dtr5lpWTS-G~Y2QoNP=QYj_^>xL@7%|}7vmdO!fwFiC#N40lw=$^w&kYZxM2VB z(G8&vVs)yM9~wCCcA!WrV67j6YLZD^{$Wf%xPl>j4K34idyF;cA$dx&RN0o6gZ~A{ zr51t!#ndkRQ3f+o?M{)0T*ou{t1y^Lvfz2*tn=uv=@yh9DNReWNU=#JmU#?4`p0;U z0*^Q^c?}-}05k%6$C;5S?kiJBo1S#rvI`}?XHd5jHOUxuKCBIF%J&hrA5<|?Sv=?aW zgso`0)aDQ>>ks@ls7S0@tpz`zR*PR)A^e0cZub-R;KMZBBAVBU$A?bGVDa$fvV)Jh zahARS;SXGs6JdjfBqsCjberH63UzrAN%>-ut<~VQ)oO_rMrj;*hFV{zOIVZiFvW!HYZCMLz&LeJ7Hy|(o%EK^-I7vQE*o}x>l1YzL`WQ)m zzC}xvDlrXdDVj|TfJB0SKvU4MCK>oM_~x7*G#z=O10CYy)S*YoJSA5ssUk^6Dd1k6 z`}u0^#@yn&tJnEiYU)!H&YF-iY+MIeE`fCk=1M0i)v6x#GdaFNdeVAw(sG>GFTE}u z7{N1d+PVbau^V`V_$U$G0POlG!ABA(hfgp8NQnTY6lMZQu4-WUwAFyOpgk^_6fqA$ qCy0e^=$0|2SvfmnJ#UQ^jujmn?Zv=Wi(`0>0+`AXC5o|)Kl~qU=W8|q literal 0 HcmV?d00001 diff --git a/scripts/__pycache__/generate_blog_post.cpython-310.pyc b/scripts/__pycache__/generate_blog_post.cpython-310.pyc new file mode 100644 index 0000000000000000000000000000000000000000..f7b569647b2ca218bd40c303cbf078895c587bff GIT binary patch literal 8055 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 NGC_3^`4aol{|6`x6utle literal 0 HcmV?d00001 diff --git a/scripts/chat_api.py b/scripts/chat_api.py new file mode 100755 index 0000000..8b79744 --- /dev/null +++ b/scripts/chat_api.py @@ -0,0 +1,348 @@ +#!/usr/bin/env python3 +""" +Chat API Server for Ollama Integration + +This Flask server provides a secure API endpoint for the chat interface. +It handles rate limiting, input validation, and communicates with Ollama. + +Usage: + python3 chat_api.py + +Configuration: + - Ollama endpoint: 10.30.20.110:11434 + - Model: drjones-posts-to-much + - Port: 5000 (configurable via PORT env var) +""" + +import os +import sys +import time +import re +import yaml +from pathlib import Path +from datetime import datetime, timedelta +from flask import Flask, request, jsonify +from flask_cors import CORS +from functools import wraps +import hashlib + +try: + from ollama import Client +except ImportError: + print("ERROR: ollama Python library not installed. Run: pip3 install ollama", file=sys.stderr) + sys.exit(1) + +try: + import pdfplumber + PDF_SUPPORT = True +except ImportError: + PDF_SUPPORT = False + print("WARNING: pdfplumber not installed. PDF text extraction disabled.", file=sys.stderr) + +# Configuration +OLLAMA_HOST = os.getenv("OLLAMA_HOST", "http://10.30.20.110:11434") +MODEL = os.getenv("OLLAMA_MODEL", "drjones-posts-to-much") +PORT = int(os.getenv("PORT", 5000)) +API_KEY = os.getenv("CHAT_API_KEY", "") # Optional API key for additional security +LIBRARY_DIR = Path("/root/hydro-sterile/static/library") +LIBRARY_YAML = Path("/root/hydro-sterile/data/library.yaml") +MAX_FILE_SIZE = 10 * 1024 * 1024 # 10MB max file size +MAX_TEXT_LENGTH = 5000 # Max characters to extract from PDF + +# Rate limiting storage (in-memory, simple implementation) +rate_limits = {} + +# System prompt for the chat +SYSTEM_PROMPT = """You are Dr. Jones, a sterile hydroponics expert with 20 years of experience growing cannabis in a legal state. +You provide practical, direct, and knowledgeable advice about sterile hydroponics, cannabis cultivation, and growing techniques. +Keep responses concise (2-4 paragraphs), practical, and actionable. Write in first person (I, me, my) and be conversational.""" + +app = Flask(__name__) +# Allow CORS from the website domain and local development +CORS(app, origins=[ + "https://hydrolord.thetempleofdoom.com", + "http://hydrolord.thetempleofdoom.com", + "http://localhost:1313", + "http://127.0.0.1:1313", + "http://10.30.20.243", + "http://10.30.20.243:1313" +]) + +def get_client_ip(): + """Get client IP address for rate limiting.""" + if request.headers.get('X-Forwarded-For'): + return request.headers.get('X-Forwarded-For').split(',')[0].strip() + return request.remote_addr + +def rate_limit(max_per_minute=10, window_minutes=1): + """Simple rate limiting decorator.""" + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + client_ip = get_client_ip() + now = time.time() + window_seconds = window_minutes * 60 + + # Clean old entries + rate_limits[client_ip] = [ + timestamp for timestamp in rate_limits.get(client_ip, []) + if now - timestamp < window_seconds + ] + + # Check rate limit + if len(rate_limits.get(client_ip, [])) >= max_per_minute: + return jsonify({ + "error": "Rate limit exceeded. Please wait a moment before sending another message." + }), 429 + + # Add current request + if client_ip not in rate_limits: + rate_limits[client_ip] = [] + rate_limits[client_ip].append(now) + + return f(*args, **kwargs) + return decorated_function + return decorator + +def sanitize_input(text): + """Sanitize user input to prevent injection attacks.""" + if not text or not isinstance(text, str): + return "" + + # Remove potentially dangerous characters + text = re.sub(r'[<>]', '', text) + + # Limit length + text = text[:1000].strip() + + return text + +def load_library_files(): + """Load library file list from YAML.""" + try: + with open(LIBRARY_YAML, 'r', encoding='utf-8') as f: + data = yaml.safe_load(f) + + files = [] + for entry in data or []: + if isinstance(entry, dict) and 'title' in entry and 'file' in entry: + file_path = entry.get('file', '') + # Convert /library/ path to actual file path + if file_path.startswith('/library/'): + filename = file_path.replace('/library/', '') + full_path = LIBRARY_DIR / filename + if full_path.exists(): + files.append({ + 'title': entry.get('title', ''), + 'filename': entry.get('filename', ''), + 'file': file_path, + 'category': entry.get('category', ''), + 'size': full_path.stat().st_size if full_path.exists() else 0 + }) + return files + except Exception as e: + print(f"Error loading library: {e}", file=sys.stderr) + return [] + +def extract_pdf_text(file_path, max_length=MAX_TEXT_LENGTH): + """Extract text from PDF file.""" + if not PDF_SUPPORT: + return None + + try: + full_path = LIBRARY_DIR / file_path.replace('/library/', '') + + if not full_path.exists(): + return None + + if full_path.stat().st_size > MAX_FILE_SIZE: + return f"[File too large: {full_path.stat().st_size / 1024 / 1024:.1f}MB. Max size: {MAX_FILE_SIZE / 1024 / 1024}MB]" + + text_parts = [] + with pdfplumber.open(str(full_path)) as pdf: + for page in pdf.pages[:10]: # Limit to first 10 pages + page_text = page.extract_text() + if page_text: + text_parts.append(page_text) + if len(' '.join(text_parts)) > max_length: + break + + full_text = ' '.join(text_parts) + if len(full_text) > max_length: + full_text = full_text[:max_length] + "... [truncated]" + + return full_text.strip() + except Exception as e: + print(f"Error extracting PDF text: {e}", file=sys.stderr) + return None + +@app.route('/api/chat', methods=['POST']) +@rate_limit(max_per_minute=10, window_minutes=1) +def chat(): + """Handle chat requests.""" + try: + data = request.get_json() + + if not data or 'message' not in data: + return jsonify({"error": "Message is required"}), 400 + + message = sanitize_input(data['message']) + + if not message: + return jsonify({"error": "Message cannot be empty"}), 400 + + # Optional API key check + if API_KEY and data.get('api_key') != API_KEY: + return jsonify({"error": "Invalid API key"}), 401 + + # Get selected library files + selected_files = data.get('files', []) + file_context = "" + + if selected_files: + file_context_parts = [] + for file_ref in selected_files[:3]: # Limit to 3 files max + if isinstance(file_ref, str): + file_path = file_ref + elif isinstance(file_ref, dict) and 'file' in file_ref: + file_path = file_ref['file'] + else: + continue + + # Extract text from PDF + pdf_text = extract_pdf_text(file_path) + if pdf_text: + # Get file title + file_title = file_ref.get('title', '') if isinstance(file_ref, dict) else file_path + file_context_parts.append(f"\n\n--- Content from: {file_title} ---\n{pdf_text}\n--- End of {file_title} ---") + + if file_context_parts: + file_context = "\n\n[The user has referenced the following library documents. Use this information to provide accurate, specific answers based on these sources:]\n" + "\n".join(file_context_parts) + + # Get conversation history (last 5 messages for context) + history = data.get('history', []) + history_messages = [] + + # Build conversation history + for msg in history[-5:]: # Last 5 messages + if isinstance(msg, dict) and 'role' in msg and 'content' in msg: + history_messages.append({ + "role": msg['role'], + "content": sanitize_input(msg['content']) + }) + + # Add current user message with file context + user_message = message + if file_context: + user_message = message + file_context + + history_messages.append({ + "role": "user", + "content": user_message + }) + + # Call Ollama + try: + client = Client(host=OLLAMA_HOST) + + # Build messages for Ollama + ollama_messages = [ + {"role": "system", "content": SYSTEM_PROMPT} + ] + + # Add conversation history + for msg in history_messages: + ollama_messages.append({ + "role": msg['role'], + "content": msg['content'] + }) + + # Generate response + response = client.chat( + model=MODEL, + messages=ollama_messages, + options={ + "temperature": 0.7, + "top_p": 0.9, + } + ) + + assistant_response = response.message.content.strip() + + return jsonify({ + "response": assistant_response, + "model": MODEL + }) + + except Exception as e: + print(f"Ollama error: {e}", file=sys.stderr) + return jsonify({ + "error": "Unable to connect to AI service. Please try again later." + }), 503 + + except Exception as e: + print(f"API error: {e}", file=sys.stderr) + return jsonify({ + "error": "An error occurred processing your request." + }), 500 + +@app.route('/api/library/files', methods=['GET']) +def list_library_files(): + """List available library files.""" + try: + files = load_library_files() + # Filter to only PDF files for now + pdf_files = [f for f in files if f['filename'].lower().endswith('.pdf')] + return jsonify({ + "files": pdf_files, + "count": len(pdf_files) + }) + except Exception as e: + print(f"Error listing files: {e}", file=sys.stderr) + return jsonify({ + "error": "Failed to load library files" + }), 500 + +@app.route('/api/library/preview', methods=['POST']) +def preview_file(): + """Preview/extract text from a library file.""" + try: + data = request.get_json() + file_path = data.get('file') + + if not file_path: + return jsonify({"error": "File path required"}), 400 + + text = extract_pdf_text(file_path) + + if text is None: + return jsonify({ + "error": "Could not extract text from file. File may not be a PDF or may be corrupted." + }), 400 + + return jsonify({ + "text": text, + "length": len(text) + }) + except Exception as e: + print(f"Error previewing file: {e}", file=sys.stderr) + return jsonify({ + "error": "Failed to preview file" + }), 500 + +@app.route('/api/health', methods=['GET']) +def health(): + """Health check endpoint.""" + return jsonify({ + "status": "healthy", + "model": MODEL, + "ollama_host": OLLAMA_HOST, + "pdf_support": PDF_SUPPORT + }) + +if __name__ == '__main__': + print(f"Starting Chat API server on port {PORT}") + print(f"Ollama endpoint: {OLLAMA_HOST}") + print(f"Model: {MODEL}") + app.run(host='0.0.0.0', port=PORT, debug=False) + diff --git a/scripts/generate_blog_post.py b/scripts/generate_blog_post.py new file mode 100755 index 0000000..7088fea --- /dev/null +++ b/scripts/generate_blog_post.py @@ -0,0 +1,320 @@ +#!/usr/bin/env python3 +""" +Ollama Blog Post Generator + +This script generates a new blog post using Ollama's drjones-posts-to-much model +and adds it to the Hugo blog. It runs automatically every 3 days via systemd timer. + +The script uses random prompt variations to ensure each blog post is unique. +The model generates titles with credentials, which are automatically extracted +and separated from the article content. + +Usage: + python3 generate_blog_post.py + +Configuration: + - Ollama endpoint: 10.30.20.110:11434 + - Model: drjones-posts-to-much + - Blog directory: /root/hydro-sterile/content/blog/ + +Error Handling: + If the Ollama endpoint is down, the script fails quietly (exits with code 0) + to prevent systemd from logging errors. It will retry in 3 days. + +Author: Dr. Jones +""" + +import os +import sys +import json +import random +from datetime import datetime, timedelta +from pathlib import Path + +try: + from ollama import Client +except ImportError: + print("ERROR: ollama Python library not installed. Run: pip3 install ollama", file=sys.stderr) + sys.exit(1) + +# Configuration +OLLAMA_HOST = "http://10.30.20.110:11434" +MODEL = "drjones-posts-to-much" +BLOG_DIR = Path("/root/hydro-sterile/content/blog") +SITE_DIR = Path("/root/hydro-sterile") + +# System prompt for generating blog posts +SYSTEM_PROMPT = """You are Dr. Jones, a sterile hydroponics expert with 20 years of experience growing cannabis in a legal state. +Your writing style is authentic, direct, and knowledgeable. You focus on sterile hydroponics methodology, +growing techniques, and practical advice. Write blog posts that are: +- 300-500 words +- Focused on sterile hydroponics, cannabis cultivation, or related topics +- Written in first person (I, me, my) +- Practical and actionable +- Authentic and conversational + +The model will generate a title with credentials, then the article content.""" + + +def get_next_post_number(): + """Find the next available post number by checking existing posts.""" + existing_posts = list(BLOG_DIR.glob("post-*.md")) + if not existing_posts: + return 1 + + numbers = [] + for post in existing_posts: + try: + # Extract number from filename like "post-1.md" + num = int(post.stem.split("-")[1]) + numbers.append(num) + except (ValueError, IndexError): + continue + + return max(numbers) + 1 if numbers else 1 + + +def generate_filename(post_number): + """Generate the filename for a new post.""" + return BLOG_DIR / f"post-{post_number}.md" + + +def create_frontmatter(title, summary, image_path=None): + """Create YAML frontmatter for Hugo blog post.""" + date_str = datetime.now().strftime("%Y-%m-%dT%H:%M:%SZ") + + frontmatter = f"""--- +title: "{title}" +date: {date_str} +image: "{image_path or '/images/IMG_1767.jpeg'}" +summary: "{summary}" +--- +""" + return frontmatter + + +def generate_random_prompt(): + """Generate a random prompt variation to ensure different content each time.""" + # Different ways to ask for hydroponics knowledge + prompt_variations = [ + "Share your wisdom about sterile hydroponics. What insights can you offer growers today?", + "I'm seeking your expertise on hydroponic cultivation. What knowledge can you impart?", + "Please share your knowledge about sterile hydroponics systems. What should growers know?", + "I need your guidance on hydroponic growing. What can you teach me today?", + "What hydroponic wisdom can you share? I'm eager to learn from your experience.", + "Please enlighten me about sterile hydroponics. What key insights do you have?", + "I'm asking for your hydroponic expertise. What valuable knowledge can you provide?", + "Share your hydroponic knowledge with me. What should I know about sterile systems?", + "I'm seeking your hydroponic wisdom. What can you teach growers today?", + "Please provide your insights on sterile hydroponics. What's important to know?", + "What can you tell me about hydroponic cultivation? I'm looking for your expertise.", + "I need your hydroponic knowledge. What guidance can you offer?", + "Share your experience with sterile hydroponics. What should growers understand?", + "I'm asking for your hydroponic insights. What knowledge can you share?", + "Please enlighten me about hydroponic systems. What wisdom do you have?", + ] + + # Additional random elements to add variety + topic_hints = [ + "", + " Focus on practical techniques.", + " Emphasize sterile methodology.", + " Include real-world examples.", + " Cover common mistakes to avoid.", + " Discuss nutrient management.", + " Talk about root health.", + " Share system design tips.", + " Address problem-solving.", + " Include cultivation tips.", + ] + + # Random opening phrases + openings = [ + "", + "Today, ", + "In this post, ", + "Let me share ", + "Here's what I know: ", + ] + + base_prompt = random.choice(prompt_variations) + topic_hint = random.choice(topic_hints) + opening = random.choice(openings) + + # Combine with some randomness + full_prompt = opening + base_prompt + topic_hint + + return full_prompt + + +def call_ollama(): + """Call Ollama API to generate blog post content.""" + client = Client(host=OLLAMA_HOST) + + try: + # Generate random prompt for variety + user_prompt = generate_random_prompt() + + # Generate the blog post content + response = client.chat( + model=MODEL, + messages=[ + {"role": "system", "content": SYSTEM_PROMPT}, + {"role": "user", "content": user_prompt} + ], + options={ + "temperature": 0.8 + random.uniform(0, 0.2), # Random temperature between 0.8-1.0 for more variety + "top_p": 0.9, + "repeat_penalty": 1.5, # High repeat penalty to avoid repetitive content + } + ) + + content = response.message.content + return content + + except Exception as e: + # Fail quietly - don't raise exception, just return None + # This allows the script to exit gracefully when Ollama is down + print(f"Warning: Could not connect to Ollama at {OLLAMA_HOST}: {e}", file=sys.stderr) + return None + + +def extract_title_and_summary(content): + """Extract title and summary from generated content. + + The model generates title with credentials, then the article. + We need to separate these and extract just the title and article content. + """ + lines = content.strip().split('\n') + + # The model typically generates: "Title - Dr. Jones, Credentials\n\nArticle content" + # Or: "Title\nDr. Jones, Credentials\n\nArticle content" + # Or: "# Title\n\nDr. Jones, Credentials\n\nArticle content" + + title = "" + article_start_idx = 0 + credentials_found = False + + # Look for title (usually first line or first few lines) + # Title might be followed by credentials line, then blank line, then article + for i, line in enumerate(lines): + line_stripped = line.strip() + + # Skip empty lines at the start + if not line_stripped: + continue + + # If we haven't found title yet, this might be it + if not title: + # Remove markdown headers + potential_title = line_stripped.replace('#', '').strip() + + # Check if this line contains credentials (common patterns) + if any(cred in potential_title.lower() for cred in ['dr.', 'dr ', 'phd', 'ph.d', 'credentials', 'expert', 'years']): + # This might be credentials, skip it and look for title before it + if i > 0: + # Previous line might be title + prev_line = lines[i-1].strip().replace('#', '').strip() + if prev_line: + title = prev_line + article_start_idx = i + 1 + credentials_found = True + break + continue + + # If line ends with common credential patterns, split it + if ' - ' in potential_title or ' | ' in potential_title: + parts = potential_title.split(' - ') if ' - ' in potential_title else potential_title.split(' | ') + title = parts[0].strip() + article_start_idx = i + 1 + credentials_found = True + break + else: + # This might be the title, but check next line for credentials + title = potential_title + article_start_idx = i + 1 + + # Check if this is a credentials line (after title) + elif not credentials_found and any(cred in line_stripped.lower() for cred in ['dr.', 'dr ', 'phd', 'ph.d', 'expert', 'years of experience']): + credentials_found = True + article_start_idx = i + 1 + break + + # If we've passed potential title/credentials, this is article content + elif title and not credentials_found and line_stripped: + # No credentials found, article starts here + article_start_idx = i + break + + # Clean up title + if not title: + # Fallback: use first non-empty line + for line in lines: + line_stripped = line.strip().replace('#', '').strip() + if line_stripped: + title = line_stripped + break + + if len(title) > 100: + title = title[:97] + "..." + + # Extract article content (skip title and credentials) + article_lines = lines[article_start_idx:] + article_content = '\n'.join(article_lines).strip() + + # Generate summary from first paragraph of article + summary = "" + for line in article_lines: + line = line.strip() + if line and not line.startswith('#') and len(line) > 20: + summary = line[:150] + if len(line) > 150: + summary += "..." + break + + if not summary: + summary = "A new insight on sterile hydroponics and cultivation techniques." + + return title, summary, article_content + + +def main(): + """Main function to generate and save blog post.""" + # Ensure blog directory exists + BLOG_DIR.mkdir(parents=True, exist_ok=True) + + # Check if Ollama is available + content = call_ollama() + if content is None: + # Fail quietly - exit with code 0 so systemd doesn't log errors + print("Ollama endpoint unavailable. Will retry in 3 days.", file=sys.stderr) + sys.exit(0) + + # Get next post number + post_number = get_next_post_number() + filename = generate_filename(post_number) + + # Extract title, summary, and article content (separate from credentials) + title, summary, article_content = extract_title_and_summary(content) + + # Create frontmatter + frontmatter = create_frontmatter(title, summary) + + # Combine frontmatter and article content (not the full content with credentials) + full_content = frontmatter + "\n" + article_content + "\n" + + # Write to file + try: + with open(filename, 'w', encoding='utf-8') as f: + f.write(full_content) + print(f"Successfully created blog post: {filename}") + print(f"Title: {title}") + return 0 + except Exception as e: + print(f"ERROR: Failed to write blog post: {e}", file=sys.stderr) + sys.exit(1) + + +if __name__ == "__main__": + sys.exit(main()) + diff --git a/scripts/membership_api.py b/scripts/membership_api.py new file mode 100644 index 0000000..d7dcd5b --- /dev/null +++ b/scripts/membership_api.py @@ -0,0 +1,478 @@ +#!/usr/bin/env python3 +""" +Membership API Server + +Handles user authentication, membership management, download tracking, +testimonials, and secure contact form submissions. +""" + +import os +import sys +import sqlite3 +import hashlib +import secrets +import re +import smtplib +import time +from pathlib import Path +from datetime import datetime, timedelta +from email.mime.text import MIMEText +from email.mime.multipart import MIMEMultipart +from functools import wraps + +from flask import Flask, request, jsonify, send_from_directory +from flask_cors import CORS +import bcrypt +import jwt + +# Configuration +PORT = int(os.getenv("MEMBERSHIP_PORT", 80)) +ADMIN_PASSWORD = os.getenv("ADMIN_PASSWORD", "") +ADMIN_EMAIL = os.getenv("ADMIN_EMAIL", "indiana.holmes8@gmail.com") +JWT_SECRET = os.getenv("JWT_SECRET", secrets.token_urlsafe(32)) +SMTP_HOST = os.getenv("SMTP_HOST", "") +SMTP_PORT = int(os.getenv("SMTP_PORT", 587)) +SMTP_USER = os.getenv("SMTP_USER", "") +SMTP_PASS = os.getenv("SMTP_PASS", "") + +# Static folder for Hugo build +STATIC_FOLDER = "/root/hydro-sterile/public" + +# Database path +DB_PATH = Path("/root/hydro-sterile/db/members.db") +DB_PATH.parent.mkdir(parents=True, exist_ok=True) + +app = Flask(__name__) +CORS(app, resources={r"/api/*": {"origins": "*"}}) + +# Static folder for Hugo build +STATIC_FOLDER = "/root/hydro-sterile/public" + +# Rate limiting storage +rate_limits = {} + +def get_client_ip(): + """Get client IP address.""" + if request.headers.get('X-Forwarded-For'): + return request.headers.get('X-Forwarded-For').split(',')[0].strip() + return request.remote_addr or 'unknown' + +def init_db(): + """Initialize database with required tables.""" + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + + # Users table + c.execute('''CREATE TABLE IF NOT EXISTS users + (id INTEGER PRIMARY KEY AUTOINCREMENT, + email TEXT UNIQUE NOT NULL, + password_hash TEXT NOT NULL, + name TEXT NOT NULL, + status TEXT DEFAULT 'pending', + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + approved_at TIMESTAMP, + approved_by TEXT)''') + + # Downloads table + c.execute('''CREATE TABLE IF NOT EXISTS downloads + (id INTEGER PRIMARY KEY AUTOINCREMENT, + ip_address TEXT NOT NULL, + user_id INTEGER, + file_path TEXT NOT NULL, + downloaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id))''') + + # Testimonials table + c.execute('''CREATE TABLE IF NOT EXISTS testimonials + (id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + content TEXT NOT NULL, + author_name TEXT NOT NULL, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + approved BOOLEAN DEFAULT 0, + FOREIGN KEY (user_id) REFERENCES users(id))''') + + # Sessions table + c.execute('''CREATE TABLE IF NOT EXISTS sessions + (id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + session_token TEXT UNIQUE NOT NULL, + expires_at TIMESTAMP NOT NULL, + FOREIGN KEY (user_id) REFERENCES users(id))''') + + # Admin sessions table + c.execute('''CREATE TABLE IF NOT EXISTS admin_sessions + (id INTEGER PRIMARY KEY AUTOINCREMENT, + session_token TEXT UNIQUE NOT NULL, + expires_at TIMESTAMP NOT NULL)''') + + # Contact submissions table + c.execute('''CREATE TABLE IF NOT EXISTS contact_submissions + (id INTEGER PRIMARY KEY AUTOINCREMENT, + user_id INTEGER NOT NULL, + name TEXT NOT NULL, + email TEXT NOT NULL, + message TEXT NOT NULL, + submitted_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + FOREIGN KEY (user_id) REFERENCES users(id))''') + + conn.commit() + conn.close() + +def rate_limit(max_per_minute=10, window_minutes=1): + """Rate limiting decorator.""" + def decorator(f): + @wraps(f) + def decorated_function(*args, **kwargs): + client_ip = get_client_ip() + now = time.time() + window_seconds = window_minutes * 60 + + if client_ip not in rate_limits: + rate_limits[client_ip] = [] + + rate_limits[client_ip] = [ + timestamp for timestamp in rate_limits[client_ip] + if now - timestamp < window_seconds + ] + + if len(rate_limits[client_ip]) >= max_per_minute: + return jsonify({"error": "Rate limit exceeded"}), 429 + + rate_limits[client_ip].append(now) + return f(*args, **kwargs) + return decorated_function + return decorator + +def sanitize_input(text, max_length=1000): + """Sanitize user input.""" + if not text or not isinstance(text, str): + return "" + text = re.sub(r'[<>]', '', text) + return text[:max_length].strip() + +def validate_email(email): + """Validate email format.""" + pattern = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$' + return re.match(pattern, email) is not None + +def send_email(to_email, subject, body_html, body_text=None): + """Send email via SMTP.""" + if not SMTP_HOST or not SMTP_USER or not SMTP_PASS: + print(f"EMAIL NOT CONFIGURED: Would send to {to_email}: {subject}", file=sys.stderr) + return False + + try: + msg = MIMEMultipart('alternative') + msg['Subject'] = subject + msg['From'] = SMTP_USER + msg['To'] = to_email + + if body_text: + msg.attach(MIMEText(body_text, 'plain')) + msg.attach(MIMEText(body_html, 'html')) + + with smtplib.SMTP(SMTP_HOST, SMTP_PORT) as server: + server.starttls() + server.login(SMTP_USER, SMTP_PASS) + server.send_message(msg) + + return True + except Exception as e: + print(f"Email send error: {e}", file=sys.stderr) + return False + +def require_auth(f): + """Require user authentication.""" + @wraps(f) + def decorated_function(*args, **kwargs): + token = request.headers.get('Authorization', '').replace('Bearer ', '') + if not token: + return jsonify({"error": "Authentication required"}), 401 + + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) + user_id = payload.get('user_id') + + # Verify session exists + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('SELECT user_id FROM sessions WHERE session_token = ? AND expires_at > ?', + (token, datetime.utcnow())) + session = c.fetchone() + conn.close() + + if not session: + return jsonify({"error": "Invalid session"}), 401 + + request.user_id = user_id + return f(*args, **kwargs) + except jwt.ExpiredSignatureError: + return jsonify({"error": "Session expired"}), 401 + except Exception as e: + return jsonify({"error": "Invalid token"}), 401 + return decorated_function + +def require_admin(f): + """Require admin authentication.""" + @wraps(f) + def decorated_function(*args, **kwargs): + token = request.headers.get('Authorization', '').replace('Bearer ', '') + if not token: + return jsonify({"error": "Admin authentication required"}), 401 + + try: + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('SELECT id FROM admin_sessions WHERE session_token = ? AND expires_at > ?', + (token, datetime.utcnow())) + session = c.fetchone() + conn.close() + + if not session: + return jsonify({"error": "Invalid admin session"}), 401 + + return f(*args, **kwargs) + except Exception as e: + return jsonify({"error": "Invalid admin token"}), 401 + return decorated_function + +# Initialize database on startup +init_db() + +# ============================================================================ +# API Endpoints +# ============================================================================ + +@app.route('/api/auth/register', methods=['POST']) +@rate_limit(max_per_minute=5, window_minutes=1) +def register(): + """Register a new user.""" + try: + data = request.get_json() + email = sanitize_input(data.get('email', ''), max_length=255) + password = data.get('password', '') + name = sanitize_input(data.get('name', ''), max_length=255) + + if not email or not password or not name: + return jsonify({"error": "Email, password, and name are required"}), 400 + + if not validate_email(email): + return jsonify({"error": "Invalid email format"}), 400 + + if len(password) < 8: + return jsonify({"error": "Password must be at least 8 characters"}), 400 + + password_hash = bcrypt.hashpw(password.encode('utf-8'), bcrypt.gensalt()).decode('utf-8') + + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + try: + c.execute('INSERT INTO users (email, password_hash, name, status) VALUES (?, ?, ?, ?)', + (email, password_hash, name, 'pending')) + user_id = c.lastrowid + conn.commit() + except sqlite3.IntegrityError: + conn.close() + return jsonify({"error": "Email already registered"}), 400 + conn.close() + + email_body = f"

New Membership Signup

Name: {name}

Email: {email}

" + send_email(ADMIN_EMAIL, f"New Membership Signup: {name}", email_body) + + return jsonify({"message": "Registration successful. Pending approval.", "user_id": user_id}), 201 + except Exception as e: + return jsonify({"error": "Registration failed"}), 500 + +@app.route('/api/auth/login', methods=['POST']) +@rate_limit(max_per_minute=5, window_minutes=1) +def login(): + """User login.""" + try: + data = request.get_json() + email = sanitize_input(data.get('email', ''), max_length=255) + password = data.get('password', '') + + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('SELECT id, password_hash, status FROM users WHERE email = ?', (email,)) + user = c.fetchone() + conn.close() + + if not user or not bcrypt.checkpw(password.encode('utf-8'), user[1].encode('utf-8')): + return jsonify({"error": "Invalid email or password"}), 401 + + if user[2] != 'approved': + return jsonify({"error": f"Membership status: {user[2]}"}), 403 + + token = jwt.encode({'user_id': user[0], 'exp': datetime.utcnow() + timedelta(days=30)}, JWT_SECRET, algorithm='HS256') + + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('INSERT INTO sessions (user_id, session_token, expires_at) VALUES (?, ?, ?)', + (user[0], token, datetime.utcnow() + timedelta(days=30))) + conn.commit() + conn.close() + + return jsonify({"token": token, "user_id": user[0], "message": "Login successful"}), 200 + except Exception as e: + return jsonify({"error": "Login failed"}), 500 + +@app.route('/api/auth/logout', methods=['POST']) +@require_auth +def logout(): + token = request.headers.get('Authorization', '').replace('Bearer ', '') + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('DELETE FROM sessions WHERE session_token = ?', (token,)) + conn.commit() + conn.close() + return jsonify({"message": "Logged out"}), 200 + +@app.route('/api/auth/check', methods=['GET']) +def check_auth(): + token = request.headers.get('Authorization', '').replace('Bearer ', '') + if not token: return jsonify({"authenticated": False}), 200 + try: + payload = jwt.decode(token, JWT_SECRET, algorithms=['HS256']) + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('SELECT u.id, u.email, u.name, u.status FROM users u JOIN sessions s ON u.id = s.user_id WHERE s.session_token = ? AND s.expires_at > ?', (token, datetime.utcnow())) + user = c.fetchone() + conn.close() + if user: return jsonify({"authenticated": True, "user_id": user[0], "email": user[1], "name": user[2], "status": user[3]}), 200 + except: pass + return jsonify({"authenticated": False}), 200 + +@app.route('/api/download/count', methods=['GET']) +def get_download_count(): + ip_address = get_client_ip() + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('SELECT COUNT(*) FROM downloads WHERE ip_address = ?', (ip_address,)) + count = c.fetchone()[0] + conn.close() + return jsonify({"count": count, "limit": 1, "unlimited": False}), 200 + +@app.route('/api/download/track', methods=['POST']) +@rate_limit(max_per_minute=10, window_minutes=1) +def track_download(): + data = request.get_json() + file_path = sanitize_input(data.get('file_path', ''), max_length=500) + ip_address = get_client_ip() + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('INSERT INTO downloads (ip_address, file_path) VALUES (?, ?)', (ip_address, file_path)) + conn.commit() + conn.close() + return jsonify({"message": "Tracked"}), 200 + +@app.route('/api/testimonials', methods=['GET', 'POST']) +def handle_testimonials(): + if request.method == 'GET': + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('SELECT id, content, author_name, created_at FROM testimonials WHERE approved = 1 ORDER BY created_at DESC') + rows = c.fetchall() + conn.close() + return jsonify({"testimonials": [{"id": r[0], "content": r[1], "author_name": r[2], "created_at": r[3]} for r in rows]}), 200 + else: + @require_auth + def submit(): + data = request.get_json() + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('INSERT INTO testimonials (user_id, content, author_name, approved) VALUES (?, ?, ?, 0)', (request.user_id, data.get('content'), data.get('author_name'))) + conn.commit() + conn.close() + return jsonify({"message": "Submitted"}), 201 + return submit() + +@app.route('/api/contact', methods=['POST']) +@require_auth +def contact(): + data = request.get_json() + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('INSERT INTO contact_submissions (user_id, name, email, message) VALUES (?, ?, ?, ?)', (request.user_id, data.get('name'), data.get('email'), data.get('message'))) + conn.commit() + conn.close() + return jsonify({"message": "Sent"}), 200 + +@app.route('/api/admin/login', methods=['POST']) +def admin_login(): + data = request.get_json() + if data.get('password') == ADMIN_PASSWORD: + token = jwt.encode({'admin': True, 'exp': datetime.utcnow() + timedelta(hours=24)}, JWT_SECRET, algorithm='HS256') + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('INSERT INTO admin_sessions (session_token, expires_at) VALUES (?, ?)', (token, datetime.utcnow() + timedelta(hours=24))) + conn.commit() + conn.close() + return jsonify({"token": token}), 200 + return jsonify({"error": "Unauthorized"}), 401 + +@app.route('/api/admin/pending', methods=['GET']) +@require_admin +def admin_pending(): + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('SELECT id, email, name, created_at FROM users WHERE status = "pending"') + rows = c.fetchall() + conn.close() + return jsonify({"members": [{"id": r[0], "email": r[1], "name": r[2], "created_at": r[3]} for r in rows]}), 200 + +@app.route('/api/admin/approve', methods=['POST']) +@require_admin +def admin_approve(): + user_id = request.get_json().get('user_id') + conn = sqlite3.connect(DB_PATH) + c = conn.cursor() + c.execute('UPDATE users SET status = "approved" WHERE id = ?', (user_id,)) + conn.commit() + conn.close() + return jsonify({"message": "Approved"}), 200 + +@app.route('/api/health', methods=['GET']) +def health(): + return jsonify({"status":"healthy"}), 200 + +# ============================================================================ +# Static File Serving (CATCH-ALL) +# ============================================================================ + +@app.route('/', defaults={'path': ''}) +@app.route('/') +def serve(path): + # Ignore API routes + if path.startswith('api/'): + return "Not Found", 404 + + full_path = os.path.join(STATIC_FOLDER, path) + + # If path is a directory or empty, serve index.html + if path == "" or os.path.isdir(full_path): + # Check if the directory has an index.html + if os.path.exists(os.path.join(full_path, 'index.html')): + return send_from_directory(full_path, 'index.html') + # Otherwise, check if it's a pretty URL like /about + elif os.path.exists(os.path.join(STATIC_FOLDER, path, 'index.html')): + return send_from_directory(os.path.join(STATIC_FOLDER, path), 'index.html') + return send_from_directory(STATIC_FOLDER, '404.html'), 404 + + # If file exists, serve it + if os.path.exists(full_path): + return send_from_directory(STATIC_FOLDER, path) + + # Handle Hugo's pretty URLs: /about -> /about/index.html + if os.path.exists(os.path.join(STATIC_FOLDER, path, 'index.html')): + return send_from_directory(os.path.join(STATIC_FOLDER, path), 'index.html') + + # Default to 404 + if os.path.exists(os.path.join(STATIC_FOLDER, '404.html')): + return send_from_directory(STATIC_FOLDER, '404.html'), 404 + return "404 Not Found", 404 + +if __name__ == '__main__': + print(f"Starting Membership API + Static Server on port {PORT}") + app.run(host='0.0.0.0', port=PORT, debug=False) diff --git a/scripts/start_hugo_server.sh b/scripts/start_hugo_server.sh new file mode 100755 index 0000000..0ad806d --- /dev/null +++ b/scripts/start_hugo_server.sh @@ -0,0 +1,42 @@ +#!/bin/bash +# +# Hugo Server Startup Script +# +# This script starts the Hugo development server with production-like settings. +# It's designed to be run as a systemd service. +# +# Configuration: +# - Site directory: /root/hydro-sterile +# - Port: 1313 (Hugo default) +# - Bind: 0.0.0.0 (all interfaces) +# - Base URL: From hugo.yaml config +# + +set -e + +SITE_DIR="/root/hydro-sterile" +PORT="${HUGO_PORT:-7575}" +BIND="${HUGO_BIND:-0.0.0.0}" +BASEURL="${HUGO_BASEURL:-$(grep '^baseURL:' hugo.yaml | sed 's/.*: //' | tr -d "'\"")}" + +cd "$SITE_DIR" || exit 1 + +# Check if Hugo is available via npm +if command -v npm >/dev/null 2>&1 && [ -d "node_modules/.bin" ]; then + HUGO_CMD="./node_modules/.bin/hugo" +elif command -v hugo >/dev/null 2>&1; then + HUGO_CMD="hugo" +else + echo "ERROR: Hugo not found. Please install Hugo or run 'npm install' in $SITE_DIR" >&2 + exit 1 +fi + +# Start Hugo server +# Using --noHTTPCache for development, remove for production +exec "$HUGO_CMD" server \ + --bind "$BIND" \ + --port "$PORT" \ + --baseURL "$BASEURL" \ + --appendPort=false \ + --disableFastRender + diff --git a/static/js/membership.js b/static/js/membership.js new file mode 100644 index 0000000..9522df7 --- /dev/null +++ b/static/js/membership.js @@ -0,0 +1,403 @@ +/** + * Membership System Frontend + * Handles authentication, paywall checks, and download tracking + */ + +const getApiBase = () => { + const host = window.location.hostname; + const isLocal = host === 'localhost' || host === '127.0.0.1' || host.startsWith('10.'); + return isLocal ? 'http://' + host + ':5001' : window.location.origin.replace(/\/$/, ''); +}; +const API_BASE = getApiBase() + '/api'; + +let authState = { + authenticated: false, + user: null, + token: null +}; + +// Initialize on page load +document.addEventListener('DOMContentLoaded', function() { + checkAuthStatus(); + setupDownloadTracking(); + setupContactForm(); +}); + +/** + * Check authentication status + */ +async function checkAuthStatus() { + const token = localStorage.getItem('membership_token'); + if (!token) { + updateAuthUI(false); + return; + } + + try { + const response = await fetch(`${API_BASE}/auth/check`, { + headers: { + 'Authorization': `Bearer ${token}` + } + }); + + const data = await response.json(); + if (data.authenticated) { + authState = { + authenticated: true, + user: data, + token: token + }; + updateAuthUI(true); + checkPaywallAccess(); + } else { + localStorage.removeItem('membership_token'); + updateAuthUI(false); + } + } catch (error) { + console.error('Auth check failed:', error); + updateAuthUI(false); + } +} + +/** + * Update UI based on authentication state + */ +function updateAuthUI(authenticated) { + const loginElements = document.querySelectorAll('.membership-login'); + const logoutElements = document.querySelectorAll('.membership-logout'); + const memberElements = document.querySelectorAll('.membership-only'); + const guestElements = document.querySelectorAll('.guest-only'); + + if (authenticated) { + loginElements.forEach(el => el.style.display = 'none'); + logoutElements.forEach(el => el.style.display = 'block'); + memberElements.forEach(el => el.style.display = 'block'); + guestElements.forEach(el => el.style.display = 'none'); + + // Update user name display + const userNameElements = document.querySelectorAll('.user-name'); + userNameElements.forEach(el => { + if (authState.user && authState.user.name) { + el.textContent = authState.user.name; + } + }); + } else { + loginElements.forEach(el => el.style.display = 'block'); + logoutElements.forEach(el => el.style.display = 'none'); + memberElements.forEach(el => el.style.display = 'none'); + guestElements.forEach(el => el.style.display = 'block'); + } +} + +/** + * Check paywall access and show/hide content + */ +async function checkPaywallAccess() { + if (!authState.authenticated) { + // Check download count for IP + try { + const response = await fetch(`${API_BASE}/download/count`); + const data = await response.json(); + + if (data.limit_reached || (data.count >= data.limit && !data.unlimited)) { + showPaywall(); + } + } catch (error) { + console.error('Download count check failed:', error); + } + } +} + +/** + * Show paywall message + */ +function showPaywall() { + const paywallElements = document.querySelectorAll('.paywall-message'); + paywallElements.forEach(el => { + el.style.display = 'block'; + }); + + const paywallContent = document.querySelectorAll('.paywall-content'); + paywallContent.forEach(el => { + el.style.display = 'none'; + }); +} + +/** + * Hide paywall message + */ +function hidePaywall() { + const paywallElements = document.querySelectorAll('.paywall-message'); + paywallElements.forEach(el => { + el.style.display = 'none'; + }); + + const paywallContent = document.querySelectorAll('.paywall-content'); + paywallContent.forEach(el => { + el.style.display = 'block'; + }); +} + +/** + * Setup download tracking + */ +function setupDownloadTracking() { + document.addEventListener('click', async function(e) { + const downloadLink = e.target.closest('a[download], a[href*="/library/"], a[href*="/pdfs/"]'); + if (!downloadLink) return; + + const filePath = downloadLink.getAttribute('href') || downloadLink.getAttribute('download'); + if (!filePath) return; + + // Check if user is authenticated + if (!authState.authenticated) { + // Check download count + try { + const response = await fetch(`${API_BASE}/download/count`); + const data = await response.json(); + + if (data.count >= data.limit && !data.unlimited) { + e.preventDefault(); + alert('You have reached the free download limit. Please sign up for membership to download unlimited resources.'); + window.location.href = '/membership/'; + return; + } + } catch (error) { + console.error('Download check failed:', error); + } + } + + // Track download + try { + await fetch(`${API_BASE}/download/track`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + ...(authState.token ? { 'Authorization': `Bearer ${authState.token}` } : {}) + }, + body: JSON.stringify({ file_path: filePath }) + }); + } catch (error) { + console.error('Download tracking failed:', error); + // Don't prevent download if tracking fails + } + }); +} + +/** + * Setup contact form + */ +function setupContactForm() { + const contactForm = document.getElementById('contact-form'); + if (!contactForm) return; + + contactForm.addEventListener('submit', async function(e) { + e.preventDefault(); + + if (!authState.authenticated) { + alert('Please log in to submit the contact form.'); + window.location.href = '/membership/'; + return; + } + + const formData = new FormData(contactForm); + const data = { + name: formData.get('name'), + email: formData.get('email'), + message: formData.get('message') + }; + + try { + const response = await fetch(`${API_BASE}/contact`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authState.token}` + }, + body: JSON.stringify(data) + }); + + const result = await response.json(); + if (response.ok) { + alert('Message sent successfully!'); + contactForm.reset(); + } else { + alert(result.error || 'Failed to send message'); + } + } catch (error) { + console.error('Contact form error:', error); + alert('Failed to send message. Please try again.'); + } + }); +} + +/** + * User login + */ +async function login(email, password) { + try { + const response = await fetch(`${API_BASE}/auth/login`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email, password }) + }); + + const data = await response.json(); + if (response.ok) { + localStorage.setItem('membership_token', data.token); + authState = { + authenticated: true, + user: data, + token: data.token + }; + updateAuthUI(true); + hidePaywall(); + return { success: true }; + } else { + return { success: false, error: data.error }; + } + } catch (error) { + return { success: false, error: 'Login failed. Please try again.' }; + } +} + +/** + * User logout + */ +async function logout() { + const token = localStorage.getItem('membership_token'); + if (token) { + try { + await fetch(`${API_BASE}/auth/logout`, { + method: 'POST', + headers: { + 'Authorization': `Bearer ${token}` + } + }); + } catch (error) { + console.error('Logout error:', error); + } + } + + localStorage.removeItem('membership_token'); + authState = { + authenticated: false, + user: null, + token: null + }; + updateAuthUI(false); + window.location.reload(); +} + +/** + * User registration + */ +async function register(email, password, name) { + try { + const response = await fetch(`${API_BASE}/auth/register`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ email, password, name }) + }); + + const data = await response.json(); + if (response.ok) { + return { success: true, message: data.message }; + } else { + return { success: false, error: data.error }; + } + } catch (error) { + return { success: false, error: 'Registration failed. Please try again.' }; + } +} + +/** + * Submit testimonial + */ +async function submitTestimonial(content, authorName) { + if (!authState.authenticated) { + return { success: false, error: 'Please log in to submit a testimonial' }; + } + + try { + const response = await fetch(`${API_BASE}/testimonials`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + 'Authorization': `Bearer ${authState.token}` + }, + body: JSON.stringify({ content, author_name: authorName }) + }); + + const data = await response.json(); + if (response.ok) { + return { success: true, message: data.message }; + } else { + return { success: false, error: data.error }; + } + } catch (error) { + return { success: false, error: 'Failed to submit testimonial' }; + } +} + +/** + * Load testimonials + */ +async function loadTestimonials() { + try { + const response = await fetch(`${API_BASE}/testimonials`); + const data = await response.json(); + if (response.ok) { + return { success: true, testimonials: data.testimonials }; + } else { + return { success: false, error: data.error }; + } + } catch (error) { + return { success: false, error: 'Failed to load testimonials' }; + } +} + +/** + * Check if the user has reached their download limit (guests only) + */ +async function checkDownloadLimit() { + if (authState.authenticated) { + document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'none'); + document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'block'); + document.querySelectorAll('.guest-only').forEach(el => el.style.display = 'none'); + document.querySelectorAll('.membership-only').forEach(el => el.style.display = 'block'); + return; + } + + try { + const response = await fetch(`${API_BASE}/download/count`); + const data = await response.json(); + + if (data.count >= data.limit && !data.unlimited) { + document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'block'); + document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'none'); + } else { + document.querySelectorAll('.paywall-message').forEach(el => el.style.display = 'none'); + document.querySelectorAll('.paywall-content').forEach(el => el.style.display = 'block'); + } + } catch (error) { + console.error('Download limit check failed:', error); + } +} + +// Export functions for use in Alpine.js or other scripts +window.MembershipAPI = { + login, + logout, + register, + submitTestimonial, + loadTestimonials, + checkAuthStatus, + checkDownloadLimit, + getAuthState: () => authState +}; + diff --git a/static/pdfs/cleaning.pdf b/static/pdfs/cleaning.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/deficiencies.pdf b/static/pdfs/deficiencies.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/dutch-buckets.pdf b/static/pdfs/dutch-buckets.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/dwc-basics.pdf b/static/pdfs/dwc-basics.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/lighting.pdf b/static/pdfs/lighting.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/nft-guide.pdf b/static/pdfs/nft-guide.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/pests.pdf b/static/pdfs/pests.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/ph-ec-mastery.pdf b/static/pdfs/ph-ec-mastery.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/seeds.pdf b/static/pdfs/seeds.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/pdfs/sterile-res.pdf b/static/pdfs/sterile-res.pdf new file mode 100644 index 0000000..e69de29 diff --git a/static/robots.txt b/static/robots.txt new file mode 100644 index 0000000..796539e --- /dev/null +++ b/static/robots.txt @@ -0,0 +1,5 @@ +User-agent: * +Allow: / + +Sitemap: https://hydrolord.thetempleofdoom.com/sitemap.xml +