1<!doctype html>
2<html lang="id">
3<head>
4<meta charset="utf-8">
5<meta name="viewport" content="width=device-width, initial-scale=1">
6<title>snapme - Ambil Foto</title>
7<!--
8 Halaman acuan UI snapme: Ambil Foto.
9 Sumber kebenaran tampilan bersama DESIGN-SYSTEM.md (token §2 sampai §7, anatomi §10).
10 Berkas ini acuan visual, bukan kode produk: gaya ditulis inline dengan nilai mentah
11 supaya terbaca jelas. Di kode produk pakai token dan komponen (DESIGN-SYSTEM §9, §13).
12 Foto dan pratinjau kamera adalah blok warna berlabel (placeholder).
13-->
14<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@500;700&family=Space+Mono:wght@400;700&family=Unbounded:wght@700;800;900&display=swap">
15<style>
16body{margin:0}
17a{color:#141414}a:hover{color:#141414}
18button{font-family:inherit;cursor:pointer}
19svg{flex-shrink:0}
20.btn{box-shadow:4px 4px 0 #141414;transition:transform .12s ease,box-shadow .12s ease}
21.btn:hover{transform:translate(-1px,-1px);box-shadow:5px 5px 0 #141414}
22.btn:active{transform:translate(4px,4px);box-shadow:0 0 0 #141414}
23a:focus-visible,button:focus-visible{outline:3px solid #5038D6;outline-offset:3px}
24.on-ink:focus-visible{outline-color:#C3F53C}
25@keyframes pop{0%{transform:scale(.6);opacity:0}60%{transform:scale(1.08);opacity:1}100%{transform:scale(1)}}
26@keyframes hold{from{width:0}to{width:100%}}
27.pop{animation:pop .5s cubic-bezier(.16,1,.3,1)}
28.hold{animation:hold .9s linear forwards}
29@media (prefers-reduced-motion: reduce){.btn{transition:none}.pop{animation:none}.hold{animation:none;width:100%}}
30@media (max-width:760px){.stage{order:-1}.ph-note,.key-hint{display:none !important}.hud{top:14px !important;padding:0 12px !important}.cam{aspect-ratio:1 / 1 !important}}
31</style>
32</head>
33<body>
34<div id="app"></div>
35
36<template id="tpl">
37<div style="background-color: #C3F53C; background-image: linear-gradient(rgba(20,20,20,0.08) 1.5px, transparent 1.5px), linear-gradient(90deg, rgba(20,20,20,0.08) 1.5px, transparent 1.5px); background-size: 56px 56px; padding: 24px clamp(16px, 3vw, 40px) 40px; box-sizing: border-box; font-family: 'Space Grotesk', sans-serif; color: #141414; min-height: 100vh">
38<div style="max-width: 1280px; margin: 0 auto; display: flex; flex-direction: column; gap: 20px">
39
40<header style="display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between; gap: 12px">
41<a href="welcome.html" style="font-family: 'Unbounded', sans-serif; font-weight: 900; font-size: 22px; letter-spacing: -0.02em; text-decoration: none; color: #141414; padding: 8px 0">snapme</a>
42<ol style="display: flex; flex-wrap: wrap; gap: 8px; list-style: none; margin: 0; padding: 0; font-family: 'Space Mono', monospace; font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em">
43<li style="display: inline-flex; align-items: center; gap: 6px; padding: 7px 12px; background: #FFFDF7; border: 2px solid #141414; border-radius: 999px">
44<svg viewBox="0 0 256 256" width="14" height="14" fill="currentColor" aria-hidden="true"><path d="M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z"></path></svg>
45Layout
46</li>
47<li aria-current="step" style="display: inline-flex; align-items: center; padding: 7px 12px; background: #141414; color: #FFFDF7; border: 2px solid #141414; border-radius: 999px">Foto</li>
48<li style="display: inline-flex; align-items: center; padding: 7px 12px; border: 2px solid #141414; border-radius: 999px">Hias</li>
49<li style="display: inline-flex; align-items: center; padding: 7px 12px; border: 2px solid #141414; border-radius: 999px">Hasil</li>
50</ol>
51</header>
52
53<main style="background: #FFFDF7; border: 3px solid #141414; border-radius: 20px; box-shadow: 6px 6px 0 #141414; overflow: hidden">
54<div style="display: flex; flex-wrap: wrap; align-items: center; gap: 14px; padding: 14px 20px; border-bottom: 3px solid #141414">
55<a href="welcome.html" aria-label="Kembali" style="display: flex; align-items: center; justify-content: center; width: 44px; height: 44px; border: 2px solid #141414; border-radius: 999px; color: #141414">
56<svg viewBox="0 0 256 256" width="20" height="20" fill="currentColor" aria-hidden="true"><path d="M228,128a12,12,0,0,1-12,12H69l51.52,51.51a12,12,0,0,1-17,17l-72-72a12,12,0,0,1,0-17l72-72a12,12,0,0,1,17,17L69,116H216A12,12,0,0,1,228,128Z"></path></svg>
57</a>
58<h1 style="margin: 0; font-family: 'Unbounded', sans-serif; font-weight: 900; font-size: clamp(22px, 3vw, 34px); line-height: 1; text-transform: uppercase; color: #F2479B; -webkit-text-stroke: 1px #141414; text-shadow: 3px 3px 0 #141414">Ambil Foto</h1>
59<span style="display: inline-flex; align-items: center; gap: 8px; font-family: 'Space Mono', monospace; font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; background: #F4F1E6; border: 2px solid #141414; border-radius: 999px; padding: 6px 12px">
60<svg viewBox="0 0 256 256" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M216,36H40A20,20,0,0,0,20,56V200a20,20,0,0,0,20,20H216a20,20,0,0,0,20-20V56A20,20,0,0,0,216,36ZM44,100h72v56H44Zm96-24V60h24V76Zm-24,0H92V60h24Zm0,104v16H92V180Zm24,0h24v16H140Zm0-24V100h72v56Zm72-80H188V60h24ZM68,60V76H44V60ZM44,180H68v16H44Zm144,16V180h24v16Z"></path></svg>
61Strip 4 foto
62</span>
63<div style="flex: 1 1 0"></div>
64<button onClick="{{toggleGesture}}" aria-pressed="{{gestureOn}}" style="display: inline-flex; align-items: center; gap: 10px; min-height: 44px; padding: 0 8px 0 16px; background: #FFFDF7; color: #141414; border: 2px solid #141414; border-radius: 999px; font-weight: 700; font-size: 15px">
65Gesture Snap
66<span style="font-family: 'Space Mono', monospace; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; background: {{gestureChipBg}}; border: 2px solid #141414; border-radius: 999px; padding: 3px 10px">{{gestureLabel}}</span>
67</button>
68<span aria-live="polite" style="font-family: 'Space Mono', monospace; font-weight: 700; font-size: 18px; background: #F2479B; border: 2px solid #141414; border-radius: 999px; padding: 6px 16px">{{progressText}}</span>
69</div>
70
71<div style="display: flex; flex-wrap: wrap; gap: 20px; padding: 20px">
72
73<aside style="flex: 1 1 200px; display: flex; flex-direction: column; gap: 12px">
74<h2 style="margin: 0; font-family: 'Space Mono', monospace; font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em">Fotomu</h2>
75<div style="display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 10px">
76<sc-for list="{{slots}}" as="slot" hint-placeholder-count="4">
77<div>
78<sc-if value="{{slot.filled}}" hint-placeholder-val="{{true}}">
79<div style="position: relative; aspect-ratio: 44 / 31; box-sizing: border-box; background: {{slot.bg}}; border: 2px solid #141414; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-family: 'Space Mono', monospace; font-weight: 700; font-size: 12px">
80Foto {{slot.n}}
81<span style="position: absolute; top: -8px; right: -8px; display: flex; align-items: center; justify-content: center; width: 22px; height: 22px; background: #5038D6; color: #FFFDF7; border: 2px solid #141414; border-radius: 999px">
82<svg viewBox="0 0 256 256" width="12" height="12" fill="currentColor" aria-hidden="true"><path d="M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z"></path></svg>
83</span>
84</div>
85</sc-if>
86<sc-if value="{{slot.empty}}" hint-placeholder-val="{{false}}">
87<div style="aspect-ratio: 44 / 31; box-sizing: border-box; border: 2px dashed #5E5E57; border-radius: 10px; display: flex; align-items: center; justify-content: center; font-family: 'Space Mono', monospace; font-weight: 700; font-size: 18px; color: #5E5E57">{{slot.n}}</div>
88</sc-if>
89</div>
90</sc-for>
91</div>
92<button onClick="{{retake}}" style="display: inline-flex; align-items: center; justify-content: center; gap: 8px; min-height: 44px; padding: 0 14px; background: #FFFDF7; color: #141414; border: 2px solid #141414; border-radius: 999px; font-weight: 700; font-size: 14px">
93<svg viewBox="0 0 256 256" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M228,128a100,100,0,0,1-98.66,100H128a99.39,99.39,0,0,1-68.62-27.29,12,12,0,0,1,16.48-17.45,76,76,0,1,0-1.57-109c-.13.13-.25.25-.39.37L54.89,92H72a12,12,0,0,1,0,24H24a12,12,0,0,1-12-12V56a12,12,0,0,1,24,0V76.72L57.48,57.06A100,100,0,0,1,228,128Z"></path></svg>
94Ulang foto terakhir
95</button>
96</aside>
97
98<section class="stage" style="flex: 3 1 440px; min-width: 0; display: flex; flex-direction: column; gap: 12px">
99<div class="cam" style="position: relative; aspect-ratio: 44 / 31; background: #141414; border: 3px solid #141414; border-radius: 16px; overflow: hidden; display: flex; align-items: center; justify-content: center">
100<div style="display: flex; flex-direction: column; align-items: center; gap: 8px; color: #FFFDF7; text-align: center; padding: 0 24px">
101<svg viewBox="0 0 256 256" width="40" height="40" fill="currentColor" aria-hidden="true"><path d="M208,52H182.42L170,33.34A12,12,0,0,0,160,28H96a12,12,0,0,0-10,5.34L73.57,52H48A28,28,0,0,0,20,80V192a28,28,0,0,0,28,28H208a28,28,0,0,0,28-28V80A28,28,0,0,0,208,52Zm4,140a4,4,0,0,1-4,4H48a4,4,0,0,1-4-4V80a4,4,0,0,1,4-4H80a12,12,0,0,0,10-5.34L102.42,52h51.15L166,70.66A12,12,0,0,0,176,76h32a4,4,0,0,1,4,4ZM128,84a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,84Zm0,72a24,24,0,1,1,24-24A24,24,0,0,1,128,156Z"></path></svg>
102<span style="font-family: 'Space Mono', monospace; font-weight: 700; font-size: 13px; text-transform: uppercase; letter-spacing: 0.06em">Pratinjau kamera</span>
103<span class="ph-note" style="font-size: 14px">Di aplikasi asli, area ini menampilkan feed kamera yang dicerminkan.</span>
104</div>
105<div style="position: absolute; top: 16px; left: 16px; width: 36px; height: 36px; border-top: 4px solid #C3F53C; border-left: 4px solid #C3F53C; border-radius: 6px 0 0 0"></div>
106<div style="position: absolute; top: 16px; right: 16px; width: 36px; height: 36px; border-top: 4px solid #C3F53C; border-right: 4px solid #C3F53C; border-radius: 0 6px 0 0"></div>
107<div style="position: absolute; bottom: 16px; left: 16px; width: 36px; height: 36px; border-bottom: 4px solid #C3F53C; border-left: 4px solid #C3F53C; border-radius: 0 0 0 6px"></div>
108<div style="position: absolute; bottom: 16px; right: 16px; width: 36px; height: 36px; border-bottom: 4px solid #C3F53C; border-right: 4px solid #C3F53C; border-radius: 0 0 6px 0"></div>
109
110<div class="hud" style="position: absolute; top: 28px; left: 0; right: 0; display: flex; justify-content: center; padding: 0 64px">
111<sc-if value="{{hudGesture}}" hint-placeholder-val="{{true}}">
112<button class="on-ink" onClick="{{demoGesture}}" style="display: inline-flex; align-items: center; gap: 10px; min-height: 44px; padding: 0 16px; background: #FFFDF7; color: #141414; border: 2px solid #141414; border-radius: 999px; font-weight: 700; font-size: 15px">
113<svg viewBox="0 0 256 256" width="22" height="22" fill="currentColor" aria-hidden="true"><path d="M215.7,28a32,32,0,0,0-58.62,7.72L148,69.64l-9.09-33.91A32,32,0,0,0,77.1,52.3l7.72,28.82-2.49-.48a32,32,0,0,0-33,49c.63.95,1.31,1.86,2,2.73a31.69,31.69,0,0,0-6.67,14,32.08,32.08,0,0,0-.65,6.59V160a84,84,0,0,0,84,84h.64c46-.34,83.36-38.43,83.36-84.9v-3.34a59.83,59.83,0,0,0-11.41-35.1L218.9,52.3A31.84,31.84,0,0,0,215.7,28ZM180.26,41.94a8,8,0,0,1,15.46,4.14L180.47,103a59.64,59.64,0,0,0-15-5.67ZM101.08,40a8,8,0,0,1,14.65,1.93l13.61,50.78a32.24,32.24,0,0,0-7.92,2.83,31.86,31.86,0,0,0-10-8l-11.1-41.43A8,8,0,0,1,101.08,40ZM69.33,157.14a8,8,0,0,1,8.18-12.26l10.09,2a8,8,0,0,1,6.24,9.43,8,8,0,0,1-9.49,6.23l-10-2A8,8,0,0,1,69.33,157.14Zm-1.16-46.73a8,8,0,0,1,9.44-6.24l.13,0,20.73,4a8,8,0,0,1-3.26,15.67l-.13,0-20.72-4a8,8,0,0,1-6.19-9.43ZM188,159.1c0,33.34-26.7,60.66-59.53,60.9a60.09,60.09,0,0,1-55.1-35.17l6.24,1.25a32,32,0,0,0,38.1-35.82,32,32,0,0,0,12,5.11l2.56.51A40.6,40.6,0,0,0,132,160a39.79,39.79,0,0,0,11.43,28,12,12,0,0,0,17.14-16.8,16,16,0,0,1-2.66-18.8,12,12,0,0,0-8.19-17.49l-15.32-3.06a8,8,0,1,1,3.25-15.67l21.4,4.28A36.09,36.09,0,0,1,188,155.76Z"></path></svg>
114Angkat dua jari, tahan sebentar
115</button>
116</sc-if>
117<sc-if value="{{hudHolding}}" hint-placeholder-val="{{false}}">
118<div role="status" style="display: flex; flex-direction: column; gap: 6px; min-width: 240px; padding: 10px 16px; background: #C3F53C; color: #141414; border: 2px solid #141414; border-radius: 16px; font-weight: 700; font-size: 15px">
119Terdeteksi. Tahan...
120<div style="height: 8px; background: #FFFDF7; border: 2px solid #141414; border-radius: 999px; overflow: hidden">
121<div class="hold" style="height: 100%; background: #5038D6"></div>
122</div>
123</div>
124</sc-if>
125<sc-if value="{{hudOff}}" hint-placeholder-val="{{false}}">
126<div style="display: inline-flex; align-items: center; min-height: 44px; padding: 0 16px; background: #FFFDF7; color: #141414; border: 2px solid #141414; border-radius: 999px; font-weight: 700; font-size: 15px">Gesture mati. Pakai tombol kamera.</div>
127</sc-if>
128<sc-if value="{{hudDone}}" hint-placeholder-val="{{false}}">
129<div role="status" style="display: inline-flex; align-items: center; gap: 8px; min-height: 44px; padding: 0 16px; background: #C3F53C; color: #141414; border: 2px solid #141414; border-radius: 999px; font-weight: 700; font-size: 15px">
130<svg viewBox="0 0 256 256" width="18" height="18" fill="currentColor" aria-hidden="true"><path d="M232.49,80.49l-128,128a12,12,0,0,1-17,0l-56-56a12,12,0,1,1,17-17L96,183,215.51,63.51a12,12,0,0,1,17,17Z"></path></svg>
1314 foto siap dihias
132</div>
133</sc-if>
134</div>
135
136<sc-if value="{{is3}}" hint-placeholder-val="{{false}}">
137<div class="pop" role="status" style="position: absolute; display: flex; align-items: center; justify-content: center; width: 168px; height: 168px; background: #C3F53C; border: 4px solid #141414; border-radius: 999px; box-shadow: 6px 6px 0 #5038D6; font-family: 'Unbounded', sans-serif; font-weight: 900; font-size: 96px; color: #141414">3</div>
138</sc-if>
139<sc-if value="{{is2}}" hint-placeholder-val="{{false}}">
140<div class="pop" role="status" style="position: absolute; display: flex; align-items: center; justify-content: center; width: 168px; height: 168px; background: #C3F53C; border: 4px solid #141414; border-radius: 999px; box-shadow: 6px 6px 0 #5038D6; font-family: 'Unbounded', sans-serif; font-weight: 900; font-size: 96px; color: #141414">2</div>
141</sc-if>
142<sc-if value="{{is1}}" hint-placeholder-val="{{false}}">
143<div class="pop" role="status" style="position: absolute; display: flex; align-items: center; justify-content: center; width: 168px; height: 168px; background: #F2479B; border: 4px solid #141414; border-radius: 999px; box-shadow: 6px 6px 0 #5038D6; font-family: 'Unbounded', sans-serif; font-weight: 900; font-size: 96px; color: #141414">1</div>
144</sc-if>
145<sc-if value="{{flash}}" hint-placeholder-val="{{false}}">
146<div style="position: absolute; top: 0; right: 0; bottom: 0; left: 0; background: #FFFDF7; opacity: 0.9"></div>
147</sc-if>
148
149<div style="position: absolute; bottom: 20px; left: 0; right: 0; display: flex; align-items: center; justify-content: center; gap: 14px">
150<button class="btn on-ink" onClick="{{shoot}}" aria-label="Ambil foto" style="display: flex; align-items: center; justify-content: center; width: 76px; height: 76px; background: #F0562B; color: #141414; border: 3px solid #FFFDF7; border-radius: 999px">
151<svg viewBox="0 0 256 256" width="34" height="34" fill="currentColor" aria-hidden="true"><path d="M208,52H182.42L170,33.34A12,12,0,0,0,160,28H96a12,12,0,0,0-10,5.34L73.57,52H48A28,28,0,0,0,20,80V192a28,28,0,0,0,28,28H208a28,28,0,0,0,28-28V80A28,28,0,0,0,208,52Zm4,140a4,4,0,0,1-4,4H48a4,4,0,0,1-4-4V80a4,4,0,0,1,4-4H80a12,12,0,0,0,10-5.34L102.42,52h51.15L166,70.66A12,12,0,0,0,176,76h32a4,4,0,0,1,4,4ZM128,84a48,48,0,1,0,48,48A48.05,48.05,0,0,0,128,84Zm0,72a24,24,0,1,1,24-24A24,24,0,0,1,128,156Z"></path></svg>
152</button>
153<span class="key-hint" style="display: inline-flex; align-items: center; gap: 6px; font-family: 'Space Mono', monospace; font-weight: 700; font-size: 12px; text-transform: uppercase; letter-spacing: 0.06em; background: #FFFDF7; color: #141414; border: 2px solid #141414; border-radius: 8px; padding: 5px 10px">
154<svg viewBox="0 0 256 256" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M224,44H32A20,20,0,0,0,12,64V192a20,20,0,0,0,20,20H224a20,20,0,0,0,20-20V64A20,20,0,0,0,224,44Zm-4,144H36V68H220ZM52,128a12,12,0,0,1,12-12H192a12,12,0,0,1,0,24H64A12,12,0,0,1,52,128Zm0-36A12,12,0,0,1,64,80H192a12,12,0,0,1,0,24H64A12,12,0,0,1,52,92Zm0,72a12,12,0,0,1,12-12h8a12,12,0,0,1,0,24H64A12,12,0,0,1,52,164Zm108,0a12,12,0,0,1-12,12H108a12,12,0,0,1,0-24h40A12,12,0,0,1,160,164Zm44,0a12,12,0,0,1-12,12h-8a12,12,0,0,1,0-24h8A12,12,0,0,1,204,164Z"></path></svg>
155Spasi
156</span>
157</div>
158</div>
159<p style="margin: 0; display: flex; align-items: center; gap: 8px; font-size: 14px; color: #5E5E57">
160<svg viewBox="0 0 256 256" width="16" height="16" fill="currentColor" aria-hidden="true"><path d="M208,76H180V56A52,52,0,0,0,76,56V76H48A20,20,0,0,0,28,96V208a20,20,0,0,0,20,20H208a20,20,0,0,0,20-20V96A20,20,0,0,0,208,76ZM100,56a28,28,0,0,1,56,0V76H100ZM204,204H52V100H204Z"></path></svg>
161Kamera dan deteksi gesture berjalan di perangkat ini. Tidak ada yang diunggah.
162</p>
163</section>
164
165<aside style="flex: 1 1 240px; display: flex; flex-direction: column; gap: 16px">
166<div style="background: #C3F53C; border: 2px solid #141414; border-radius: 14px; padding: 16px; display: flex; flex-direction: column; gap: 12px">
167<h2 style="margin: 0; font-family: 'Unbounded', sans-serif; font-weight: 800; font-size: 16px; line-height: 1.2">Senyum, kamera siap!</h2>
168<ul style="list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 10px; font-size: 15px; line-height: 1.35">
169<li style="display: flex; align-items: center; gap: 10px">
170<svg viewBox="0 0 256 256" width="24" height="24" fill="currentColor" aria-hidden="true" style="flex-shrink: 0"><path d="M215.7,28a32,32,0,0,0-58.62,7.72L148,69.64l-9.09-33.91A32,32,0,0,0,77.1,52.3l7.72,28.82-2.49-.48a32,32,0,0,0-33,49c.63.95,1.31,1.86,2,2.73a31.69,31.69,0,0,0-6.67,14,32.08,32.08,0,0,0-.65,6.59V160a84,84,0,0,0,84,84h.64c46-.34,83.36-38.43,83.36-84.9v-3.34a59.83,59.83,0,0,0-11.41-35.1L218.9,52.3A31.84,31.84,0,0,0,215.7,28ZM180.26,41.94a8,8,0,0,1,15.46,4.14L180.47,103a59.64,59.64,0,0,0-15-5.67ZM101.08,40a8,8,0,0,1,14.65,1.93l13.61,50.78a32.24,32.24,0,0,0-7.92,2.83,31.86,31.86,0,0,0-10-8l-11.1-41.43A8,8,0,0,1,101.08,40ZM69.33,157.14a8,8,0,0,1,8.18-12.26l10.09,2a8,8,0,0,1,6.24,9.43,8,8,0,0,1-9.49,6.23l-10-2A8,8,0,0,1,69.33,157.14Zm-1.16-46.73a8,8,0,0,1,9.44-6.24l.13,0,20.73,4a8,8,0,0,1-3.26,15.67l-.13,0-20.72-4a8,8,0,0,1-6.19-9.43ZM188,159.1c0,33.34-26.7,60.66-59.53,60.9a60.09,60.09,0,0,1-55.1-35.17l6.24,1.25a32,32,0,0,0,38.1-35.82,32,32,0,0,0,12,5.11l2.56.51A40.6,40.6,0,0,0,132,160a39.79,39.79,0,0,0,11.43,28,12,12,0,0,0,17.14-16.8,16,16,0,0,1-2.66-18.8,12,12,0,0,0-8.19-17.49l-15.32-3.06a8,8,0,1,1,3.25-15.67l21.4,4.28A36.09,36.09,0,0,1,188,155.76Z"></path></svg>
171Dua jari: mulai hitung mundur
172</li>
173<li style="display: flex; align-items: center; gap: 10px">
174<svg viewBox="0 0 256 256" width="24" height="24" fill="currentColor" aria-hidden="true" style="flex-shrink: 0"><path d="M188,84a32,32,0,0,0-8,1V60a32,32,0,0,0-43.21-30A32,32,0,0,0,76,44v1A32,32,0,0,0,36,76v76a92,92,0,0,0,184,0V116A32,32,0,0,0,188,84Zm8,68a68,68,0,0,1-136,0V76a8,8,0,0,1,16,0v40a12,12,0,0,0,24,0V44a8,8,0,0,1,16,0v64a12,12,0,0,0,24,0V60a8,8,0,0,1,16,0v65.4A52.09,52.09,0,0,0,116,176a12,12,0,0,0,24,0,28,28,0,0,1,28-28,12,12,0,0,0,12-12V116a8,8,0,0,1,16,0Z"></path></svg>
175Telapak: ulang foto terakhir
176</li>
177<li style="display: flex; align-items: center; gap: 10px">
178<svg viewBox="0 0 256 256" width="24" height="24" fill="currentColor" aria-hidden="true" style="flex-shrink: 0"><path d="M237,77.47A28,28,0,0,0,216,68H164V56a44.05,44.05,0,0,0-44-44,12,12,0,0,0-10.73,6.63L72.58,92H32a20,20,0,0,0-20,20v88a20,20,0,0,0,20,20H204a28,28,0,0,0,27.78-24.53l12-96A28,28,0,0,0,237,77.47ZM36,116H68v80H36ZM220,96.5l-12,96a4,4,0,0,1-4,3.5H92V106.83L126.82,37.2A20,20,0,0,1,140,56V80a12,12,0,0,0,12,12h64a4,4,0,0,1,4,4.5Z"></path></svg>
179Jempol: lanjut ke hias
180</li>
181</ul>
182</div>
183
184<div style="background: #A9A4F4; border: 2px solid #141414; border-radius: 14px; padding: 16px; display: flex; flex-direction: column; gap: 10px">
185<h2 style="margin: 0; display: flex; align-items: center; gap: 8px; font-family: 'Unbounded', sans-serif; font-weight: 800; font-size: 16px">
186<svg viewBox="0 0 256 256" width="22" height="22" fill="currentColor" aria-hidden="true"><path d="M125.18,156.94a64,64,0,1,0-82.36,0,100.23,100.23,0,0,0-39.49,32,12,12,0,0,0,19.35,14.2,76,76,0,0,1,122.64,0,12,12,0,0,0,19.36-14.2A100.33,100.33,0,0,0,125.18,156.94ZM44,108a40,40,0,1,1,40,40A40,40,0,0,1,44,108Zm206.1,97.67a12,12,0,0,1-16.78-2.57A76.31,76.31,0,0,0,172,172a12,12,0,0,1,0-24,40,40,0,1,0-10.3-78.67,12,12,0,1,1-6.16-23.19,64,64,0,0,1,57.64,110.8,100.23,100.23,0,0,1,39.49,32A12,12,0,0,1,250.1,205.67Z"></path></svg>
187Strip Estafet
188</h2>
189<p style="margin: 0; font-size: 15px; line-height: 1.4">Cukup ambil 2 foto. Sisanya diisi temanmu lewat link.</p>
190<button class="btn" style="display: inline-flex; align-items: center; justify-content: center; min-height: 44px; padding: 0 16px; background: #5038D6; color: #FFFDF7; border: 2px solid #141414; border-radius: 999px; font-weight: 700; font-size: 15px">Estafet ke Teman</button>
191</div>
192
193<div style="display: flex; flex-direction: column; gap: 8px; margin-top: auto">
194<sc-if value="{{done}}" hint-placeholder-val="{{false}}">
195<a class="btn" href="customize.html" style="display: flex; align-items: center; justify-content: center; gap: 10px; min-height: 56px; padding: 0 24px; background: #F0562B; color: #141414; border: 2px solid #141414; border-radius: 999px; font-weight: 700; font-size: 18px; text-decoration: none">
196Lanjut Hias
197<svg viewBox="0 0 256 256" width="22" height="22" fill="currentColor" aria-hidden="true"><path d="M224.49,136.49l-72,72a12,12,0,0,1-17-17L187,140H40a12,12,0,0,1,0-24H187L135.51,64.48a12,12,0,0,1,17-17l72,72A12,12,0,0,1,224.49,136.49Z"></path></svg>
198</a>
199</sc-if>
200<sc-if value="{{notDone}}" hint-placeholder-val="{{true}}">
201<button disabled="{{yes}}" style="display: flex; align-items: center; justify-content: center; min-height: 56px; padding: 0 24px; background: #F4F1E6; color: #5E5E57; border: 2px dashed #5E5E57; border-radius: 999px; font-weight: 700; font-size: 18px; cursor: not-allowed">Lanjut Hias</button>
202</sc-if>
203<p aria-live="polite" style="margin: 0; text-align: center; font-size: 14px; color: #5E5E57">{{remainingText}}</p>
204</div>
205</aside>
206
207</div>
208</main>
209
210</div>
211</div>
212</template>
213
214<script>
215/* Halaman acuan: state dan data contoh. Bukan kode produk. */
216class DCLogic {
217 setState(patch) { this.state = Object.assign({}, this.state, patch); render(); }
218}
219class Component extends DCLogic {
220get s() {
221return Object.assign({ shots: 2, count: null, holding: false, flash: false, gesture: true }, this.state || {});
222}
223componentWillUnmount() {
224clearInterval(this._t);
225clearTimeout(this._h);
226clearTimeout(this._f);
227}
228shoot() {
229const s = this.s;
230if (s.count !== null || s.shots >= 4) return;
231clearTimeout(this._h);
232this.setState({ holding: false, count: 3 });
233this._t = setInterval(() => {
234const c = this.s.count - 1;
235if (c > 0) { this.setState({ count: c }); return; }
236clearInterval(this._t);
237this.setState({ count: null, flash: true, shots: Math.min(4, this.s.shots + 1) });
238this._f = setTimeout(() => this.setState({ flash: false }), 160);
239}, 800);
240}
241demoGesture() {
242const s = this.s;
243if (s.count !== null || s.holding || s.shots >= 4 || !s.gesture) return;
244this.setState({ holding: true });
245this._h = setTimeout(() => this.shoot(), 900);
246}
247retake() {
248const s = this.s;
249if (s.count !== null || s.holding || s.shots <= 0) return;
250this.setState({ shots: s.shots - 1 });
251}
252renderVals() {
253const s = this.s;
254const colors = ['#F2479B', '#C3F53C', '#F0562B', '#A9A4F4'];
255const counting = s.count !== null;
256const done = s.shots >= 4;
257const idle = !counting && !s.holding;
258const left = 4 - s.shots;
259return {
260yes: true,
261slots: [0, 1, 2, 3].map((i) => ({ n: i + 1, filled: i < s.shots, empty: i >= s.shots, bg: colors[i] })),
262progressText: s.shots + '/4',
263remainingText: done ? 'Semua foto sudah ada.' : 'Butuh ' + left + ' foto lagi.',
264done: done,
265notDone: !done,
266is3: s.count === 3,
267is2: s.count === 2,
268is1: s.count === 1,
269flash: s.flash,
270hudGesture: s.gesture && idle && !done,
271hudHolding: s.holding,
272hudOff: !s.gesture && idle && !done,
273hudDone: done && idle,
274gestureOn: s.gesture,
275gestureLabel: s.gesture ? 'Nyala' : 'Mati',
276gestureChipBg: s.gesture ? '#C3F53C' : '#F4F1E6',
277toggleGesture: () => this.setState({ gesture: !this.s.gesture }),
278shoot: () => this.shoot(),
279demoGesture: () => this.demoGesture(),
280retake: () => this.retake()
281};
282}
283}
284
285/* Mesin templat kecil untuk halaman acuan ini saja (bukan kode produk).
286 {{nama}} diisi dari renderVals(); <sc-if> menampilkan isinya bila nilainya benar;
287 <sc-for> mengulang isinya untuk tiap butir daftar. */
288function lookup(path, scope) {
289 path = path.trim();
290 if (path === 'true') return true;
291 if (path === 'false') return false;
292 return path.split('.').reduce((v, k) => (v == null ? undefined : v[k]), scope);
293}
294const WHOLE = /^\s*\{\{([^}]+)\}\}\s*$/;
295function fill(text, scope) {
296 return text.replace(/\{\{([^}]+)\}\}/g, (_, p) => { const v = lookup(p, scope); return v == null ? '' : String(v); });
297}
298function build(node, scope, out) {
299 if (node.nodeType === 3) { out.appendChild(document.createTextNode(fill(node.nodeValue, scope))); return; }
300 if (node.nodeType !== 1) return;
301 const tag = node.tagName.toLowerCase();
302 if (tag === 'sc-if') {
303 if (lookup(node.getAttribute('value').match(WHOLE)[1], scope)) node.childNodes.forEach((c) => build(c, scope, out));
304 return;
305 }
306 if (tag === 'sc-for') {
307 const list = lookup(node.getAttribute('list').match(WHOLE)[1], scope) || [];
308 const as = node.getAttribute('as');
309 list.forEach((item, i) => {
310 const inner = Object.assign({}, scope, { [as]: item, $index: i });
311 node.childNodes.forEach((c) => build(c, inner, out));
312 });
313 return;
314 }
315 const el = node.cloneNode(false);
316 for (const attr of Array.from(node.attributes)) {
317 const whole = attr.value.match(WHOLE);
318 if (/^on[a-z]+$/i.test(attr.name) && whole) {
319 el.removeAttribute(attr.name);
320 el.addEventListener(attr.name.slice(2).toLowerCase(), lookup(whole[1], scope));
321 } else if (whole) {
322 const v = lookup(whole[1], scope);
323 if (attr.name === 'disabled') { if (v) el.setAttribute('disabled', ''); else el.removeAttribute('disabled'); }
324 else el.setAttribute(attr.name, v == null ? '' : String(v));
325 } else if (attr.value.includes('{{')) {
326 el.setAttribute(attr.name, fill(attr.value, scope));
327 }
328 }
329 node.childNodes.forEach((c) => build(c, scope, el));
330 out.appendChild(el);
331}
332function pathOf(el, root) {
333 if (!el || el === root || !root.contains(el)) return null;
334 const path = [];
335 while (el !== root) { path.unshift(Array.prototype.indexOf.call(el.parentNode.children, el)); el = el.parentNode; }
336 return path;
337}
338const app = document.getElementById('app');
339const tpl = document.getElementById('tpl');
340const component = new Component();
341function render() {
342 const focus = pathOf(document.activeElement, app);
343 const frag = document.createDocumentFragment();
344 const vals = component.renderVals();
345 tpl.content.childNodes.forEach((c) => build(c, vals, frag));
346 app.replaceChildren(frag);
347 if (focus) { let el = app; for (const i of focus) el = el && el.children[i]; if (el && el.focus) el.focus(); }
348}
349render();
350</script>
351</body>
352</html>
353