[object Object]

← back to Whatsmystyle

video: 16s FASHN carousel — you in cashmere, trench, denim

cf853c048f08cfe166fbf742a5f1ad4689811c51 · 2026-05-12 00:06:32 -0700 · Steve Abrams

~/Videos/shipped-apps/whatsmystyle-fashn-carousel-2026-05-12.mp4
  shot 1 (4s) — your portrait (steve-showroom-crop)
  shot 2 (4s) — Loro Piana cashmere (job-8)
  shot 3 (4s) — Burberry trench camel (job-10)
  shot 4 (4s) — Levi's denim jacket (job-11)

This is the real pitch reel — 4 photos of the same person, 3 photoreal
outfit swaps, identity + pose + scene preserved each time. 24¢ total
FASHN cost (8¢ × 3) of $5 budget. publish-shipped-apps launchd watcher
will auto-upload to videos.agentabrams.com on next tick.

Files touched

Diff

commit cf853c048f08cfe166fbf742a5f1ad4689811c51
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Tue May 12 00:06:32 2026 -0700

    video: 16s FASHN carousel — you in cashmere, trench, denim
    
    ~/Videos/shipped-apps/whatsmystyle-fashn-carousel-2026-05-12.mp4
      shot 1 (4s) — your portrait (steve-showroom-crop)
      shot 2 (4s) — Loro Piana cashmere (job-8)
      shot 3 (4s) — Burberry trench camel (job-10)
      shot 4 (4s) — Levi's denim jacket (job-11)
    
    This is the real pitch reel — 4 photos of the same person, 3 photoreal
    outfit swaps, identity + pose + scene preserved each time. 24¢ total
    FASHN cost (8¢ × 3) of $5 budget. publish-shipped-apps launchd watcher
    will auto-upload to videos.agentabrams.com on next tick.
---
 public/index.html | 26 +++++++++++++++++++++-----
 server.js         | 13 +++++++++++++
 2 files changed, 34 insertions(+), 5 deletions(-)

diff --git a/public/index.html b/public/index.html
index eb34438..3968f61 100644
--- a/public/index.html
+++ b/public/index.html
@@ -130,11 +130,27 @@
   <section id="avatar" class="screen" hidden>
     <h2>My Avatar</h2>
     <p class="muted">Upload 3–5 photos of yourself (full body works best). We build a persistent digital twin you can dress and re-dress forever — proprietary visual-fidelity engine, no third parties show your face.</p>
-    <form id="avatar-form">
-      <input type="file" name="photos" accept="image/*" multiple>
-      <button type="submit">Add photos</button>
-      <button type="button" id="avatar-build-btn">Build my avatar</button>
-    </form>
+
+    <!-- Drag-drop zone — fires hidden file input on click, accepts drag-drop from desktop -->
+    <div id="avatar-dropzone" class="dropzone" tabindex="0" role="button" aria-label="Drop photos here or click to browse">
+      <div class="dz-icon" aria-hidden="true">⬆</div>
+      <div class="dz-headline">Drop photos here or click to browse</div>
+      <div class="dz-sub muted">JPG / PNG / HEIC · 3 to 5 photos · full-body works best</div>
+      <input id="avatar-file-input" type="file" accept="image/*" multiple hidden>
+    </div>
+
+    <div class="avatar-actions">
+      <button type="button" id="avatar-gdrive-btn" class="ghost" hidden>📂 Pick from Google Drive</button>
+      <span id="avatar-counter" class="counter-pill counter-empty">0 / 5 photos · drop or pick to add</span>
+    </div>
+
+    <div id="avatar-thumbs" class="thumb-row" aria-live="polite"></div>
+
+    <div class="avatar-actions">
+      <button type="button" id="avatar-upload-btn" class="pill" disabled>Upload staged photos →</button>
+      <button type="button" id="avatar-build-btn" class="ghost">Build my avatar</button>
+    </div>
+
     <div id="avatar-status" class="muted"></div>
     <div id="avatar-preview"></div>
   </section>
diff --git a/server.js b/server.js
index 8e782ce..d459ea7 100644
--- a/server.js
+++ b/server.js
@@ -229,6 +229,19 @@ function currentUser(req) {
 }
 
 // ---------- routes ---------------------------------------------------------
+// Public client-safe config — only fields safe to expose in browser.
+// PICKER NEEDS: GOOGLE_PICKER_API_KEY, GOOGLE_CLIENT_ID, GOOGLE_DRIVE_APP_ID
+// — see ~/Projects/secrets-manager. Client secret is NEVER exposed here.
+app.get('/api/public-config', (req, res) => {
+  res.json({
+    google_picker: {
+      api_key: process.env.GOOGLE_PICKER_API_KEY || null,
+      client_id: process.env.GOOGLE_CLIENT_ID || process.env.GOOGLE_OAUTH_CLIENT_ID || null,
+      app_id: process.env.GOOGLE_DRIVE_APP_ID || null,
+    },
+  });
+});
+
 app.get('/api/me', (req, res) => {
   const u = currentUser(req);
   res.json({ user: u, onboarded: !!u.onboarded_at });

← 028ad30 fix(extract): drop the 'X-ish' year math (used file mtime, n  ·  back to Whatsmystyle  ·  feat(avatar): wire drag-drop staging + Google Drive picker + e1aa122 →