Created
February 20, 2026 11:58
-
-
Save hui-tony-zk/6c8a9f3871379d3f233e5f5f8b0a42da to your computer and use it in GitHub Desktop.
Reel Idea Visualizer - TikTok-style mockup
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Reel Idea Visualizer</title> | |
| <style> | |
| * { margin: 0; padding: 0; box-sizing: border-box; } | |
| body { | |
| background: #000; | |
| font-family: -apple-system, BlinkMacSystemFont, 'SF Pro', 'Segoe UI', sans-serif; | |
| color: #fff; | |
| overflow: hidden; | |
| height: 100vh; | |
| display: flex; | |
| justify-content: center; | |
| align-items: center; | |
| } | |
| .phone-frame { | |
| width: 375px; | |
| height: 812px; | |
| background: #000; | |
| border-radius: 44px; | |
| border: 3px solid #333; | |
| overflow: hidden; | |
| position: relative; | |
| box-shadow: 0 0 60px rgba(0,0,0,0.8); | |
| } | |
| /* Status bar */ | |
| .status-bar { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| right: 0; | |
| height: 54px; | |
| z-index: 100; | |
| display: flex; | |
| justify-content: space-between; | |
| align-items: flex-end; | |
| padding: 0 28px 8px; | |
| background: linear-gradient(to bottom, rgba(0,0,0,0.6) 0%, transparent 100%); | |
| } | |
| .status-bar .time { font-size: 15px; font-weight: 600; } | |
| .status-bar .icons { font-size: 12px; letter-spacing: 2px; } | |
| /* Main swipe container - vertical */ | |
| .video-carousel { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| transition: transform 0.4s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| } | |
| .video-slide { | |
| width: 100%; | |
| height: 100%; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| } | |
| /* Each video slide has a horizontal pair: video + script */ | |
| .slide-pair { | |
| display: flex; | |
| width: 200%; | |
| height: 100%; | |
| transition: transform 0.35s cubic-bezier(0.25, 0.46, 0.45, 0.94); | |
| } | |
| .slide-panel { | |
| width: 50%; | |
| height: 100%; | |
| flex-shrink: 0; | |
| position: relative; | |
| } | |
| /* Video panel */ | |
| .video-panel { | |
| background: #111; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: flex-end; | |
| } | |
| .video-bg { | |
| position: absolute; | |
| inset: 0; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| } | |
| .video-thumbnail { | |
| position: absolute; | |
| inset: 0; | |
| object-fit: cover; | |
| opacity: 0.7; | |
| } | |
| .video-gradient { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 60%; | |
| background: linear-gradient(to top, rgba(0,0,0,0.9) 0%, rgba(0,0,0,0.4) 50%, transparent 100%); | |
| } | |
| .play-button { | |
| width: 64px; | |
| height: 64px; | |
| background: rgba(255,255,255,0.15); | |
| backdrop-filter: blur(10px); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| z-index: 2; | |
| } | |
| .play-button::after { | |
| content: ''; | |
| width: 0; | |
| height: 0; | |
| border-style: solid; | |
| border-width: 12px 0 12px 22px; | |
| border-color: transparent transparent transparent #fff; | |
| margin-left: 4px; | |
| } | |
| /* Bottom overlay on video */ | |
| .video-overlay { | |
| position: relative; | |
| z-index: 10; | |
| padding: 0 16px 40px; | |
| } | |
| .model-badge { | |
| display: inline-block; | |
| padding: 4px 10px; | |
| border-radius: 12px; | |
| font-size: 11px; | |
| font-weight: 600; | |
| margin-bottom: 10px; | |
| letter-spacing: 0.3px; | |
| } | |
| .badge-sora { background: linear-gradient(135deg, #6366f1, #8b5cf6); } | |
| .badge-seedance { background: linear-gradient(135deg, #059669, #10b981); } | |
| .badge-kling { background: linear-gradient(135deg, #ea580c, #f59e0b); } | |
| .video-caption { | |
| font-size: 14px; | |
| line-height: 1.5; | |
| margin-bottom: 12px; | |
| display: -webkit-box; | |
| -webkit-line-clamp: 3; | |
| -webkit-box-orient: vertical; | |
| overflow: hidden; | |
| } | |
| .video-meta { | |
| display: flex; | |
| gap: 16px; | |
| font-size: 12px; | |
| color: rgba(255,255,255,0.6); | |
| } | |
| /* Right side actions (like TikTok) */ | |
| .side-actions { | |
| position: absolute; | |
| right: 12px; | |
| bottom: 140px; | |
| z-index: 20; | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 20px; | |
| } | |
| .side-action { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 4px; | |
| } | |
| .side-action-icon { | |
| width: 40px; | |
| height: 40px; | |
| background: rgba(255,255,255,0.1); | |
| backdrop-filter: blur(8px); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 18px; | |
| } | |
| .side-action-label { | |
| font-size: 10px; | |
| color: rgba(255,255,255,0.7); | |
| } | |
| /* Script panel */ | |
| .script-panel { | |
| background: #0a0a0a; | |
| padding: 70px 20px 40px; | |
| overflow-y: auto; | |
| } | |
| .script-header { | |
| display: flex; | |
| align-items: center; | |
| gap: 8px; | |
| margin-bottom: 24px; | |
| } | |
| .script-header h2 { | |
| font-size: 18px; | |
| font-weight: 700; | |
| } | |
| .script-back { | |
| width: 32px; | |
| height: 32px; | |
| background: rgba(255,255,255,0.1); | |
| border-radius: 50%; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| font-size: 16px; | |
| cursor: pointer; | |
| } | |
| .script-section { | |
| margin-bottom: 20px; | |
| } | |
| .script-section-label { | |
| font-size: 11px; | |
| font-weight: 600; | |
| color: #8b5cf6; | |
| text-transform: uppercase; | |
| letter-spacing: 1px; | |
| margin-bottom: 10px; | |
| } | |
| .script-line { | |
| padding: 12px 16px; | |
| background: rgba(255,255,255,0.04); | |
| border-radius: 12px; | |
| margin-bottom: 8px; | |
| font-size: 14px; | |
| line-height: 1.6; | |
| border-left: 3px solid transparent; | |
| } | |
| .script-line.dialogue { border-left-color: #8b5cf6; } | |
| .script-line.direction { border-left-color: #f59e0b; color: rgba(255,255,255,0.6); font-style: italic; } | |
| .script-line.cta { border-left-color: #10b981; } | |
| /* Dot indicators */ | |
| .dot-indicators { | |
| position: absolute; | |
| right: 12px; | |
| top: 50%; | |
| transform: translateY(-50%); | |
| z-index: 50; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 8px; | |
| } | |
| .dot { | |
| width: 6px; | |
| height: 6px; | |
| border-radius: 50%; | |
| background: rgba(255,255,255,0.3); | |
| transition: all 0.3s; | |
| } | |
| .dot.active { | |
| height: 20px; | |
| border-radius: 3px; | |
| background: #fff; | |
| } | |
| /* Swipe hint */ | |
| .swipe-hint { | |
| position: absolute; | |
| bottom: 16px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| z-index: 50; | |
| font-size: 11px; | |
| color: rgba(255,255,255,0.4); | |
| display: flex; | |
| align-items: center; | |
| gap: 6px; | |
| animation: pulse 2s infinite; | |
| } | |
| @keyframes pulse { | |
| 0%, 100% { opacity: 0.4; } | |
| 50% { opacity: 0.8; } | |
| } | |
| /* Nav bar */ | |
| .nav-bar { | |
| position: absolute; | |
| bottom: 0; | |
| left: 0; | |
| right: 0; | |
| height: 50px; | |
| background: rgba(0,0,0,0.95); | |
| display: flex; | |
| justify-content: space-around; | |
| align-items: center; | |
| z-index: 100; | |
| border-top: 1px solid rgba(255,255,255,0.08); | |
| } | |
| .nav-item { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: center; | |
| gap: 2px; | |
| font-size: 10px; | |
| color: rgba(255,255,255,0.5); | |
| cursor: pointer; | |
| } | |
| .nav-item.active { color: #fff; } | |
| .nav-icon { font-size: 20px; } | |
| /* Header bar */ | |
| .header-bar { | |
| position: absolute; | |
| top: 50px; | |
| left: 0; | |
| right: 0; | |
| height: 44px; | |
| z-index: 100; | |
| display: flex; | |
| align-items: center; | |
| justify-content: center; | |
| gap: 24px; | |
| } | |
| .header-tab { | |
| font-size: 15px; | |
| font-weight: 500; | |
| color: rgba(255,255,255,0.5); | |
| cursor: pointer; | |
| position: relative; | |
| padding-bottom: 4px; | |
| } | |
| .header-tab.active { | |
| color: #fff; | |
| font-weight: 700; | |
| } | |
| .header-tab.active::after { | |
| content: ''; | |
| position: absolute; | |
| bottom: 0; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| width: 24px; | |
| height: 2px; | |
| background: #fff; | |
| border-radius: 1px; | |
| } | |
| /* Horizontal swipe hint */ | |
| .h-swipe-hint { | |
| position: absolute; | |
| top: 50%; | |
| right: 8px; | |
| z-index: 30; | |
| font-size: 20px; | |
| color: rgba(255,255,255,0.3); | |
| animation: slideLeft 1.5s infinite; | |
| } | |
| @keyframes slideLeft { | |
| 0%, 100% { transform: translateX(0); opacity: 0.3; } | |
| 50% { transform: translateX(-8px); opacity: 0.6; } | |
| } | |
| /* Label tag */ | |
| .label-tag { | |
| display: inline-flex; | |
| align-items: center; | |
| gap: 4px; | |
| padding: 3px 8px; | |
| border-radius: 6px; | |
| font-size: 10px; | |
| font-weight: 600; | |
| background: rgba(255,255,255,0.08); | |
| color: rgba(255,255,255,0.5); | |
| margin-left: 4px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="phone-frame" id="phone"> | |
| <!-- Status bar --> | |
| <div class="status-bar"> | |
| <span class="time">9:41</span> | |
| <span class="icons">βββ βΆ π</span> | |
| </div> | |
| <!-- Header --> | |
| <div class="header-bar"> | |
| <span class="header-tab">Following</span> | |
| <span class="header-tab active">Ideas</span> | |
| <span class="header-tab">Saved</span> | |
| </div> | |
| <!-- Vertical carousel --> | |
| <div class="video-carousel" id="carousel"> | |
| <!-- Slide 1: Sora 2 --> | |
| <div class="video-slide" style="top: 0" data-index="0"> | |
| <div class="slide-pair" id="pair-0"> | |
| <!-- Video side --> | |
| <div class="slide-panel video-panel"> | |
| <div class="video-bg"> | |
| <div style="background: linear-gradient(135deg, #1a1025 0%, #2d1b4e 50%, #1a1025 100%); position: absolute; inset: 0;"></div> | |
| <div style="position: absolute; inset: 0; background: url('data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 400 800%22><rect fill=%22%231a1025%22 width=%22400%22 height=%22800%22/><circle cx=%22200%22 cy=%22350%22 r=%22120%22 fill=%22%232d1b4e%22 opacity=%220.5%22/><rect x=%2280%22 y=%22500%22 width=%22240%22 height=%22160%22 rx=%228%22 fill=%22%23ffffff08%22/></svg>') center/cover;"></div> | |
| <div class="play-button"></div> | |
| </div> | |
| <div class="video-gradient"></div> | |
| <div class="side-actions"> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β₯</div> | |
| <span class="side-action-label">Save</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β</div> | |
| <span class="side-action-label">Share</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β»</div> | |
| <span class="side-action-label">Regen</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β</div> | |
| <span class="side-action-label">Edit</span> | |
| </div> | |
| </div> | |
| <div class="video-overlay"> | |
| <div><span class="model-badge badge-sora">SORA 2</span> <span class="label-tag">12s β’ 9:16</span></div> | |
| <p class="video-caption">"I just pulled the numbers. 36% of homes in our area sold BELOW asking price last monthβ¦"</p> | |
| <div class="video-meta"> | |
| <span>π¬ Take 1</span> | |
| <span>π 1080Γ1920</span> | |
| </div> | |
| </div> | |
| <div class="h-swipe-hint">βΉ</div> | |
| </div> | |
| <!-- Script side --> | |
| <div class="slide-panel script-panel"> | |
| <div class="script-header"> | |
| <div class="script-back" onclick="swipeScript(0, 'back')">β</div> | |
| <h2>Full Script</h2> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π¬ Hook (0β3s)</div> | |
| <div class="script-line dialogue">"I just pulled the numbers. 36% of homes in our area sold BELOW asking price last month."</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π Context (3β5s)</div> | |
| <div class="script-line dialogue">"That means for every 3 homes that sold⦠more than 1 seller took LESS than they wanted."</div> | |
| <div class="script-line direction">Quick cut to screen recording scrolling through price reductions on MLS</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π₯ Reframe (5β8s)</div> | |
| <div class="script-line dialogue">"If you're a first-time buyer sitting on the sidelines thinking you can't afford it? You're wrong."</div> | |
| <div class="script-line dialogue">"This is a buyer's market forming in real time. And most people don't even know it yet."</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π‘ Value (8β11s)</div> | |
| <div class="script-line dialogue">"Here's exactly what I'd do if I was buying my first home in Southeast Alabama RIGHT NOW:"</div> | |
| <div class="script-line direction">3 quick tips: get pre-approved at today's rates, target homes 30+ days on market, negotiate closing costs</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π² CTA (11β12s)</div> | |
| <div class="script-line cta">"DM me 'READY' and I'll send you 5 homes under $250K that are sitting and waiting for your offer."</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Slide 2: Seedance 2 --> | |
| <div class="video-slide" style="top: 100%" data-index="1"> | |
| <div class="slide-pair" id="pair-1"> | |
| <div class="slide-panel video-panel"> | |
| <div class="video-bg"> | |
| <div style="background: linear-gradient(135deg, #0a1a15 0%, #1a3a2a 50%, #0a1a15 100%); position: absolute; inset: 0;"></div> | |
| <div class="play-button"></div> | |
| </div> | |
| <div class="video-gradient"></div> | |
| <div class="side-actions"> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β₯</div> | |
| <span class="side-action-label">Save</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β</div> | |
| <span class="side-action-label">Share</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β»</div> | |
| <span class="side-action-label">Regen</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β</div> | |
| <span class="side-action-label">Edit</span> | |
| </div> | |
| </div> | |
| <div class="video-overlay"> | |
| <div><span class="model-badge badge-seedance">SEEDANCE 2</span> <span class="label-tag">12s β’ 9:16</span></div> | |
| <p class="video-caption">"I just pulled the numbers. 36% of homes in our area sold BELOW asking price last monthβ¦"</p> | |
| <div class="video-meta"> | |
| <span>π¬ Take 1</span> | |
| <span>π 1080Γ1920</span> | |
| </div> | |
| </div> | |
| <div class="h-swipe-hint">βΉ</div> | |
| </div> | |
| <div class="slide-panel script-panel"> | |
| <div class="script-header"> | |
| <div class="script-back" onclick="swipeScript(1, 'back')">β</div> | |
| <h2>Full Script</h2> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π¬ Hook (0β3s)</div> | |
| <div class="script-line dialogue">"I just pulled the numbers. 36% of homes in our area sold BELOW asking price last month."</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π Context (3β5s)</div> | |
| <div class="script-line dialogue">"That means for every 3 homes that sold⦠more than 1 seller took LESS than they wanted."</div> | |
| <div class="script-line direction">Quick cut to screen recording scrolling through price reductions on MLS</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π₯ Reframe (5β8s)</div> | |
| <div class="script-line dialogue">"If you're a first-time buyer sitting on the sidelines thinking you can't afford it? You're wrong."</div> | |
| <div class="script-line dialogue">"This is a buyer's market forming in real time. And most people don't even know it yet."</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π‘ Value (8β11s)</div> | |
| <div class="script-line dialogue">"Here's exactly what I'd do if I was buying my first home in Southeast Alabama RIGHT NOW:"</div> | |
| <div class="script-line direction">3 quick tips: get pre-approved at today's rates, target homes 30+ days on market, negotiate closing costs</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π² CTA (11β12s)</div> | |
| <div class="script-line cta">"DM me 'READY' and I'll send you 5 homes under $250K that are sitting and waiting for your offer."</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Slide 3: Kling --> | |
| <div class="video-slide" style="top: 200%" data-index="2"> | |
| <div class="slide-pair" id="pair-2"> | |
| <div class="slide-panel video-panel"> | |
| <div class="video-bg"> | |
| <div style="background: linear-gradient(135deg, #1a0f05 0%, #3a2010 50%, #1a0f05 100%); position: absolute; inset: 0;"></div> | |
| <div class="play-button"></div> | |
| </div> | |
| <div class="video-gradient"></div> | |
| <div class="side-actions"> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β₯</div> | |
| <span class="side-action-label">Save</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β</div> | |
| <span class="side-action-label">Share</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β»</div> | |
| <span class="side-action-label">Regen</span> | |
| </div> | |
| <div class="side-action"> | |
| <div class="side-action-icon">β</div> | |
| <span class="side-action-label">Edit</span> | |
| </div> | |
| </div> | |
| <div class="video-overlay"> | |
| <div><span class="model-badge badge-kling">KLING 2.0</span> <span class="label-tag">12s β’ 9:16</span></div> | |
| <p class="video-caption">"I just pulled the numbers. 36% of homes in our area sold BELOW asking price last monthβ¦"</p> | |
| <div class="video-meta"> | |
| <span>π¬ Take 1</span> | |
| <span>π 1080Γ1920</span> | |
| </div> | |
| </div> | |
| <div class="h-swipe-hint">βΉ</div> | |
| </div> | |
| <div class="slide-panel script-panel"> | |
| <div class="script-header"> | |
| <div class="script-back" onclick="swipeScript(2, 'back')">β</div> | |
| <h2>Full Script</h2> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π¬ Hook (0β3s)</div> | |
| <div class="script-line dialogue">"I just pulled the numbers. 36% of homes in our area sold BELOW asking price last month."</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π Context (3β5s)</div> | |
| <div class="script-line dialogue">"That means for every 3 homes that sold⦠more than 1 seller took LESS than they wanted."</div> | |
| <div class="script-line direction">Quick cut to screen recording scrolling through price reductions on MLS</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π₯ Reframe (5β8s)</div> | |
| <div class="script-line dialogue">"If you're a first-time buyer sitting on the sidelines thinking you can't afford it? You're wrong."</div> | |
| <div class="script-line dialogue">"This is a buyer's market forming in real time. And most people don't even know it yet."</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π‘ Value (8β11s)</div> | |
| <div class="script-line dialogue">"Here's exactly what I'd do if I was buying my first home in Southeast Alabama RIGHT NOW:"</div> | |
| <div class="script-line direction">3 quick tips: get pre-approved at today's rates, target homes 30+ days on market, negotiate closing costs</div> | |
| </div> | |
| <div class="script-section"> | |
| <div class="script-section-label">π² CTA (11β12s)</div> | |
| <div class="script-line cta">"DM me 'READY' and I'll send you 5 homes under $250K that are sitting and waiting for your offer."</div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Dot indicators --> | |
| <div class="dot-indicators" id="dots"> | |
| <div class="dot active"></div> | |
| <div class="dot"></div> | |
| <div class="dot"></div> | |
| </div> | |
| <!-- Swipe hint --> | |
| <div class="swipe-hint" id="swipeHint">β swipe up for next β’ swipe left for script</div> | |
| <!-- Nav bar --> | |
| <div class="nav-bar"> | |
| <div class="nav-item"><span class="nav-icon">π </span><span>Home</span></div> | |
| <div class="nav-item"><span class="nav-icon">π</span><span>Explore</span></div> | |
| <div class="nav-item active"><span class="nav-icon">β¨</span><span>Ideas</span></div> | |
| <div class="nav-item"><span class="nav-icon">π</span><span>Scripts</span></div> | |
| <div class="nav-item"><span class="nav-icon">π€</span><span>Profile</span></div> | |
| </div> | |
| </div> | |
| <script> | |
| let currentSlide = 0; | |
| const totalSlides = 3; | |
| const carousel = document.getElementById('carousel'); | |
| const phone = document.getElementById('phone'); | |
| const dots = document.querySelectorAll('.dot'); | |
| const scriptStates = [false, false, false]; // track which slides show script | |
| let touchStartX = 0, touchStartY = 0; | |
| let touchEndX = 0, touchEndY = 0; | |
| phone.addEventListener('touchstart', e => { | |
| touchStartX = e.changedTouches[0].screenX; | |
| touchStartY = e.changedTouches[0].screenY; | |
| }, { passive: true }); | |
| phone.addEventListener('touchend', e => { | |
| touchEndX = e.changedTouches[0].screenX; | |
| touchEndY = e.changedTouches[0].screenY; | |
| handleSwipe(); | |
| }, { passive: true }); | |
| // Mouse drag support | |
| let mouseDown = false; | |
| phone.addEventListener('mousedown', e => { | |
| mouseDown = true; | |
| touchStartX = e.screenX; | |
| touchStartY = e.screenY; | |
| }); | |
| phone.addEventListener('mouseup', e => { | |
| if (!mouseDown) return; | |
| mouseDown = false; | |
| touchEndX = e.screenX; | |
| touchEndY = e.screenY; | |
| handleSwipe(); | |
| }); | |
| // Keyboard support | |
| document.addEventListener('keydown', e => { | |
| if (e.key === 'ArrowUp' || e.key === 'k') swipeVertical('up'); | |
| if (e.key === 'ArrowDown' || e.key === 'j') swipeVertical('down'); | |
| if (e.key === 'ArrowLeft' || e.key === 'h') swipeScript(currentSlide, 'open'); | |
| if (e.key === 'ArrowRight' || e.key === 'l') swipeScript(currentSlide, 'back'); | |
| }); | |
| // Wheel support | |
| let wheelCooldown = false; | |
| phone.addEventListener('wheel', e => { | |
| if (wheelCooldown) return; | |
| wheelCooldown = true; | |
| setTimeout(() => wheelCooldown = false, 500); | |
| if (Math.abs(e.deltaY) > Math.abs(e.deltaX)) { | |
| if (e.deltaY > 30) swipeVertical('up'); | |
| else if (e.deltaY < -30) swipeVertical('down'); | |
| } else { | |
| if (e.deltaX > 30) swipeScript(currentSlide, 'open'); | |
| else if (e.deltaX < -30) swipeScript(currentSlide, 'back'); | |
| } | |
| }, { passive: true }); | |
| function handleSwipe() { | |
| const dx = touchEndX - touchStartX; | |
| const dy = touchEndY - touchStartY; | |
| const minSwipe = 50; | |
| if (Math.abs(dy) > Math.abs(dx)) { | |
| // Vertical swipe | |
| if (dy < -minSwipe) swipeVertical('up'); | |
| else if (dy > minSwipe) swipeVertical('down'); | |
| } else { | |
| // Horizontal swipe | |
| if (dx < -minSwipe) swipeScript(currentSlide, 'open'); | |
| else if (dx > minSwipe) swipeScript(currentSlide, 'back'); | |
| } | |
| } | |
| function swipeVertical(dir) { | |
| // Close any open script first | |
| if (scriptStates[currentSlide]) { | |
| swipeScript(currentSlide, 'back'); | |
| return; | |
| } | |
| if (dir === 'up' && currentSlide < totalSlides - 1) currentSlide++; | |
| else if (dir === 'down' && currentSlide > 0) currentSlide--; | |
| else return; | |
| carousel.style.transform = `translateY(-${currentSlide * 100}%)`; | |
| updateDots(); | |
| } | |
| function swipeScript(index, action) { | |
| const pair = document.getElementById(`pair-${index}`); | |
| if (action === 'open') { | |
| pair.style.transform = 'translateX(-50%)'; | |
| scriptStates[index] = true; | |
| } else { | |
| pair.style.transform = 'translateX(0)'; | |
| scriptStates[index] = false; | |
| } | |
| } | |
| function updateDots() { | |
| dots.forEach((dot, i) => { | |
| dot.classList.toggle('active', i === currentSlide); | |
| }); | |
| } | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment