The exact system running inside Moonlight and Lace Boudoir. Not a template. Not a mockup. The real build, handed to you with every file, every feature, and every line of code.
This is a complete, password-protected client portal that lives on its own domain. Your client gets a login. She logs in, lands on her personal dashboard, and from there she has access to everything related to her session... her prep materials, her paperwork, her gallery, and her collection details.
The gallery is connected to Cloudflare R2 cloud storage. You upload her images from a photographer dashboard. The moment images are in her folder, her gallery comes alive. She can view, heart her favorites, watch a cinematic slideshow with music, see her images as wall art in a real room, and send her selections directly to you via pre-filled text message.
This is not a link to a Google Drive folder. This is not Pic-Time or Shootproof. This is your own branded system, on your domain, under your full control, for a fraction of the ongoing cost of any gallery delivery platform.
The portal has two sides. The client-facing side is what she sees when she logs in. The photographer dashboard is what you use to upload images, manage galleries, and send notifications to your clients.
Her gallery experience includes watermark protection on preview images, a favorites system that saves to the cloud in real time, a cinematic fullscreen slideshow with music, a wall art mockup viewer where she can place her image inside a real room, and a post-selection flow that walks her into your upgrade calculator and routes her directly into your ordering experience.
This is the full system. Nothing is hidden. Nothing is watered down. You get every file, every feature, every line of code, with placeholder content already in place so you know exactly what to swap.
Here is exactly what this build does, broken down so you understand what you are building and what your client experiences at every step.
Built on Netlify Identity. Clients receive an invitation email and create their own password. You control access from your Netlify dashboard. No client can view the portal without your approval. Sessions persist so she does not have to log in every visit, but expire after a set period for security. Login is handled entirely by Netlify at no extra cost.
Her home base after login. Shows her name, session information, quick-access buttons to every section of the portal, and a progress indicator showing where she is in the client journey. All content is pulled dynamically from her gallery record stored in Cloudflare R2, so each client sees their own personalized experience.
A cinema-quality masonry-style gallery that automatically adjusts row heights based on each image's aspect ratio. Portrait and landscape images flow together cleanly without awkward cropping. Works perfectly on both mobile and desktop. Images load lazily as she scrolls so even large galleries of 100+ images load without slowing down her device.
She taps the heart on any image to favorite it. Her favorites save to Cloudflare R2 in real time. If she closes her browser and comes back a week later, her hearts are still there exactly as she left them. A floating selection bar at the bottom shows her count in real time and lets her send selections when she is ready. No account, no app download, no extra steps for her.
A fullscreen experience that plays through her gallery with a slow Ken Burns zoom effect and gentle crossfades between images. Background music starts automatically at a soft volume. She can toggle music on or off, pause, skip forward or back, and use arrow keys on desktop. The slideshow ends gracefully. You choose your own royalty-free track from Pixabay and upload it to your R2 bucket.
A separate reveal experience (reveal.html) designed for in-person reveal appointments or a self-guided reveal she watches from home. Features per-image duration controls you set before sending, drag-and-drop image sequencing, and a cinematic full-bleed presentation mode. This is the experience she watches when she sees her images for the very first time.
The watermark is controlled per gallery. Turn it on for preview galleries (unpurchased images), turn it off for delivered galleries. When on, a tiled semi-transparent watermark overlay covers every image in the grid, the lightbox, and the slideshow. Right-click to save, drag-to-desktop, and keyboard shortcuts are all blocked. An agreement gate appears before she can even enter the gallery requiring her to acknowledge your terms before viewing.
Clicking any image opens a full-resolution lightbox with smooth animation. She navigates with arrow keys or swipes on mobile. A counter shows her position in the gallery. On clean (purchased) galleries, a Download button appears for individual saves. A heart button lets her favorite directly from the lightbox. A "See it on your wall" button opens the wall art viewer for that specific image.
Her image placed inside a real room photo. She can drag the print, rotate it, and tilt it to see it at an angle on the wall. Multiple room backgrounds (bedroom, nightstand vignette, gallery wall) and multiple product sizes (8x10 bedside metal, 16x24, 20x30, 24x36, 40x60, and a wall gallery trio set) let her visualize exactly what a piece looks like before she commits. A CTA at the bottom of the viewer routes her directly into the ordering experience.
After she taps Send My Selections, the system checks whether she has favorited every image. If she has not, a Stop Moment screen appears showing her the images she is about to leave behind and offering her the option to keep them all before proceeding. After the stop moment, she enters an upgrade calculator where she can add wall art, albums, acrylic blocks, a USB, or upgrade her package. PA sales tax calculates automatically by ZIP code. Everything routes to your GHL order webhook.
The ordering.html file is a full appointment-based ordering experience. It walks her through selecting her package, reviewing her selections, customizing physical products, choosing a payment structure, and submitting her order. The order data sends to your GHL webhook and triggers your fulfillment workflow. This file is large and has multiple routing sections for different package tiers.
When she saves the portal to her phone's home screen and enables notifications, she receives push alerts directly from you. You send notifications from your photographer dashboard... gallery ready, paperwork reminder, selection deadline reminder, order confirmation. A notification bell in her sidebar shows an unread badge. Full push delivery to the home screen requires VAPID key setup. In-portal notification delivery works without it. Instructions in Section 17.
A dedicated Notifications page inside her portal shows every message you have sent in reverse chronological order. Unread messages are marked. The sidebar bell badge updates automatically when she has unread messages and clears when she visits the page. She can see the full communication history from you without searching through texts or emails.
On galleries with watermark turned off (purchased, delivered images), a Download All button appears. It bundles all images into a single ZIP file using JSZip, named with her first name and your studio name, and downloads directly to her device. Falls back to individual downloads automatically if the ZIP fails. Individual image download is also available inside the lightbox on clean galleries.
A password-protected drag-and-drop upload interface only you access. You select a client folder, drag in her images, set the gallery type (preview or clean delivery), add an expiry date, include a personal note, and publish. The gallery goes live immediately. No FTP client. No backend system. No third party gallery platform. Everything writes directly to your Cloudflare R2 bucket.
A separate dashboard showing you client activity across all galleries. Which clients have viewed their gallery, which images they opened, how many times, whether they sent selections, and whether they submitted an upgrade request. Activity logs write to R2 in real time. You always know exactly where each client is in the process without having to follow up manually.
A filterable inventory of your studio wardrobe. Clients browse by category, view item details, see size ranges and descriptions, and note what is available before their session. You replace the placeholder product images and descriptions with your actual closet inventory. Full closet setup instructions in Section 18.
Basics, Prep, and Arrival pages give your client everything she needs before her session. These pages contain your studio-specific prep instructions, what to bring, what to expect on session day, and how to arrive. You replace all placeholder content with your own. These are the pages that eliminate your prep emails and get clients showing up camera-ready.
A full-screen welcome display designed to run on a TV or monitor in your studio lobby or shooting area when a client arrives. Shows her name, a welcome message, and your studio branding. Update this page for each client from your dashboard before she arrives. Creates an immediate luxury first impression the moment she walks in the door.
Two fully built HTML email templates are included. One for gallery delivery (images-ready-email.html) and one for download delivery (images-downloadable-email.html). These are formatted HTML emails that render beautifully in any email client, match your portal branding exactly, and include your logo, buttons, and studio information. Copy them into your GHL email builder as custom HTML.
A post-session questionnaire (questionnaire.html) and a post-delivery feedback survey (post-session-survey.html) are included in the portal. These are browsable pages within her client portal. Customize the questions and link them to your GHL forms or internal tracking system.
An interactive album layout designer (album-designer.html) where clients can arrange their selected images into album spreads before their order is finalized. Helps clients visualize their album and gives you a clear direction for the album layout without a back-and-forth approval process.
This is not a template where you swap a logo and publish in 20 minutes. This is a real, functioning, custom-coded portal system with cloud storage, an image delivery pipeline, a login system, push notifications, a cinematic reveal experience, an ordering flow, and more than two dozen interconnected files. The real version runs live inside an active boudoir studio. Getting YOUR version running requires real time and real effort.
Work in phases. Do not try to do everything at once. The build walkthrough in Sections 9 through 13 breaks this into five distinct phases. Finish one phase, test it, and move to the next.
Test after every single change. Do not make 10 changes and then deploy. Make one change, deploy, check that it works, and then move on. This is how you avoid ending up with a broken system and no idea which change caused it.
Use Claude or ChatGPT for every question. No question is too small. If you do not understand something, ask. If something looks wrong, paste the code and ask. Section 14 shows you exactly how to do this effectively.
They do not try to rush it. They block out real time on their calendar, they work through one step at a time, and they treat every stuck moment as a question to ask AI rather than a reason to give up. Every piece of this system is solvable. None of it requires a computer science degree. It requires patience and a willingness to paste code into a chat box and read the response.
All of these are free to create. Some have optional paid tiers. Here is exactly what each one is for, how to sign up, and what you will need from each one during the build.
Cloudflare is where all your images, watermarks, music, and client data live. The gallery system is built on Cloudflare R2, which is their cloud storage product. Every image your client sees is served from R2. Every heart she saves writes back to R2. Every notification you send stores in R2. This is the backbone of the entire system.
How to create your account: Go to cloudflare.com and sign up for a free account using your email. Once inside your dashboard, find R2 Object Storage in the left sidebar. Click Create Bucket. Name the bucket galleries (lowercase, no spaces, exactly that word). Under the bucket's Settings tab, find Public Access and enable it. Copy the public URL that appears. It will look like this:
That URL goes everywhere in your code where you see [YOUR R2 PUBLIC URL].
Creating your R2 API keys: In the Cloudflare dashboard, go to R2 in the left sidebar and click Manage API Tokens. Click Create API Token. Give it a name like "Portal Upload." Under Permissions, select Object Read and Write. Under Specify Bucket, select your galleries bucket. Click Create API Token. You will see two values... an Access Key ID and a Secret Access Key. Copy both immediately because you cannot see the secret key again after leaving this screen. These go in your code as [YOUR R2 ACCESS KEY] and [YOUR R2 SECRET KEY].
Your R2 endpoint for uploads looks like this:
Find it in R2 → your bucket → Settings. This goes in your code as [YOUR R2 ENDPOINT].
Netlify hosts all your portal HTML files and handles the client login system through a built-in product called Netlify Identity. When you make a change to any file and redeploy, it goes live within 30 to 60 seconds. Your portal URL lives here.
How to create your account: Go to netlify.com and sign up for free with your email or Google account. Once inside, click Add New Site. The easiest option is Deploy Manually, which lets you drag and drop your portal folder directly. Netlify gives you a temporary URL like random-words.netlify.app immediately. You can connect your own custom domain later.
How to enable the login system: After your first deploy, go to your site in the Netlify dashboard. Click Site Settings in the top menu. Find Identity in the left sidebar. Click Enable Identity. Then scroll down to Registration and set it to Invite Only. This is what powers client logins. Once enabled, you can invite clients directly from the Identity tab by entering their email address. They will receive an email to set their password.
Free vs Paid: The free tier is fully functional. It gives you 300 build minutes per month. Each time you redeploy (drag a new version of your folder), it uses build minutes. For a stable, live studio portal, free works well. If you are actively developing and redeploying multiple times per day, you may hit the limit. The $9/month Pro plan removes this concern entirely.
How to connect your custom domain: In Site Settings → Domain Management → Add Custom Domain, type in the domain or subdomain you want (like vip.yourstudio.com). Netlify will show you the exact DNS records to add in your domain registrar (wherever you bought your domain). Add the CNAME or A record as instructed. DNS usually propagates within 5 to 30 minutes. Netlify also automatically provisions a free SSL certificate once your domain is connected.
VS Code is the code editor you will use to open and edit your portal files. It is free, it works on Mac and Windows, and it makes HTML files readable and navigable. You do not need to understand code to use it. You are using it as a powerful text editor that knows how to search across dozens of files at once.
How to get it: Go to code.visualstudio.com and download the version for your operating system. Install it and open it. Go to File → Open Folder and select your downloaded portal folder. All your HTML files appear in the left sidebar. Click any file to open it.
The two commands you will use most:
The search-across-all-files feature is how you find every placeholder efficiently and make sure you have not missed any instance of something that appears across multiple files.
You need one of these open the entire time you are building. When something breaks, when you do not know what a placeholder means, when something looks wrong, when you want to change something and are not sure how... you paste the code in and ask. This is how non-developers successfully build systems like this. This is literally how this portal was built.
Claude (claude.ai) is recommended. The paid Claude Pro plan is around $20/month and gives you a large context window, meaning you can paste entire large files like gallery.html without hitting a character limit. The free tier has smaller limits and may cut off large files. Start with free and upgrade if you hit the limit.
ChatGPT (chat.openai.com) also works. The Plus plan is also $20/month and gives you GPT-4o with a similar large context window.
Section 14 of this lesson covers exactly how to use these tools effectively, what to paste, how to phrase your questions, and what to do when you get a response you don't understand.
Pixabay Music is where you get your slideshow background music. It is completely royalty-free with no attribution required and no commercial restrictions. You download the MP3, upload it to your R2 bucket, and reference the URL in gallery.html.
How to find the right track: Go to pixabay.com/music and search for terms like "cinematic romantic," "soft piano ambient," "luxury editorial," "ethereal feminine," or "slow emotional piano." Filter by duration if you want something longer. Longer tracks (3+ minutes) feel more natural before they loop. Download the MP3 file. Instructions for uploading it are in Section 16.
These are third party services you sign up for directly. The prices below are accurate as of the time this lesson was written. Always verify current pricing on each platform's website before signing up.
Free for the first 10GB of storage and 10 million read operations per month. After that, storage costs $0.015 per GB. For context... 500 full-resolution boudoir images at an average of 8MB each is about 4GB total. One typical client gallery is well under 1GB. You would need a very high volume of active galleries simultaneously to exceed the free tier.
Most studios using this system pay $0 to $3 per month on Cloudflare R2. Even at full studio capacity with 10 to 15 active client galleries at once, you are unlikely to exceed $5/month.
Free tier gives you 300 build minutes per month and 100GB of bandwidth. Each time you deploy a change (drag a new folder), it uses build minutes. For a stable live studio portal where you are making occasional updates, free works perfectly.
If you are actively developing and redeploying many times per day during setup, you may approach the monthly limit. The $9/month Pro plan removes this concern. Start free and upgrade only if you need to. Once the portal is live and stable, redeployments become infrequent.
Most studios pay $0 to $9 per month on Netlify depending on whether they are actively developing.
Compare this to a dedicated gallery delivery platform like Shootproof ($10 to $45/month) or Pic-Time ($10 to $20/month plus storage fees), and this system pays for itself with a single month of use. You own it. There are no per-client fees. No storage caps that force you to delete client work. No branding you cannot remove. This is your system, permanently.
Install these before you open a single file. Having the right tools set up from the beginning saves you from frustrating moments mid-build.
Go to code.visualstudio.com. Download the version for your OS (Mac or Windows). Install it. This is your code editor for the entire build. When you open your portal folder in VS Code, you will see all files in the left sidebar and can click any one to open and edit it.
Chrome or Edge are both excellent for this. Firefox also works. Safari works but its developer tools are slightly different. During the build, you will use the browser's developer console (press F12) to see error messages when something breaks. This is your diagnostic tool throughout the build.
Before you create a single account, open a Google Doc, Notes file, or any text document and title it "Portal Build Credentials." Every URL, key, password, and token you generate goes in here immediately. You will create credentials across Cloudflare and Netlify. If you lose a key mid-build, you usually have to revoke it and generate a new one, which can break things already in place.
Before you start Phase 1, open claude.ai or chat.openai.com and keep it in a tab for the entire build. Every question goes here. Every error goes here. Every placeholder you are unsure about goes here. This is your co-developer for the entire project.
Here is every file in the portal package and exactly what it does. Do not be overwhelmed by the list. You will only need to actively edit a handful of them to make this yours. The rest either work automatically or contain content you customize at your own pace.
portal-nav.js ... edit this FIRST. Your logo URL and studio name go here. It drives the navigation on every single page.
gallery.html ... your R2 public URL, R2 endpoint, R2 access key, R2 secret key, your watermark URL, your music URL, your phone number for SMS pre-fill, and your studio name. Nothing in the gallery works without correct R2 credentials.
reveal.html ... your R2 credentials and studio branding. Must match gallery.html credentials exactly.
ordering.html ... your GHL order webhook URL, your collection package names, prices, and your studio branding. Large file with multiple routing sections.
collection.html ... your pricing, package names, and GHL payment link URLs for each package.
closet.html, basics.html, prep.html, arrival.html, bonus.html, body-love-workbook.html, questionnaire.html, post-session-survey.html, and welcome.html all contain placeholder content that you can replace gradually as you have time. The portal functions without them being fully customized. Get the gallery working first, then come back to these.
Every placeholder in the code is labeled clearly inside brackets. Use Ctrl+Shift+F in VS Code to search for the bracket character [ and see every placeholder across all files at once. Here is what each one means and where to find the value it needs.
Your full studio name as you want it displayed to clients. Appears in page titles, footers, SMS pre-fill messages, and email templates. Found in almost every file.
The direct URL to your logo image. Must be a publicly accessible image URL ending in .png or .jpg. Host it on your GHL CDN, Cloudflare R2, or any image hosting service. Found in portal-nav.js and multiple pages.
Your studio phone number in plain digits (no dashes or spaces needed in the SMS link format). Appears in SMS pre-fill buttons throughout the gallery. When she taps Send My Selections, a text to this number pre-fills automatically.
Your studio email address for footer contact links and any contact forms.
Your studio address displayed in footer areas of gallery and dashboard pages.
Your portal domain once connected. Example: vip.yourstudio.com or yourstudio.netlify.app. Used in redirect logic and internal links.
Your Cloudflare R2 bucket public URL. Starts with https://pub- and ends with .r2.dev. Found in Cloudflare → R2 → your bucket → Settings → Public URL. Goes in gallery.html, reveal.html, and dashboard.html as the variable R2_PUB.
Your Cloudflare R2 storage endpoint for authenticated uploads. Found in R2 → your bucket → Settings → Bucket Details. Looks like https://accountid.r2.cloudflarestorage.com. Goes in gallery.html and the upload dashboard as the variable R2_EP.
Your Cloudflare R2 API Access Key ID. Generated under R2 → Manage API Tokens. Goes in gallery.html as the variable R2_KEY. This is not your Cloudflare account login password.
Your Cloudflare R2 API Secret Access Key. Copy it immediately when generated because Cloudflare only shows it once. Goes in gallery.html as the variable R2_SEC. Keep this private.
Direct URL to your watermark PNG file uploaded to Cloudflare R2. The file should be a PNG with a transparent background. Upload it to the root of your galleries bucket, then copy its public URL. Goes in gallery.html as the variable WM_URL.
Direct URL to your royalty-free MP3 file uploaded to Cloudflare R2. Upload your Pixabay MP3 to the root of your galleries bucket, then copy its public URL. Replaces the audio src tag inside gallery.html.
Your GoHighLevel order webhook URL. Used in ordering.html to receive order submissions. Found in GHL under Automation → Webhooks. If you do not have a GHL webhook set up yet, you can temporarily use a free webhook testing service like webhook.site while building, and swap in your real URL when your GHL workflow is ready.
Your collection package names as you want them to appear to clients. Found in collection.html and ordering.html. Replace every instance of the placeholder package names with your actual studio collection names.
Your GHL payment link URLs for each package. These are the links that send clients to your payment page after they select a package. Found in collection.html and ordering.html. Generate these in GHL under Payments → Payment Links.
Your R2 Access Key and Secret Key give write access to your storage bucket. Do not paste them into social posts, do not share them in group chats, and do not commit them to a public GitHub repository. They live inside your HTML files on your deployed Netlify site. Since the site is deployed (not a public repo), the keys are not exposed as raw source to visitors. Be mindful of who you share your code files with.
Do not touch a code file yet. Set up your cloud storage first. Everything depends on this being correct before you deploy anything.
Have your credentials document open. You will copy values into it throughout this phase. Do not skip this step or you will lose keys.
Go to cloudflare.com. Click Sign Up. Enter your email and a password. Verify your email. You do not need to add a domain or website. Just create the account and log in.
In your Cloudflare dashboard, find R2 Object Storage in the left sidebar. If you don't see it, look for Storage or click the hamburger menu. Click Create Bucket. Name it exactly: galleries (lowercase, no spaces, no hyphens). Choose your region as closest to you. Click Create Bucket.
Inside your galleries bucket, click the Settings tab. Scroll to Public Access. Click Allow Access. A public URL will appear that starts with pub-. Copy it and paste it into your credentials document as "R2 Public URL." This URL is how every image in the portal is served to clients.
Still in the Settings tab of your bucket, find Bucket Details. There will be an S3-compatible endpoint URL. It looks like https://accountid.r2.cloudflarestorage.com. Copy it to your credentials document as "R2 Endpoint."
Go back to the main R2 page (click R2 in the left sidebar, not your bucket). Click Manage API Tokens in the top right. Click Create API Token. Name it "Portal." Under Permissions, select Object Read and Write. Under Specify Bucket, select your galleries bucket only. Click Create API Token.
On the next screen, you will see your Access Key ID and your Secret Access Key. Copy both of these immediately. The secret key is never shown again after you leave this screen. Paste both into your credentials document.
Go into your galleries bucket and click Upload. First, upload your watermark PNG file (see Section 15 for how to make one). Then upload your Pixabay music MP3 (see Section 16 for how to find one). Both files should sit in the root of the bucket, not inside any folder. After uploading each file, click it and copy its public URL. Add both to your credentials document as "Watermark URL" and "Music URL."
Now you open your portal files and put your credentials and information in. Work through these in the order listed. Do not skip files and circle back later.
Open Visual Studio Code. Go to File → Open Folder. Navigate to and select the portal folder you downloaded from this page. All your HTML files appear in the Explorer panel on the left.
Click portal-nav.js in the left sidebar. This file controls the navigation that appears on every single page. Find and replace:
Save the file with Ctrl+S or Cmd+S. Updating this file once applies your branding to the entire portal navigation automatically.
Open gallery.html. Press Ctrl+F or Cmd+F and search for R2_PUB. You will find a variable block near the top of the script section. Replace each value with the credentials from your credentials document:
Then find the audio tag and replace the music URL:
Then find and replace your phone number (search for the SMS link format), your studio name, your logo URL, and your address. Save the file.
Open reveal.html. Search for R2_PUB and replace the same credential block with the same values you used in gallery.html. The credentials must match exactly. Also replace your studio name and logo URL. Save.
Open ordering.html. This is a large file. Search for GHL_WEBHOOK or ORDER_WEBHOOK and replace the placeholder with your GHL order webhook URL. Then search for your package name placeholders and replace with your actual collection names and prices. Search for payment link placeholders and replace with your GHL payment link URLs. Save.
If you do not have your GHL webhook set up yet, use webhook.site (a free temporary URL) as a placeholder so you can test the rest of the system. Swap in your real GHL webhook when your workflow is ready.
Open collection.html. Replace your package names, prices, and payment link URLs. This is the page where she reviews her investment options. Make sure the package names here match exactly what appears in ordering.html.
In VS Code, press Ctrl+Shift+F (Windows) or Cmd+Shift+F (Mac) to open the global search. Search for the [ character. This will show you every remaining placeholder across all files. Work through each one. Some are in prep pages and closet.html, which you can fill in later. But make sure gallery.html, reveal.html, ordering.html, and collection.html have zero remaining placeholders.
Go to netlify.com. Click Sign Up. You can sign up with email or connect via Google. Once inside, you land on the Teams dashboard.
Click Add New Site → Deploy Manually. A drag-and-drop zone appears. Open your file explorer or Finder, navigate to your portal folder, and drag the entire folder into the Netlify drop zone. Do not zip it. Drag the folder itself. Netlify will upload all files and deploy them. Within 30 to 60 seconds, it gives you a live URL like random-words.netlify.app. Click the URL to see your portal live.
In your Netlify dashboard, click your site name. Click Site Settings at the top. Find Identity in the left sidebar. Click Enable Identity. Under Registration preferences, set to Invite Only. This activates the client login system. Without this step, no one can log in to the portal.
In Netlify, go to Identity. Click Invite Users. Enter your own email address and click Send. Check your email for the invitation. Click the link in the email, set a password, and you will be logged in to your portal. Navigate through each page to make sure everything loads correctly. If something shows a blank page or an error, press F12, check the Console tab for red errors, and bring them to Claude or ChatGPT.
In Site Settings → Domain Management → Add Custom Domain, enter the subdomain you want (like vip.yourstudio.com). Netlify shows you the DNS records to add in your domain registrar. Log in to wherever you bought your domain (GoDaddy, Namecheap, Cloudflare, etc.) and add the CNAME or A record exactly as Netlify instructs. DNS propagation takes anywhere from 5 minutes to 48 hours. Netlify automatically provisions your free SSL certificate once the domain resolves. Until then, use the netlify.app URL.
Every time you make a change to any file in VS Code and want to see it live, save the file, then go back to Netlify. Under Deploys, click the Deploy Manually option and drag your updated folder in again. Wait 30 to 60 seconds, then hard refresh your browser with Ctrl+Shift+R (Windows) or Cmd+Shift+R (Mac). Do not use a regular refresh. Hard refresh clears the cached version so you see the actual updated page.
Now that the core system is live and tested, go through each page and make it fully yours. Work through these in whatever order makes sense for your studio's priorities.
Replace all placeholder studio name, logo URL, and welcome messaging with your own. This is the first page she sees after login. It should feel immediately like YOUR studio. Replace any generic language with your voice and your brand.
Open each of these and replace all placeholder studio information and prep content with your own. Your specific prep instructions, what you tell clients about skin care, what to bring, how to find the studio, where to park, and what to expect when they arrive. These pages eliminate your prep emails. Make them complete and detailed.
Replace every placeholder product image URL and product description with your actual closet inventory. Photograph your pieces, upload the images to your R2 bucket inside a folder called closet, copy each image URL, and paste it into the corresponding item block. Full closet setup instructions are in Section 18.
Customize the welcome page with your logo, branding colors, and the welcome message you want displayed when a client arrives at your studio. This page is designed to run on a TV or monitor. Update it before each client's arrival day to show her name.
Open each email template, replace the placeholder logo URL, studio name, and any messaging with your own. Then copy the full HTML from each file and paste it into GHL as a custom HTML email template. These are the emails your clients receive when their gallery is ready.
Do not send a client to this portal until you have tested every step of the experience yourself, from login through selection submission. Use a test gallery with your own images before going live with a real client.
From your photographer upload dashboard (your portal URL /gallery or however the dashboard is routed), create a folder for a test client. Name it something like "test-client." Upload 10 to 15 test images into that folder. Set the gallery type to Preview and turn watermark on. Note the exact folder name because you will need it to access the test gallery.
Log in to the portal as your test client account. Navigate to gallery.html with the test client folder. Verify the watermark appears on every image. Open the lightbox. Navigate between images with arrow keys. Open the slideshow and confirm music plays. Open the wall art viewer and try moving the image in a room. Heart several images. Click Send My Selections and go through the stop moment, upgrade calculator, and ordering flow. Verify the order submission reaches your GHL webhook (or webhook.site if you are using a temp URL).
Open the portal on your phone using Safari (iOS) or Chrome (Android). Navigate through every page. Check that the gallery layout looks correct, images are full-width, and buttons are tappable. Add the portal to your home screen (Share → Add to Home Screen on iOS) and verify it opens like an app. If a push notification prompt appears, allow it and send yourself a test notification from the photographer dashboard.
Go back to the photographer dashboard and change the test gallery to clean delivery with watermark off. Reload the gallery as the client and verify the watermark is gone, the agreement gate does not appear, and the Download All button appears. Test the download to confirm it bundles images into a ZIP correctly.
Before going live with a paying client, invite a friend or colleague via Netlify Identity and have them go through the full experience on their own device without any guidance from you. Note anything confusing, anything that breaks, or anything that does not match what you intended. Fix those things, redeploy, and test again. Only go live with paying clients once you are fully satisfied with the experience end to end.
This is not a 20-minute implementation. This is a real, functioning, custom-coded system with cloud storage, login, push notifications, a cinematic reveal player, and a full ordering experience. Getting YOUR version live can take anywhere from 2 days to 2 weeks depending on your starting point. That is completely normal. That is how this system was built.
Keep Claude or ChatGPT open the entire time. Work through one step at a time. Test after every change. Use AI for every question, no matter how small it seems.
Claude (claude.ai) is recommended. The paid Claude Pro plan ($20/month) gives you a large context window so you can paste entire files like gallery.html without hitting a character limit. Free tier has smaller limits and may cut off large files mid-paste.
ChatGPT (chat.openai.com) Plus ($20/month) with GPT-4o also works well. Either will get you through this build.
Set up your workspace like this:
For sections of code (a few lines to a few hundred lines), copy and paste directly into the chat. For large files like gallery.html or ordering.html, you have two options.
Option A: Copy the entire file contents and paste into the chat, then ask your question. Most paid AI plans handle files this size without issue.
Option B (Claude only): Drag and drop the file directly into the Claude chat window. It reads the whole file without you having to copy it.
More context is always better. Pasting the whole file is almost always better than pasting a small piece, because the AI understands how everything connects.
Open your browser. Press F12. Click the Console tab. Any red text is an error. Copy all of it.
After getting the fix: make the change in VS Code, save, drag the updated folder to Netlify, wait 30 to 60 seconds, then hard refresh with Ctrl+Shift+R or Cmd+Shift+R.
All visual styling is in the CSS section of each file, between the <style> and </style> tags near the top. You do not need to understand CSS to change it with AI help. Describe what you want and paste the CSS section.
Ask for clarification. You do not need to pretend you understand the answer. Say exactly what you are confused about.
Every time you make a significant set of changes that works correctly, make a backup copy of those files before making more changes. Name backups something like gallery-working-v1.html so you can always go back. When you are deep in a change and something breaks, having a working backup saves you from having to redo all your previous work.
And when you get truly stuck and cannot figure out what is wrong even with AI help: take a break, come back fresh, and start over from your last working backup. Fresh eyes catch things that exhausted eyes miss every time.
Before you touch a single file, start your AI conversation with this exact setup message. The more context you give upfront, the fewer back-and-forth clarifications you need later. This list covers every piece of information the AI will need to help you swap placeholders, fix errors, and customize this system end to end.
Open Claude or ChatGPT. Start a new conversation. Upload the ZIP file you downloaded from this page (drag it directly into the chat window on Claude, or use the paperclip attachment button on ChatGPT). Then paste the message below, filling in every blank with your actual information. Send it all at once before asking anything else. This becomes the AI's reference document for your entire build.
The credentials block (Cloudflare R2, studio name, phone, logo) is what you need to make the gallery functional. Everything else can be filled in as you go. If you paste this message with the top half completed and the bottom half left as placeholders, the AI can still help you with the technical setup first and circle back to content and customization later.
What matters is that the AI has your real information in front of it from the start, so it never gives you instructions using placeholder values. When you paste your actual credentials, it fills them in for you instead of just telling you where they go.
Save this filled-in version somewhere you can find it again. If your AI conversation gets too long and you need to start a new one, paste it again at the top of the fresh conversation. This is your studio data document. It also doubles as a reference for every credential and URL you will need throughout the build.
Your watermark tiles repeatedly across every image in the gallery when watermark protection is turned on. It appears in the grid, the lightbox, and the slideshow. It cannot be removed by right-clicking, screenshotting shortcuts, or dragging.
Your watermark should be a PNG file with a transparent background. It will tile at roughly 38% size, meaning it repeats in a grid pattern across the full image. Make it semi-transparent (50 to 70% opacity works well). Your studio name, logo mark, or a combination works best. Avoid anything too detailed at small sizes.
Make it in Canva: Create a new design with a transparent background (PNG export). Place your studio name or logo mark in the center. Keep the text or mark large enough to be readable when tiled at reduced size. Export as PNG with transparent background.
Make it in Photoshop or Illustrator: Same principle. Export as PNG-24 with transparency.
Go to Cloudflare → R2 → your galleries bucket. Click Upload. Select your watermark PNG. Upload it to the root of the bucket (not inside any folder). After upload, click the file and copy its public URL. It will look like: https://pub-xxxx.r2.dev/your-watermark.png
Open gallery.html in VS Code. Find the variable WM_URL and replace the placeholder with your watermark URL. Save and redeploy.
Test it by visiting your gallery in preview mode (watermark on). If the watermark is too dark, too large, or not tiling correctly, ask Claude or ChatGPT to adjust the opacity or tiling density. Paste the watermark CSS section from gallery.html and describe exactly what you want changed.
The slideshow plays background music automatically when a client opens the cinematic view. You choose the track. It must be royalty-free so there are no copyright issues.
Go to pixabay.com/music and search for any of these terms:
Longer tracks (3+ minutes) feel more natural before the loop. The slideshow loops the audio so any length technically works. Listen before downloading to confirm the mood fits the experience you want clients to have.
Download the MP3 from Pixabay. Go to Cloudflare → R2 → your galleries bucket. Click Upload. Upload the MP3 to the root of the bucket. After upload, click the file and copy its public URL.
Open gallery.html. Find the audio tag and replace the src value with your music URL:
Save and redeploy. Test by opening the slideshow in your portal.
The music button in the slideshow shows MUSIC ON by default and lets the client toggle it off. The volume fades in gently when the slideshow starts so it is never jarring.
When your client visits the portal on her phone, her browser may prompt her to allow notifications. If she taps Allow, her browser registers for push delivery. If she saves the portal to her home screen (Add to Home Screen on iOS or Android), she gets a full app-like experience and push alerts arrive even when the browser is not open.
A Notifications page inside her portal shows every message you have sent in reverse chronological order. The bell icon in her sidebar shows a badge with unread count. When she visits the page, messages mark as read and the badge clears.
From your photographer dashboard, you can send a notification to any client gallery. Type your message, select the client's folder name, and send. The notification writes to their gallery folder in R2 as a JSON record. Her portal reads this and surfaces it in the Notifications page.
Common uses: gallery ready alert, paperwork reminder, selection deadline reminder, order placed confirmation, reveal appointment reminder.
The cloudflare-worker-push.js and push-client.js and sw-push.js files handle push delivery. To enable full home-screen push notifications, you need to generate VAPID keys (a pair of public/private keys that authorize your push server) and deploy the Cloudflare Worker. This is an advanced step. If you want to set it up, paste the contents of cloudflare-worker-push.js and sw-push.js into Claude or ChatGPT and ask it to walk you through generating your VAPID keys, deploying the Worker to Cloudflare, and adding the keys to the service worker file. For in-portal notification delivery only (no home-screen push), no VAPID setup is needed and the system works out of the box.
The closet page is a filterable, browsable inventory of everything available for clients to wear at your studio. It includes category filters, item images, descriptions, size ranges, and availability notes.
closet.html contains stock placeholder product images and generic descriptions. You replace every image URL and every product description with your own studio's actual wardrobe. This can be done gradually after the rest of the portal is live.
Clean flat-lay photos on a neutral background work best. Product-on-hanger shots also work. Keep them consistent in framing and lighting so the closet page looks cohesive.
Create a folder inside your galleries bucket called closet. Upload all product images into that folder. After uploading, click each image and copy its public URL. It will look like: https://pub-xxxx.r2.dev/closet/item-name.jpg
Each item has an image URL, a name, a category tag (used for filtering), a size range, and notes. Replace each placeholder with your actual item details. To add or remove items from the list, paste the current closet.html into Claude or ChatGPT and ask it to add new items or remove existing ones for you. Describe what you want in plain language.