Capstone project. I'm ngl it's vibe-coded and it's only here so I can mess around with it
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #2 from chriskalos/refactor/testable-apps

Refactor all testable apps to be more unique and meaningful

authored by

Chris and committed by
GitHub
8781293d d0c14195

+1538 -469
+4 -6
index.html
··· 65 65 <select id="app-select"> 66 66 <option value="">Select an app...</option> 67 67 <option value="testable-apps/shop-app/index.html" data-task="Find and purchase a blue t-shirt" 68 - data-win="selector:.checkout-success.active">ShopEasy Store</option> 68 + data-win="selector:.checkout-success.active">THREAD Store</option> 69 69 <option value="testable-apps/example-app/index.html" 70 - data-task="Fill out the contact form with your details" 71 - data-win="text:Form submitted successfully"> 72 - Example Form App 73 - </option> 70 + data-task="Fill out the feedback form with your details" 71 + data-win="text:Form submitted successfully">Feedback Terminal</option> 74 72 <option value="testable-apps/long-page-app/index.html" 75 73 data-task="Review the comparison sections and subscribe at the bottom of the page" 76 - data-win="selector:#success-banner">Long Page Demo</option> 74 + data-win="selector:#success-banner">Signal Intelligence Report</option> 77 75 </select> 78 76 </label> 79 77
+396 -149
testable-apps/example-app/index.html
··· 4 4 <head> 5 5 <meta charset="UTF-8"> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 - <title>Example App - UXET Test</title> 7 + <title>Feedback Terminal — Submit a Report</title> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=DM+Mono:wght@400;500&family=Instrument+Sans:wght@400;500;600;700&display=swap" rel="stylesheet"> 8 11 <style> 9 - * { 12 + *, 13 + *::before, 14 + *::after { 10 15 box-sizing: border-box; 11 16 margin: 0; 12 17 padding: 0; 13 18 } 14 19 20 + :root { 21 + --surface-0: #0e0e0e; 22 + --surface-1: #161616; 23 + --surface-2: #1e1e1e; 24 + --surface-3: #282828; 25 + --surface-4: #333333; 26 + --border: #2a2a2a; 27 + --border-focus: #d4a843; 28 + --text-primary: #e8e4dc; 29 + --text-secondary: #8a857d; 30 + --text-muted: #5c5851; 31 + --accent: #d4a843; 32 + --accent-dim: rgba(212, 168, 67, 0.12); 33 + --accent-glow: rgba(212, 168, 67, 0.25); 34 + --success: #5fb06d; 35 + --success-bg: rgba(95, 176, 109, 0.1); 36 + --danger: #c9564a; 37 + --radius-sm: 6px; 38 + --radius-md: 10px; 39 + --radius-lg: 16px; 40 + --font-body: 'Instrument Sans', system-ui, sans-serif; 41 + --font-mono: 'DM Mono', 'Menlo', monospace; 42 + } 43 + 15 44 body { 16 - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; 17 - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 45 + font-family: var(--font-body); 46 + background: var(--surface-0); 47 + color: var(--text-primary); 18 48 min-height: 100vh; 19 - padding: 40px 20px; 20 - color: #333; 49 + line-height: 1.55; 50 + -webkit-font-smoothing: antialiased; 21 51 } 22 52 23 - .container { 24 - max-width: 600px; 53 + /* Noise overlay */ 54 + body::before { 55 + content: ''; 56 + position: fixed; 57 + inset: 0; 58 + pointer-events: none; 59 + opacity: 0.025; 60 + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E"); 61 + background-size: 200px; 62 + z-index: 999; 63 + } 64 + 65 + .page-container { 66 + max-width: 620px; 25 67 margin: 0 auto; 68 + padding: 48px 24px 80px; 26 69 } 27 70 71 + /* Header */ 72 + .page-header { 73 + margin-bottom: 40px; 74 + } 75 + 76 + .page-header .tag { 77 + display: inline-flex; 78 + align-items: center; 79 + gap: 6px; 80 + font-family: var(--font-mono); 81 + font-size: 0.72rem; 82 + letter-spacing: 0.08em; 83 + text-transform: uppercase; 84 + color: var(--accent); 85 + background: var(--accent-dim); 86 + border: 1px solid rgba(212, 168, 67, 0.2); 87 + padding: 5px 12px; 88 + border-radius: 999px; 89 + margin-bottom: 16px; 90 + } 91 + 92 + .page-header .tag::before { 93 + content: ''; 94 + width: 6px; 95 + height: 6px; 96 + border-radius: 50%; 97 + background: var(--accent); 98 + animation: pulse 2s ease-in-out infinite; 99 + } 100 + 101 + @keyframes pulse { 102 + 0%, 100% { opacity: 1; } 103 + 50% { opacity: 0.3; } 104 + } 105 + 106 + .page-header h1 { 107 + font-size: 1.8rem; 108 + font-weight: 700; 109 + letter-spacing: -0.03em; 110 + color: var(--text-primary); 111 + margin-bottom: 6px; 112 + } 113 + 114 + .page-header p { 115 + color: var(--text-secondary); 116 + font-size: 0.92rem; 117 + } 118 + 119 + /* Card shell */ 28 120 .card { 29 - background: white; 30 - border-radius: 16px; 31 - box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2); 32 - padding: 40px; 33 - margin-bottom: 24px; 121 + background: var(--surface-1); 122 + border: 1px solid var(--border); 123 + border-radius: var(--radius-lg); 124 + padding: 32px; 125 + margin-bottom: 20px; 126 + position: relative; 34 127 } 35 128 36 - h1 { 37 - font-size: 2rem; 38 - margin-bottom: 8px; 39 - color: #1a1a2e; 129 + .card::after { 130 + content: ''; 131 + position: absolute; 132 + inset: 0; 133 + border-radius: var(--radius-lg); 134 + pointer-events: none; 135 + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.03); 40 136 } 41 137 42 - .subtitle { 43 - color: #666; 44 - margin-bottom: 32px; 138 + .card-title { 139 + font-family: var(--font-mono); 140 + font-size: 0.72rem; 141 + text-transform: uppercase; 142 + letter-spacing: 0.1em; 143 + color: var(--text-muted); 144 + margin-bottom: 24px; 145 + padding-bottom: 12px; 146 + border-bottom: 1px solid var(--border); 45 147 } 46 148 149 + /* Form controls */ 47 150 .form-group { 48 - margin-bottom: 24px; 151 + margin-bottom: 22px; 49 152 } 50 153 51 154 label { 52 155 display: block; 156 + font-size: 0.82rem; 53 157 font-weight: 600; 158 + color: var(--text-secondary); 54 159 margin-bottom: 8px; 55 - color: #444; 160 + letter-spacing: 0.01em; 56 161 } 57 162 58 163 input[type="text"], ··· 60 165 textarea, 61 166 select { 62 167 width: 100%; 63 - padding: 12px 16px; 64 - border: 2px solid #e0e0e0; 65 - border-radius: 8px; 66 - font-size: 1rem; 67 - transition: border-color 0.2s, box-shadow 0.2s; 168 + padding: 12px 14px; 169 + background: var(--surface-2); 170 + border: 1px solid var(--border); 171 + border-radius: var(--radius-md); 172 + color: var(--text-primary); 173 + font-family: var(--font-body); 174 + font-size: 0.92rem; 175 + transition: border-color 0.2s, box-shadow 0.2s, background 0.2s; 176 + outline: none; 68 177 } 69 178 70 - input:focus, 179 + input[type="text"]:focus, 180 + input[type="email"]:focus, 71 181 textarea:focus, 72 182 select:focus { 73 - outline: none; 74 - border-color: #667eea; 75 - box-shadow: 0 0 0 4px rgba(102, 126, 234, 0.1); 183 + border-color: var(--accent); 184 + box-shadow: 0 0 0 3px var(--accent-dim); 185 + background: var(--surface-0); 186 + } 187 + 188 + input::placeholder, 189 + textarea::placeholder { 190 + color: var(--text-muted); 76 191 } 77 192 78 193 textarea { 79 - min-height: 120px; 194 + min-height: 110px; 80 195 resize: vertical; 81 196 } 82 197 83 - .button-group { 198 + select { 199 + cursor: pointer; 200 + appearance: none; 201 + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath d='M1 1.5L6 6.5L11 1.5' stroke='%238a857d' stroke-width='1.5' fill='none' stroke-linecap='round' stroke-linejoin='round'/%3E%3C/svg%3E"); 202 + background-repeat: no-repeat; 203 + background-position: right 14px center; 204 + padding-right: 40px; 205 + } 206 + 207 + /* Checkbox group */ 208 + .checkbox-group { 84 209 display: flex; 85 - gap: 12px; 86 - flex-wrap: wrap; 210 + flex-direction: column; 211 + gap: 10px; 87 212 } 88 213 89 - button { 90 - padding: 14px 28px; 91 - border: none; 92 - border-radius: 8px; 93 - font-size: 1rem; 94 - font-weight: 600; 214 + .checkbox-item { 215 + display: flex; 216 + align-items: center; 217 + gap: 10px; 95 218 cursor: pointer; 96 - transition: transform 0.2s, box-shadow 0.2s; 219 + font-size: 0.9rem; 220 + color: var(--text-primary); 97 221 } 98 222 99 - button:hover { 100 - transform: translateY(-2px); 223 + .checkbox-item input[type="checkbox"] { 224 + appearance: none; 225 + width: 18px; 226 + height: 18px; 227 + border: 1.5px solid var(--surface-4); 228 + border-radius: 4px; 229 + background: var(--surface-2); 230 + cursor: pointer; 231 + position: relative; 232 + transition: border-color 0.15s, background 0.15s; 233 + flex-shrink: 0; 101 234 } 102 235 103 - button:active { 104 - transform: translateY(0); 236 + .checkbox-item input[type="checkbox"]:checked { 237 + background: var(--accent); 238 + border-color: var(--accent); 105 239 } 106 240 107 - .btn-primary { 108 - background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); 109 - color: white; 110 - box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4); 241 + .checkbox-item input[type="checkbox"]:checked::after { 242 + content: ''; 243 + position: absolute; 244 + left: 5px; 245 + top: 2px; 246 + width: 5px; 247 + height: 9px; 248 + border: solid var(--surface-0); 249 + border-width: 0 2px 2px 0; 250 + transform: rotate(45deg); 111 251 } 112 252 113 - .btn-primary:hover { 114 - box-shadow: 0 6px 20px rgba(102, 126, 234, 0.5); 253 + .checkbox-item input[type="checkbox"]:focus-visible { 254 + box-shadow: 0 0 0 3px var(--accent-dim); 115 255 } 116 256 117 - .btn-secondary { 118 - background: #f0f0f0; 119 - color: #333; 257 + /* Buttons */ 258 + .button-row { 259 + display: flex; 260 + gap: 10px; 261 + flex-wrap: wrap; 262 + margin-top: 8px; 120 263 } 121 264 122 - .btn-secondary:hover { 123 - background: #e0e0e0; 265 + .btn { 266 + display: inline-flex; 267 + align-items: center; 268 + justify-content: center; 269 + gap: 6px; 270 + padding: 12px 22px; 271 + border: none; 272 + border-radius: var(--radius-md); 273 + font-family: var(--font-body); 274 + font-size: 0.88rem; 275 + font-weight: 600; 276 + cursor: pointer; 277 + transition: transform 0.12s, box-shadow 0.2s, background 0.2s; 278 + outline: none; 279 + } 280 + 281 + .btn:active { 282 + transform: scale(0.97); 124 283 } 125 284 126 - .checkbox-group { 127 - display: flex; 128 - flex-direction: column; 129 - gap: 12px; 285 + .btn:focus-visible { 286 + box-shadow: 0 0 0 3px var(--accent-dim); 130 287 } 131 288 132 - .checkbox-item { 133 - display: flex; 134 - align-items: center; 135 - gap: 10px; 136 - cursor: pointer; 289 + .btn-primary { 290 + background: var(--accent); 291 + color: var(--surface-0); 137 292 } 138 293 139 - .checkbox-item input { 140 - width: 20px; 141 - height: 20px; 142 - cursor: pointer; 294 + .btn-primary:hover { 295 + background: #dcb34e; 296 + box-shadow: 0 4px 20px var(--accent-glow); 143 297 } 144 298 145 - .interactive-section { 146 - background: #f8f9fa; 147 - border-radius: 12px; 148 - padding: 24px; 149 - margin-top: 24px; 299 + .btn-ghost { 300 + background: var(--surface-2); 301 + color: var(--text-secondary); 302 + border: 1px solid var(--border); 150 303 } 151 304 152 - .interactive-section h3 { 153 - margin-bottom: 16px; 154 - color: #444; 305 + .btn-ghost:hover { 306 + background: var(--surface-3); 307 + color: var(--text-primary); 155 308 } 156 309 157 - .counter { 310 + /* Counter */ 311 + .counter-row { 158 312 display: flex; 159 313 align-items: center; 160 - gap: 16px; 314 + gap: 20px; 161 315 } 162 316 163 317 .counter-value { 164 - font-size: 2rem; 165 - font-weight: 700; 166 - color: #667eea; 167 - min-width: 60px; 318 + font-family: var(--font-mono); 319 + font-size: 2.2rem; 320 + font-weight: 500; 321 + color: var(--accent); 322 + min-width: 64px; 168 323 text-align: center; 324 + user-select: none; 325 + transition: transform 0.12s; 326 + } 327 + 328 + .counter-value.bump { 329 + transform: scale(1.15); 169 330 } 170 331 171 - .counter button { 172 - width: 48px; 173 - height: 48px; 332 + .counter-btn { 333 + width: 44px; 334 + height: 44px; 174 335 border-radius: 50%; 175 - font-size: 1.5rem; 176 - padding: 0; 177 336 display: flex; 178 337 align-items: center; 179 338 justify-content: center; 339 + background: var(--surface-2); 340 + border: 1px solid var(--border); 341 + color: var(--text-primary); 342 + font-size: 1.3rem; 343 + cursor: pointer; 344 + transition: background 0.15s, border-color 0.15s, transform 0.1s; 345 + outline: none; 180 346 } 181 347 182 - .scrollable-list { 183 - max-height: 200px; 348 + .counter-btn:hover { 349 + background: var(--surface-3); 350 + border-color: var(--surface-4); 351 + } 352 + 353 + .counter-btn:active { 354 + transform: scale(0.92); 355 + } 356 + 357 + /* Scrollable list */ 358 + .list-container { 359 + border: 1px solid var(--border); 360 + border-radius: var(--radius-md); 361 + max-height: 220px; 184 362 overflow-y: auto; 185 - border: 1px solid #e0e0e0; 186 - border-radius: 8px; 187 - margin-top: 16px; 363 + margin-top: 12px; 364 + } 365 + 366 + .list-container::-webkit-scrollbar { 367 + width: 6px; 368 + } 369 + 370 + .list-container::-webkit-scrollbar-track { 371 + background: transparent; 372 + } 373 + 374 + .list-container::-webkit-scrollbar-thumb { 375 + background: var(--surface-4); 376 + border-radius: 3px; 188 377 } 189 378 190 379 .list-item { 191 380 padding: 12px 16px; 192 - border-bottom: 1px solid #e0e0e0; 381 + border-bottom: 1px solid var(--border); 193 382 cursor: pointer; 194 - transition: background 0.2s; 383 + font-size: 0.88rem; 384 + color: var(--text-secondary); 385 + transition: background 0.12s, color 0.12s; 386 + display: flex; 387 + align-items: center; 388 + justify-content: space-between; 195 389 } 196 390 197 391 .list-item:last-child { ··· 199 393 } 200 394 201 395 .list-item:hover { 202 - background: #f0f4ff; 396 + background: var(--surface-2); 397 + color: var(--text-primary); 203 398 } 204 399 205 400 .list-item.selected { 206 - background: #667eea; 207 - color: white; 401 + background: var(--accent-dim); 402 + color: var(--accent); 403 + border-left: 3px solid var(--accent); 404 + } 405 + 406 + .list-item .item-index { 407 + font-family: var(--font-mono); 408 + font-size: 0.72rem; 409 + color: var(--text-muted); 410 + } 411 + 412 + .list-item.selected .item-index { 413 + color: var(--accent); 414 + } 415 + 416 + /* Success banner */ 417 + #success-banner { 418 + display: none; 419 + position: fixed; 420 + top: 0; 421 + left: 0; 422 + right: 0; 423 + padding: 14px 24px; 424 + background: var(--success); 425 + color: var(--surface-0); 426 + text-align: center; 427 + font-weight: 600; 428 + font-size: 0.9rem; 429 + z-index: 1000; 430 + animation: slideDown 0.35s cubic-bezier(0.22, 1, 0.36, 1); 431 + } 432 + 433 + @keyframes slideDown { 434 + from { 435 + opacity: 0; 436 + transform: translateY(-100%); 437 + } 438 + to { 439 + opacity: 1; 440 + transform: translateY(0); 441 + } 442 + } 443 + 444 + /* Divider */ 445 + .section-gap { 446 + height: 1px; 447 + background: var(--border); 448 + margin: 28px 0; 208 449 } 209 450 </style> 210 451 </head> 211 452 212 453 <body> 213 - <div class="container"> 214 - <div class="card"> 215 - <h1>Example Test App</h1> 216 - <p class="subtitle">Interact with this form to test UXET tracking</p> 454 + <div id="success-banner"></div> 455 + <div class="page-container"> 456 + <div class="page-header"> 457 + <div class="tag">Live Terminal</div> 458 + <h1>Feedback Terminal</h1> 459 + <p>File a report, adjust preferences, or interact with the controls below.</p> 460 + </div> 217 461 462 + <div class="card"> 463 + <div class="card-title">// report details</div> 218 464 <form id="test-form"> 219 465 <div class="form-group"> 220 - <label for="name">Your Name</label> 221 - <input type="text" id="name" placeholder="Enter your name"> 466 + <label for="name">Operator Name</label> 467 + <input type="text" id="name" placeholder="e.g. Jordan K."> 222 468 </div> 223 469 224 470 <div class="form-group"> 225 - <label for="email">Email Address</label> 226 - <input type="email" id="email" placeholder="you@example.com"> 471 + <label for="email">Contact Email</label> 472 + <input type="email" id="email" placeholder="operator@facility.io"> 227 473 </div> 228 474 229 475 <div class="form-group"> 230 - <label for="category">Category</label> 476 + <label for="category">Report Category</label> 231 477 <select id="category"> 232 - <option value="">Select a category...</option> 233 - <option value="feedback">Feedback</option> 234 - <option value="bug">Bug Report</option> 235 - <option value="feature">Feature Request</option> 478 + <option value="">Choose category…</option> 479 + <option value="feedback">General Feedback</option> 480 + <option value="bug">System Fault</option> 481 + <option value="feature">Enhancement Request</option> 236 482 <option value="other">Other</option> 237 483 </select> 238 484 </div> 239 485 240 486 <div class="form-group"> 241 - <label for="message">Message</label> 242 - <textarea id="message" placeholder="Type your message here..."></textarea> 487 + <label for="message">Description</label> 488 + <textarea id="message" placeholder="Describe the issue or suggestion in detail…"></textarea> 243 489 </div> 244 490 245 491 <div class="form-group"> 246 - <label>Preferences</label> 492 + <label>Notification Preferences</label> 247 493 <div class="checkbox-group"> 248 494 <label class="checkbox-item"> 249 495 <input type="checkbox" id="pref1"> 250 - <span>Send me email updates</span> 496 + <span>Email me status updates</span> 251 497 </label> 252 498 <label class="checkbox-item"> 253 499 <input type="checkbox" id="pref2"> 254 - <span>Enable notifications</span> 500 + <span>Enable push notifications</span> 255 501 </label> 256 502 <label class="checkbox-item"> 257 503 <input type="checkbox" id="pref3"> ··· 260 506 </div> 261 507 </div> 262 508 263 - <div class="button-group"> 264 - <button type="submit" class="btn-primary">Submit Form</button> 265 - <button type="reset" class="btn-secondary">Reset</button> 509 + <div class="button-row"> 510 + <button type="submit" class="btn btn-primary">Submit Report</button> 511 + <button type="reset" class="btn btn-ghost">Clear Form</button> 266 512 </div> 267 513 </form> 514 + </div> 268 515 269 - <div class="interactive-section"> 270 - <h3>Interactive Counter</h3> 271 - <div class="counter"> 272 - <button class="btn-secondary" id="decrease">−</button> 273 - <span class="counter-value" id="counter-value">0</span> 274 - <button class="btn-primary" id="increase">+</button> 275 - </div> 516 + <div class="card"> 517 + <div class="card-title">// counter module</div> 518 + <div class="counter-row"> 519 + <button class="counter-btn" id="decrease" aria-label="Decrease">−</button> 520 + <span class="counter-value" id="counter-value">0</span> 521 + <button class="counter-btn" id="increase" aria-label="Increase">+</button> 276 522 </div> 523 + </div> 277 524 278 - <div class="interactive-section"> 279 - <h3>Scrollable List</h3> 280 - <div class="scrollable-list" id="scrollable-list"> 281 - <div class="list-item">Item 1 - Click to select</div> 282 - <div class="list-item">Item 2 - Click to select</div> 283 - <div class="list-item">Item 3 - Click to select</div> 284 - <div class="list-item">Item 4 - Click to select</div> 285 - <div class="list-item">Item 5 - Click to select</div> 286 - <div class="list-item">Item 6 - Click to select</div> 287 - <div class="list-item">Item 7 - Click to select</div> 288 - <div class="list-item">Item 8 - Click to select</div> 289 - <div class="list-item">Item 9 - Click to select</div> 290 - <div class="list-item">Item 10 - Click to select</div> 291 - </div> 525 + <div class="card"> 526 + <div class="card-title">// incident log</div> 527 + <p style="font-size: 0.85rem; color: var(--text-secondary); margin-bottom: 4px;">Select an entry to review details.</p> 528 + <div class="list-container" id="scrollable-list"> 529 + <div class="list-item"><span>Sensor calibration drift detected</span><span class="item-index">INC-001</span></div> 530 + <div class="list-item"><span>Thermal threshold exceeded — Zone B</span><span class="item-index">INC-002</span></div> 531 + <div class="list-item"><span>Access badge reader offline</span><span class="item-index">INC-003</span></div> 532 + <div class="list-item"><span>Backup generator scheduled test</span><span class="item-index">INC-004</span></div> 533 + <div class="list-item"><span>Network latency spike — 14:22 UTC</span><span class="item-index">INC-005</span></div> 534 + <div class="list-item"><span>Firmware update pending — Module 7</span><span class="item-index">INC-006</span></div> 535 + <div class="list-item"><span>Pressure valve alert — cleared</span><span class="item-index">INC-007</span></div> 536 + <div class="list-item"><span>Humidity sensor replaced — Lab 3</span><span class="item-index">INC-008</span></div> 537 + <div class="list-item"><span>Fire suppression system check OK</span><span class="item-index">INC-009</span></div> 538 + <div class="list-item"><span>Quarterly compliance audit complete</span><span class="item-index">INC-010</span></div> 292 539 </div> 293 540 </div> 294 541 </div> 295 542 296 543 <script> 297 - // Counter functionality 544 + // Counter 298 545 let count = 0; 299 546 const counterValue = document.getElementById('counter-value'); 300 547 301 548 document.getElementById('increase').addEventListener('click', () => { 302 549 count++; 303 550 counterValue.textContent = count; 551 + counterValue.classList.add('bump'); 552 + setTimeout(() => counterValue.classList.remove('bump'), 120); 304 553 }); 305 554 306 555 document.getElementById('decrease').addEventListener('click', () => { 307 556 count--; 308 557 counterValue.textContent = count; 558 + counterValue.classList.add('bump'); 559 + setTimeout(() => counterValue.classList.remove('bump'), 120); 309 560 }); 310 561 311 562 // List selection ··· 320 571 // Form submission 321 572 document.getElementById('test-form').addEventListener('submit', (e) => { 322 573 e.preventDefault(); 323 - // Show success banner in the DOM 324 574 let banner = document.getElementById('success-banner'); 325 - if (!banner) { 326 - banner = document.createElement('div'); 327 - banner.id = 'success-banner'; 328 - banner.style.cssText = 'position:fixed;top:0;left:0;right:0;padding:16px;background:#22c55e;color:white;text-align:center;font-weight:600;font-size:1.1rem;z-index:1000;animation:slideDown 0.3s ease;'; 329 - document.body.appendChild(banner); 330 - } 331 575 banner.textContent = 'Form submitted successfully!'; 332 576 banner.style.display = 'block'; 577 + setTimeout(() => { 578 + banner.style.display = 'none'; 579 + }, 4000); 333 580 }); 334 581 </script> 335 582 </body>
+559 -74
testable-apps/long-page-app/index.html
··· 4 4 <head> 5 5 <meta charset="UTF-8"> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 - <title>Long Page Demo</title> 7 + <title>Long Page — Signal Intelligence Report</title> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Playfair+Display:wght@400;500;600;700&family=Source+Sans+3:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet"> 8 11 <style> 9 - * { box-sizing: border-box; } 10 - body { 12 + *, 13 + *::before, 14 + *::after { 15 + box-sizing: border-box; 11 16 margin: 0; 12 - font-family: Georgia, serif; 13 - color: #1f1a17; 14 - background: #faf6ef; 17 + padding: 0; 15 18 } 19 + 20 + :root { 21 + --bg: #f7f4ef; 22 + --bg-warm: #f0ebe2; 23 + --surface: #ffffff; 24 + --border: #e2ddd4; 25 + --border-strong: #c8c0b4; 26 + --text: #1b1813; 27 + --text-secondary: #6b625a; 28 + --text-muted: #9e958b; 29 + --accent: #b4432e; 30 + --accent-light: rgba(180, 67, 46, 0.08); 31 + --accent-glow: rgba(180, 67, 46, 0.15); 32 + --ink: #2d2520; 33 + --success: #2a6648; 34 + --success-bg: rgba(42, 102, 72, 0.06); 35 + --font-display: 'Playfair Display', Georgia, serif; 36 + --font-body: 'Source Sans 3', 'Source Sans Pro', system-ui, sans-serif; 37 + --font-mono: 'JetBrains Mono', 'Menlo', monospace; 38 + } 39 + 40 + body { 41 + font-family: var(--font-body); 42 + background: var(--bg); 43 + color: var(--text); 44 + line-height: 1.65; 45 + -webkit-font-smoothing: antialiased; 46 + } 47 + 48 + /* Subtle grain */ 49 + body::before { 50 + content: ''; 51 + position: fixed; 52 + inset: 0; 53 + pointer-events: none; 54 + opacity: 0.03; 55 + background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 512 512' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.7' numOctaves='5' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E"); 56 + background-size: 256px; 57 + z-index: 999; 58 + } 59 + 60 + /* Sticky nav */ 16 61 header { 17 62 position: sticky; 18 63 top: 0; 19 - z-index: 10; 20 - background: rgba(255, 249, 240, 0.92); 21 - backdrop-filter: blur(8px); 22 - border-bottom: 1px solid rgba(31, 26, 23, 0.12); 23 - padding: 18px 24px; 64 + z-index: 50; 65 + background: rgba(247, 244, 239, 0.88); 66 + backdrop-filter: blur(12px) saturate(1.2); 67 + -webkit-backdrop-filter: blur(12px) saturate(1.2); 68 + border-bottom: 1px solid var(--border); 69 + padding: 0 32px; 70 + } 71 + 72 + .header-inner { 73 + max-width: 920px; 74 + margin: 0 auto; 75 + display: flex; 76 + align-items: center; 77 + justify-content: space-between; 78 + height: 60px; 79 + } 80 + 81 + .header-brand { 82 + font-family: var(--font-display); 83 + font-size: 1.15rem; 84 + font-weight: 700; 85 + color: var(--ink); 86 + letter-spacing: -0.02em; 24 87 } 25 - header h1 { margin: 0 0 6px; } 88 + 89 + .header-nav { 90 + display: flex; 91 + gap: 28px; 92 + } 93 + 94 + .header-nav a { 95 + font-size: 0.82rem; 96 + font-weight: 600; 97 + color: var(--text-secondary); 98 + text-decoration: none; 99 + letter-spacing: 0.02em; 100 + text-transform: uppercase; 101 + transition: color 0.2s; 102 + } 103 + 104 + .header-nav a:hover, 105 + .header-nav a.active { 106 + color: var(--accent); 107 + } 108 + 109 + /* Progress bar */ 110 + .scroll-progress { 111 + position: fixed; 112 + top: 0; 113 + left: 0; 114 + height: 3px; 115 + width: 0%; 116 + background: var(--accent); 117 + z-index: 100; 118 + transition: width 0.05s linear; 119 + } 120 + 121 + /* Main content */ 26 122 main { 27 - max-width: 980px; 123 + max-width: 920px; 28 124 margin: 0 auto; 29 - padding: 24px; 125 + padding: 48px 32px 96px; 126 + } 127 + 128 + /* Sections */ 129 + .content-section { 130 + min-height: 85vh; 131 + margin-bottom: 48px; 132 + position: relative; 133 + } 134 + 135 + .section-label { 136 + font-family: var(--font-mono); 137 + font-size: 0.68rem; 138 + text-transform: uppercase; 139 + letter-spacing: 0.12em; 140 + color: var(--accent); 141 + background: var(--accent-light); 142 + display: inline-flex; 143 + padding: 4px 12px; 144 + border-radius: 999px; 145 + margin-bottom: 16px; 146 + } 147 + 148 + .section-heading { 149 + font-family: var(--font-display); 150 + font-size: 2.1rem; 151 + font-weight: 600; 152 + letter-spacing: -0.02em; 153 + color: var(--ink); 154 + margin-bottom: 10px; 155 + line-height: 1.25; 30 156 } 31 - section { 32 - min-height: 92vh; 33 - border: 1px solid rgba(31, 26, 23, 0.12); 34 - border-radius: 24px; 35 - background: white; 36 - margin-bottom: 24px; 37 - padding: 28px; 38 - box-shadow: 0 18px 40px rgba(47, 36, 22, 0.08); 157 + 158 + .section-subtext { 159 + color: var(--text-secondary); 160 + font-size: 1rem; 161 + max-width: 600px; 162 + margin-bottom: 32px; 39 163 } 40 - .cards { 164 + 165 + /* Cards grid */ 166 + .card-grid { 41 167 display: grid; 42 - grid-template-columns: repeat(3, minmax(0, 1fr)); 43 - gap: 16px; 44 - margin-top: 20px; 168 + grid-template-columns: repeat(3, 1fr); 169 + gap: 18px; 170 + margin-top: 24px; 45 171 } 172 + 46 173 .card { 47 - border-radius: 18px; 48 - padding: 18px; 49 - background: linear-gradient(180deg, #fef8ef, #f4e8d8); 50 - min-height: 160px; 174 + background: var(--surface); 175 + border: 1px solid var(--border); 176 + border-radius: 14px; 177 + padding: 26px 22px; 178 + min-height: 200px; 179 + display: flex; 180 + flex-direction: column; 181 + transition: transform 0.2s, box-shadow 0.25s, border-color 0.2s; 182 + cursor: default; 183 + } 184 + 185 + .card:hover { 186 + transform: translateY(-3px); 187 + box-shadow: 0 14px 36px rgba(43, 30, 18, 0.08); 188 + border-color: var(--border-strong); 189 + } 190 + 191 + .card-icon { 192 + font-size: 1.6rem; 193 + margin-bottom: 14px; 194 + } 195 + 196 + .card h3 { 197 + font-family: var(--font-display); 198 + font-size: 1.05rem; 199 + font-weight: 600; 200 + margin-bottom: 8px; 201 + color: var(--ink); 202 + } 203 + 204 + .card p { 205 + color: var(--text-secondary); 206 + font-size: 0.88rem; 207 + flex: 1; 208 + } 209 + 210 + .card-tag { 211 + font-family: var(--font-mono); 212 + font-size: 0.68rem; 213 + color: var(--text-muted); 214 + margin-top: 14px; 215 + padding-top: 10px; 216 + border-top: 1px solid var(--border); 217 + } 218 + 219 + /* Pull quote */ 220 + .pull-quote { 221 + background: var(--bg-warm); 222 + border-left: 3px solid var(--accent); 223 + padding: 28px 32px; 224 + border-radius: 0 14px 14px 0; 225 + margin: 36px 0; 51 226 } 52 - button { 53 - padding: 14px 18px; 54 - border: none; 227 + 228 + .pull-quote p { 229 + font-family: var(--font-display); 230 + font-size: 1.2rem; 231 + font-style: italic; 232 + color: var(--ink); 233 + line-height: 1.55; 234 + } 235 + 236 + .pull-quote cite { 237 + display: block; 238 + font-family: var(--font-body); 239 + font-style: normal; 240 + font-size: 0.82rem; 241 + color: var(--text-muted); 242 + margin-top: 10px; 243 + } 244 + 245 + /* Data table */ 246 + .data-table { 247 + width: 100%; 248 + border-collapse: collapse; 249 + margin-top: 24px; 250 + font-size: 0.88rem; 251 + } 252 + 253 + .data-table th { 254 + font-family: var(--font-mono); 255 + font-size: 0.7rem; 256 + text-transform: uppercase; 257 + letter-spacing: 0.1em; 258 + color: var(--text-muted); 259 + text-align: left; 260 + padding: 10px 16px; 261 + border-bottom: 2px solid var(--border); 262 + } 263 + 264 + .data-table td { 265 + padding: 14px 16px; 266 + border-bottom: 1px solid var(--border); 267 + color: var(--text-secondary); 268 + } 269 + 270 + .data-table tr:hover td { 271 + background: var(--accent-light); 272 + } 273 + 274 + .badge { 275 + display: inline-flex; 276 + padding: 2px 10px; 55 277 border-radius: 999px; 56 - background: #9b3d25; 278 + font-size: 0.72rem; 279 + font-weight: 600; 280 + } 281 + 282 + .badge-high { 283 + background: rgba(180, 67, 46, 0.1); 284 + color: var(--accent); 285 + } 286 + 287 + .badge-mid { 288 + background: rgba(180, 140, 46, 0.1); 289 + color: #967020; 290 + } 291 + 292 + .badge-low { 293 + background: rgba(42, 102, 72, 0.08); 294 + color: var(--success); 295 + } 296 + 297 + /* Subscribe form */ 298 + .subscribe-form { 299 + max-width: 440px; 300 + } 301 + 302 + .subscribe-form label { 303 + font-size: 0.82rem; 304 + font-weight: 600; 305 + color: var(--text-secondary); 306 + display: block; 307 + margin-bottom: 8px; 308 + } 309 + 310 + .subscribe-form input[type="email"] { 311 + width: 100%; 312 + padding: 14px 16px; 313 + background: var(--surface); 314 + border: 1px solid var(--border); 315 + border-radius: 10px; 316 + font-family: var(--font-body); 317 + font-size: 0.92rem; 318 + color: var(--text); 319 + outline: none; 320 + transition: border-color 0.2s, box-shadow 0.2s; 321 + margin-bottom: 16px; 322 + } 323 + 324 + .subscribe-form input[type="email"]:focus { 325 + border-color: var(--accent); 326 + box-shadow: 0 0 0 3px var(--accent-light); 327 + } 328 + 329 + .btn-subscribe { 330 + padding: 13px 28px; 331 + background: var(--accent); 57 332 color: white; 58 - font: inherit; 333 + border: none; 334 + border-radius: 10px; 335 + font-family: var(--font-body); 336 + font-size: 0.9rem; 337 + font-weight: 600; 59 338 cursor: pointer; 339 + transition: background 0.2s, transform 0.12s, box-shadow 0.2s; 340 + outline: none; 60 341 } 342 + 343 + .btn-subscribe:hover { 344 + background: #9c3a28; 345 + box-shadow: 0 4px 18px var(--accent-glow); 346 + } 347 + 348 + .btn-subscribe:active { 349 + transform: scale(0.97); 350 + } 351 + 352 + /* Success banner */ 61 353 #success-banner { 62 354 display: none; 63 - margin-top: 18px; 64 - padding: 16px; 65 - border-radius: 16px; 66 - background: #276749; 67 - color: white; 68 - font-weight: 700; 355 + margin-top: 20px; 356 + padding: 16px 22px; 357 + border-radius: 12px; 358 + background: var(--success-bg); 359 + border: 1px solid rgba(42, 102, 72, 0.15); 360 + color: var(--success); 361 + font-weight: 600; 362 + font-size: 0.9rem; 363 + animation: fadeUp 0.4s cubic-bezier(0.22, 1, 0.36, 1); 364 + } 365 + 366 + @keyframes fadeUp { 367 + from { 368 + opacity: 0; 369 + transform: translateY(8px); 370 + } 371 + to { 372 + opacity: 1; 373 + transform: translateY(0); 374 + } 375 + } 376 + 377 + /* Section divider */ 378 + .section-divider { 379 + height: 1px; 380 + background: linear-gradient(to right, transparent, var(--border-strong), transparent); 381 + margin: 48px 0; 69 382 } 383 + 384 + /* Responsive */ 70 385 @media (max-width: 720px) { 71 - .cards { grid-template-columns: 1fr; } 386 + .card-grid { 387 + grid-template-columns: 1fr; 388 + } 389 + 390 + .header-nav { 391 + gap: 16px; 392 + } 393 + 394 + .section-heading { 395 + font-size: 1.6rem; 396 + } 72 397 } 73 398 </style> 74 399 </head> 75 400 76 401 <body> 402 + <div class="scroll-progress" id="scroll-progress"></div> 403 + 77 404 <header> 78 - <h1>Long Page Scroll Demo</h1> 79 - <p>Use this to validate that UXET keeps gaze points aligned as the page scrolls.</p> 405 + <div class="header-inner"> 406 + <div class="header-brand">Signal Intelligence</div> 407 + <nav class="header-nav"> 408 + <a href="#overview" class="active">Overview</a> 409 + <a href="#compare">Compare</a> 410 + <a href="#assess">Assess</a> 411 + <a href="#subscribe">Subscribe</a> 412 + </nav> 413 + </div> 80 414 </header> 81 415 82 416 <main> 83 - <section> 84 - <h2>Overview</h2> 85 - <p>This first section is intentionally roomy so gaze landing areas spread out over a larger scroll surface.</p> 86 - <div class="cards"> 87 - <div class="card">Pricing comparison</div> 88 - <div class="card">Feature matrix</div> 89 - <div class="card">Research summary</div> 417 + <!-- Section 1: Overview --> 418 + <section class="content-section" id="overview"> 419 + <div class="section-label">Section 01</div> 420 + <h2 class="section-heading">Market Landscape Overview</h2> 421 + <p class="section-subtext"> 422 + This briefing synthesises our Q1 findings across three key verticals. Scroll through each section to review the analysis, then subscribe for the full report. 423 + </p> 424 + <div class="card-grid"> 425 + <div class="card"> 426 + <div class="card-icon">📊</div> 427 + <h3>Pricing Analysis</h3> 428 + <p>Tier-by-tier breakdown across 14 competing solutions, normalised by seat count and feature parity.</p> 429 + <div class="card-tag">Updated Mar 2026</div> 430 + </div> 431 + <div class="card"> 432 + <div class="card-icon">🧩</div> 433 + <h3>Feature Matrix</h3> 434 + <p>Capability comparison spanning authentication, analytics, integrations, and developer experience.</p> 435 + <div class="card-tag">87 features tracked</div> 436 + </div> 437 + <div class="card"> 438 + <div class="card-icon">📰</div> 439 + <h3>Research Summary</h3> 440 + <p>Key takeaways from 220+ customer interviews and 12 industry analyst briefings this quarter.</p> 441 + <div class="card-tag">Confidence: High</div> 442 + </div> 443 + </div> 444 + 445 + <div class="pull-quote"> 446 + <p>"The most revealing insight wasn't which features users wanted — it was which ones they never discovered."</p> 447 + <cite>— Ava Chen, Principal Researcher</cite> 90 448 </div> 91 449 </section> 92 450 93 - <section> 94 - <h2>Comparison</h2> 95 - <p>Read through the options, scan the cards, and continue toward the bottom of the page.</p> 96 - <div class="cards"> 97 - <div class="card">Option A: best onboarding</div> 98 - <div class="card">Option B: strongest analytics</div> 99 - <div class="card">Option C: lowest cost</div> 451 + <div class="section-divider"></div> 452 + 453 + <!-- Section 2: Compare --> 454 + <section class="content-section" id="compare"> 455 + <div class="section-label">Section 02</div> 456 + <h2 class="section-heading">Competitive Comparison</h2> 457 + <p class="section-subtext"> 458 + Read through the options below. Each card highlights a different competitive advantage backed by our data. 459 + </p> 460 + <div class="card-grid"> 461 + <div class="card"> 462 + <div class="card-icon">🚀</div> 463 + <h3>Option A — Best Onboarding</h3> 464 + <p>Time-to-value under 8 minutes. Guided wizard reduces support tickets by 40% in the first week.</p> 465 + <div class="card-tag">NPS: 72</div> 466 + </div> 467 + <div class="card"> 468 + <div class="card-icon">📈</div> 469 + <h3>Option B — Strongest Analytics</h3> 470 + <p>Real-time dashboards with cohort breakdowns, funnel analysis, and customisable retention curves.</p> 471 + <div class="card-tag">NPS: 68</div> 472 + </div> 473 + <div class="card"> 474 + <div class="card-icon">💰</div> 475 + <h3>Option C — Lowest Cost</h3> 476 + <p>35% below category median with comparable feature coverage. Free tier supports up to 10K MAU.</p> 477 + <div class="card-tag">NPS: 65</div> 478 + </div> 100 479 </div> 480 + 481 + <table class="data-table"> 482 + <thead> 483 + <tr> 484 + <th>Criteria</th> 485 + <th>Option A</th> 486 + <th>Option B</th> 487 + <th>Option C</th> 488 + </tr> 489 + </thead> 490 + <tbody> 491 + <tr> 492 + <td>Setup Time</td> 493 + <td>8 min</td> 494 + <td>22 min</td> 495 + <td>15 min</td> 496 + </tr> 497 + <tr> 498 + <td>Analytics Depth</td> 499 + <td><span class="badge badge-mid">Medium</span></td> 500 + <td><span class="badge badge-high">Advanced</span></td> 501 + <td><span class="badge badge-low">Basic</span></td> 502 + </tr> 503 + <tr> 504 + <td>Annual Cost (100 seats)</td> 505 + <td>$14,400</td> 506 + <td>$18,600</td> 507 + <td>$9,200</td> 508 + </tr> 509 + <tr> 510 + <td>Support SLA</td> 511 + <td>4h</td> 512 + <td>2h</td> 513 + <td>24h</td> 514 + </tr> 515 + </tbody> 516 + </table> 101 517 </section> 102 518 103 - <section> 104 - <h2>Decision Notes</h2> 105 - <p>This section gives more vertical space for testing scroll-aware heatmap projection.</p> 106 - <div class="cards"> 107 - <div class="card">Implementation risk notes</div> 108 - <div class="card">Support expectations</div> 109 - <div class="card">Rollout concerns</div> 519 + <div class="section-divider"></div> 520 + 521 + <!-- Section 3: Decision Notes --> 522 + <section class="content-section" id="assess"> 523 + <div class="section-label">Section 03</div> 524 + <h2 class="section-heading">Risk Assessment & Decision Notes</h2> 525 + <p class="section-subtext"> 526 + This section provides deeper context for your procurement decision. Review the risk dimensions before subscribing. 527 + </p> 528 + <div class="card-grid"> 529 + <div class="card"> 530 + <div class="card-icon">⚠️</div> 531 + <h3>Implementation Risk</h3> 532 + <p>Option A's SDK requires Node ≥ 18 and has breaking changes every major release. Migration path exists but costs 2–3 dev-weeks.</p> 533 + <div class="card-tag">Risk: Medium</div> 534 + </div> 535 + <div class="card"> 536 + <div class="card-icon">🛟</div> 537 + <h3>Support Infrastructure</h3> 538 + <p>Only Option B offers a dedicated CSM at this tier. Options A and C rely on ticket queues with varying response quality.</p> 539 + <div class="card-tag">Verified Q1 2026</div> 540 + </div> 541 + <div class="card"> 542 + <div class="card-icon">🗓️</div> 543 + <h3>Rollout Considerations</h3> 544 + <p>Feature-flagged rollout is fully supported by Option B. Options A and C require custom implementation or third-party tooling.</p> 545 + <div class="card-tag">Phased rollout preferred</div> 546 + </div> 547 + </div> 548 + 549 + <div class="pull-quote"> 550 + <p>"Choosing the cheapest tool is rarely the cheapest decision."</p> 551 + <cite>— Marcus Henriksson, VP Engineering</cite> 110 552 </div> 111 553 </section> 112 554 113 - <section> 114 - <h2>Subscribe</h2> 115 - <p>Finish the task by subscribing below.</p> 116 - <form id="subscribe-form"> 117 - <label for="email">Email</label> 118 - <input id="email" type="email" required placeholder="name@example.com" 119 - style="display:block;width:min(420px,100%);margin:10px 0 18px;padding:14px 16px;border:1px solid rgba(31,26,23,0.2);border-radius:14px;"> 120 - <button type="submit">Subscribe</button> 555 + <div class="section-divider"></div> 556 + 557 + <!-- Section 4: Subscribe --> 558 + <section class="content-section" id="subscribe"> 559 + <div class="section-label">Section 04</div> 560 + <h2 class="section-heading">Get the Full Report</h2> 561 + <p class="section-subtext"> 562 + Subscribe below to receive the complete analysis with vendor scorecards, pricing breakdowns, and our recommended shortlist. 563 + </p> 564 + <form class="subscribe-form" id="subscribe-form"> 565 + <label for="email">Work Email</label> 566 + <input id="email" type="email" required placeholder="you@company.com"> 567 + <button type="submit" class="btn-subscribe">Subscribe to Report</button> 121 568 </form> 122 - <div id="success-banner">Subscription complete. Thanks for reviewing the full page.</div> 569 + <div id="success-banner">Subscription confirmed — the full report is on its way to your inbox.</div> 123 570 </section> 124 571 </main> 125 572 126 573 <script> 574 + // Scroll progress indicator 575 + const progressBar = document.getElementById('scroll-progress'); 576 + window.addEventListener('scroll', () => { 577 + const scrollTop = window.scrollY; 578 + const docHeight = document.documentElement.scrollHeight - window.innerHeight; 579 + const progress = docHeight > 0 ? (scrollTop / docHeight) * 100 : 0; 580 + progressBar.style.width = progress + '%'; 581 + }, { passive: true }); 582 + 583 + // Smooth scroll for nav links 584 + document.querySelectorAll('.header-nav a').forEach(link => { 585 + link.addEventListener('click', (e) => { 586 + e.preventDefault(); 587 + const target = document.querySelector(link.getAttribute('href')); 588 + if (target) { 589 + target.scrollIntoView({ behavior: 'smooth', block: 'start' }); 590 + } 591 + document.querySelectorAll('.header-nav a').forEach(l => l.classList.remove('active')); 592 + link.classList.add('active'); 593 + }); 594 + }); 595 + 596 + // Update active nav on scroll 597 + const sections = document.querySelectorAll('.content-section'); 598 + const navLinks = document.querySelectorAll('.header-nav a'); 599 + const observer = new IntersectionObserver((entries) => { 600 + entries.forEach(entry => { 601 + if (entry.isIntersecting) { 602 + navLinks.forEach(l => l.classList.remove('active')); 603 + const matching = document.querySelector(`.header-nav a[href="#${entry.target.id}"]`); 604 + if (matching) matching.classList.add('active'); 605 + } 606 + }); 607 + }, { threshold: 0.3 }); 608 + 609 + sections.forEach(s => observer.observe(s)); 610 + 611 + // Subscribe form 127 612 document.getElementById('subscribe-form').addEventListener('submit', (event) => { 128 613 event.preventDefault(); 129 614 document.getElementById('success-banner').style.display = 'block';
+579 -240
testable-apps/shop-app/index.html
··· 4 4 <head> 5 5 <meta charset="UTF-8"> 6 6 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 7 - <title>ShopEasy - Your Style, Delivered</title> 7 + <title>THREAD — Summer Collection 2026</title> 8 + <link rel="preconnect" href="https://fonts.googleapis.com"> 9 + <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> 10 + <link href="https://fonts.googleapis.com/css2?family=Syne:wght@400;500;600;700;800&family=Outfit:wght@300;400;500;600;700&display=swap" rel="stylesheet"> 8 11 <style> 9 - * { 12 + *, 13 + *::before, 14 + *::after { 10 15 box-sizing: border-box; 11 16 margin: 0; 12 17 padding: 0; 13 18 } 14 19 20 + :root { 21 + --bg: #faf9f7; 22 + --surface: #ffffff; 23 + --surface-elevated: #ffffff; 24 + --surface-muted: #f2f0ec; 25 + --border: #e8e4de; 26 + --border-strong: #d4cfc7; 27 + --text: #1a1715; 28 + --text-secondary: #6e6760; 29 + --text-muted: #a39e96; 30 + --brand: #1a1715; 31 + --brand-accent: #c2410c; 32 + --brand-accent-light: rgba(194, 65, 12, 0.06); 33 + --brand-accent-glow: rgba(194, 65, 12, 0.15); 34 + --success: #15803d; 35 + --success-bg: rgba(21, 128, 61, 0.06); 36 + --danger: #dc2626; 37 + --font-display: 'Syne', system-ui, sans-serif; 38 + --font-body: 'Outfit', system-ui, sans-serif; 39 + --radius-sm: 8px; 40 + --radius-md: 12px; 41 + --radius-lg: 18px; 42 + --radius-xl: 24px; 43 + } 44 + 15 45 body { 16 - font-family: 'Segoe UI', -apple-system, BlinkMacSystemFont, sans-serif; 17 - background: #f5f5f7; 18 - color: #1d1d1f; 46 + font-family: var(--font-body); 47 + background: var(--bg); 48 + color: var(--text); 19 49 line-height: 1.5; 50 + -webkit-font-smoothing: antialiased; 20 51 } 21 52 22 - /* Header */ 53 + /* ─── Header ─── */ 23 54 .header { 24 - background: white; 25 - box-shadow: 0 2px 10px rgba(0, 0, 0, 0.08); 55 + background: var(--surface); 56 + border-bottom: 1px solid var(--border); 26 57 position: sticky; 27 58 top: 0; 28 59 z-index: 100; ··· 31 62 .header-inner { 32 63 max-width: 1200px; 33 64 margin: 0 auto; 34 - padding: 16px 24px; 65 + padding: 0 32px; 35 66 display: flex; 36 67 align-items: center; 37 68 justify-content: space-between; 69 + height: 64px; 38 70 } 39 71 40 72 .logo { 41 - font-size: 1.5rem; 42 - font-weight: 700; 43 - color: #6366f1; 73 + font-family: var(--font-display); 74 + font-size: 1.35rem; 75 + font-weight: 800; 76 + color: var(--brand); 44 77 text-decoration: none; 78 + letter-spacing: 0.06em; 79 + text-transform: uppercase; 45 80 } 46 81 47 82 .nav { ··· 50 85 } 51 86 52 87 .nav a { 53 - color: #555; 88 + color: var(--text-secondary); 54 89 text-decoration: none; 55 90 font-weight: 500; 91 + font-size: 0.88rem; 56 92 transition: color 0.2s; 93 + position: relative; 57 94 } 58 95 59 - .nav a:hover { 60 - color: #6366f1; 96 + .nav a::after { 97 + content: ''; 98 + position: absolute; 99 + bottom: -2px; 100 + left: 0; 101 + width: 0; 102 + height: 1.5px; 103 + background: var(--brand-accent); 104 + transition: width 0.25s cubic-bezier(0.22, 1, 0.36, 1); 105 + } 106 + 107 + .nav a:hover::after, 108 + .nav a.active::after { 109 + width: 100%; 61 110 } 62 111 112 + .nav a:hover, 63 113 .nav a.active { 64 - color: #6366f1; 114 + color: var(--text); 65 115 } 66 116 67 117 .header-actions { 68 118 display: flex; 69 119 align-items: center; 70 - gap: 16px; 120 + gap: 14px; 71 121 } 72 122 73 123 .search-box { 74 124 display: flex; 75 125 align-items: center; 76 - background: #f0f0f0; 77 - border-radius: 8px; 78 - padding: 8px 12px; 126 + gap: 8px; 127 + background: var(--surface-muted); 128 + border: 1px solid var(--border); 129 + border-radius: var(--radius-md); 130 + padding: 8px 14px; 131 + transition: border-color 0.2s; 132 + } 133 + 134 + .search-box:focus-within { 135 + border-color: var(--border-strong); 136 + } 137 + 138 + .search-icon { 139 + color: var(--text-muted); 140 + font-size: 0.85rem; 79 141 } 80 142 81 143 .search-box input { 82 144 border: none; 83 145 background: transparent; 84 146 outline: none; 85 - width: 200px; 86 - font-size: 0.9rem; 147 + width: 180px; 148 + font-family: var(--font-body); 149 + font-size: 0.85rem; 150 + color: var(--text); 151 + } 152 + 153 + .search-box input::placeholder { 154 + color: var(--text-muted); 87 155 } 88 156 89 157 .cart-btn { 90 158 position: relative; 91 159 background: none; 92 - border: none; 93 - font-size: 1.5rem; 160 + border: 1px solid var(--border); 161 + border-radius: var(--radius-md); 162 + font-size: 1.1rem; 94 163 cursor: pointer; 95 - padding: 8px; 164 + padding: 8px 12px; 165 + transition: background 0.15s, border-color 0.15s; 166 + display: flex; 167 + align-items: center; 168 + gap: 6px; 169 + font-family: var(--font-body); 170 + font-size: 0.85rem; 171 + font-weight: 500; 172 + color: var(--text-secondary); 173 + } 174 + 175 + .cart-btn:hover { 176 + background: var(--surface-muted); 177 + border-color: var(--border-strong); 96 178 } 97 179 98 180 .cart-count { 99 - position: absolute; 100 - top: 0; 101 - right: 0; 102 - background: #ef4444; 181 + background: var(--brand-accent); 103 182 color: white; 104 - font-size: 0.7rem; 105 - width: 18px; 183 + font-size: 0.68rem; 184 + min-width: 18px; 106 185 height: 18px; 107 - border-radius: 50%; 186 + border-radius: 999px; 108 187 display: flex; 109 188 align-items: center; 110 189 justify-content: center; 111 190 font-weight: 600; 191 + padding: 0 5px; 112 192 } 113 193 114 - /* Hero */ 194 + /* ─── Hero ─── */ 115 195 .hero { 116 - background: linear-gradient(135deg, #6366f1, #8b5cf6); 196 + background: var(--brand); 117 197 color: white; 118 - padding: 60px 24px; 198 + padding: 72px 32px; 119 199 text-align: center; 200 + position: relative; 201 + overflow: hidden; 202 + } 203 + 204 + .hero::before { 205 + content: ''; 206 + position: absolute; 207 + inset: 0; 208 + background: linear-gradient(135deg, 209 + rgba(194, 65, 12, 0.15) 0%, 210 + transparent 50%, 211 + rgba(194, 65, 12, 0.08) 100%); 212 + pointer-events: none; 213 + } 214 + 215 + .hero-inner { 216 + position: relative; 217 + z-index: 1; 218 + } 219 + 220 + .hero-tag { 221 + font-family: var(--font-display); 222 + font-size: 0.72rem; 223 + letter-spacing: 0.15em; 224 + text-transform: uppercase; 225 + opacity: 0.6; 226 + margin-bottom: 16px; 120 227 } 121 228 122 229 .hero h1 { 123 - font-size: 2.5rem; 230 + font-family: var(--font-display); 231 + font-size: 3rem; 232 + font-weight: 800; 233 + letter-spacing: -0.02em; 124 234 margin-bottom: 12px; 235 + line-height: 1.1; 125 236 } 126 237 127 238 .hero p { 128 - font-size: 1.1rem; 129 - opacity: 0.9; 239 + font-size: 1.05rem; 240 + font-weight: 300; 241 + opacity: 0.75; 242 + max-width: 480px; 243 + margin: 0 auto; 130 244 } 131 245 132 - /* Category Filters */ 246 + /* ─── Filters ─── */ 133 247 .filters { 134 248 max-width: 1200px; 135 - margin: 32px auto 0; 136 - padding: 0 24px; 249 + margin: 36px auto 0; 250 + padding: 0 32px; 137 251 } 138 252 139 253 .filter-tabs { 140 254 display: flex; 141 - gap: 12px; 255 + gap: 8px; 142 256 flex-wrap: wrap; 143 257 } 144 258 145 259 .filter-tab { 146 - padding: 10px 20px; 147 - background: white; 148 - border: 2px solid #e0e0e0; 149 - border-radius: 24px; 150 - font-size: 0.9rem; 260 + padding: 9px 20px; 261 + background: var(--surface); 262 + border: 1px solid var(--border); 263 + border-radius: 999px; 264 + font-family: var(--font-body); 265 + font-size: 0.82rem; 151 266 font-weight: 500; 152 267 cursor: pointer; 268 + color: var(--text-secondary); 153 269 transition: all 0.2s; 154 270 } 155 271 156 272 .filter-tab:hover { 157 - border-color: #6366f1; 158 - color: #6366f1; 273 + border-color: var(--border-strong); 274 + color: var(--text); 159 275 } 160 276 161 277 .filter-tab.active { 162 - background: #6366f1; 163 - border-color: #6366f1; 278 + background: var(--brand); 279 + border-color: var(--brand); 164 280 color: white; 165 281 } 166 282 167 - /* Product Grid */ 283 + /* ─── Product Grid ─── */ 168 284 .products { 169 285 max-width: 1200px; 170 - margin: 32px auto; 171 - padding: 0 24px; 286 + margin: 36px auto 80px; 287 + padding: 0 32px; 172 288 } 173 289 174 - .products h2 { 175 - font-size: 1.5rem; 290 + .section-title { 291 + font-family: var(--font-display); 292 + font-size: 1.3rem; 293 + font-weight: 700; 176 294 margin-bottom: 24px; 295 + letter-spacing: -0.01em; 177 296 } 178 297 179 298 .product-grid { 180 299 display: grid; 181 - grid-template-columns: repeat(auto-fill, minmax(250px, 1fr)); 182 - gap: 24px; 300 + grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); 301 + gap: 20px; 183 302 } 184 303 185 304 .product-card { 186 - background: white; 187 - border-radius: 16px; 305 + background: var(--surface); 306 + border: 1px solid var(--border); 307 + border-radius: var(--radius-lg); 188 308 overflow: hidden; 189 - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05); 190 - transition: transform 0.2s, box-shadow 0.2s; 191 309 cursor: pointer; 310 + transition: transform 0.2s, box-shadow 0.25s, border-color 0.2s; 192 311 } 193 312 194 313 .product-card:hover { 195 314 transform: translateY(-4px); 196 - box-shadow: 0 8px 24px rgba(0, 0, 0, 0.1); 315 + box-shadow: 0 16px 40px rgba(26, 23, 21, 0.08); 316 + border-color: var(--border-strong); 197 317 } 198 318 199 319 .product-image { 200 - height: 200px; 320 + height: 220px; 201 321 display: flex; 202 322 align-items: center; 203 323 justify-content: center; 204 - font-size: 4rem; 205 - background: linear-gradient(135deg, #f0f0ff, #e8e8ff); 324 + font-size: 1.1rem; 325 + font-weight: 600; 326 + color: var(--text-muted); 327 + position: relative; 328 + overflow: hidden; 329 + } 330 + 331 + .product-image .product-swatch { 332 + position: absolute; 333 + inset: 0; 334 + opacity: 0.12; 335 + } 336 + 337 + .product-image .product-label { 338 + position: relative; 339 + z-index: 1; 340 + font-family: var(--font-display); 341 + font-size: 0.82rem; 342 + letter-spacing: 0.06em; 343 + text-transform: uppercase; 344 + color: var(--text-secondary); 206 345 } 207 346 208 347 .product-info { 209 - padding: 16px; 348 + padding: 18px; 210 349 } 211 350 212 351 .product-name { 213 352 font-weight: 600; 214 - font-size: 1rem; 215 - margin-bottom: 4px; 353 + font-size: 0.95rem; 354 + margin-bottom: 2px; 355 + color: var(--text); 216 356 } 217 357 218 358 .product-color { 219 - color: #888; 220 - font-size: 0.85rem; 221 - margin-bottom: 8px; 359 + color: var(--text-muted); 360 + font-size: 0.82rem; 361 + margin-bottom: 10px; 362 + } 363 + 364 + .product-bottom { 365 + display: flex; 366 + align-items: center; 367 + justify-content: space-between; 222 368 } 223 369 224 370 .product-price { 225 - font-size: 1.25rem; 371 + font-family: var(--font-display); 372 + font-size: 1.15rem; 226 373 font-weight: 700; 227 - color: #6366f1; 374 + color: var(--text); 228 375 } 229 376 230 377 .product-rating { 231 378 display: flex; 232 379 align-items: center; 233 380 gap: 4px; 234 - margin-top: 8px; 235 - color: #fbbf24; 236 - font-size: 0.85rem; 381 + font-size: 0.78rem; 382 + color: var(--text-muted); 237 383 } 238 384 239 - /* Product Modal */ 385 + .product-rating .stars { 386 + color: var(--brand-accent); 387 + letter-spacing: 1px; 388 + } 389 + 390 + /* ─── Product Modal ─── */ 240 391 .modal-overlay { 241 392 display: none; 242 393 position: fixed; 243 394 inset: 0; 244 - background: rgba(0, 0, 0, 0.5); 395 + background: rgba(26, 23, 21, 0.45); 396 + backdrop-filter: blur(6px); 245 397 z-index: 200; 246 398 align-items: center; 247 399 justify-content: center; ··· 253 405 } 254 406 255 407 .modal { 256 - background: white; 257 - border-radius: 20px; 258 - max-width: 600px; 408 + background: var(--surface); 409 + border-radius: var(--radius-xl); 410 + max-width: 560px; 259 411 width: 100%; 260 412 max-height: 90vh; 261 413 overflow-y: auto; 262 - animation: modalSlide 0.3s ease; 414 + animation: modalIn 0.3s cubic-bezier(0.22, 1, 0.36, 1); 415 + border: 1px solid var(--border); 263 416 } 264 417 265 - @keyframes modalSlide { 418 + @keyframes modalIn { 266 419 from { 267 420 opacity: 0; 268 - transform: scale(0.95); 421 + transform: scale(0.96) translateY(8px); 269 422 } 270 - 271 423 to { 272 424 opacity: 1; 273 - transform: scale(1); 425 + transform: scale(1) translateY(0); 274 426 } 275 427 } 276 428 ··· 279 431 justify-content: space-between; 280 432 align-items: center; 281 433 padding: 20px 24px; 282 - border-bottom: 1px solid #eee; 434 + border-bottom: 1px solid var(--border); 435 + } 436 + 437 + .modal-header h3 { 438 + font-family: var(--font-display); 439 + font-size: 0.92rem; 440 + font-weight: 700; 441 + letter-spacing: 0.03em; 442 + text-transform: uppercase; 283 443 } 284 444 285 445 .modal-close { 286 - background: none; 287 - border: none; 288 - font-size: 1.5rem; 446 + background: var(--surface-muted); 447 + border: 1px solid var(--border); 448 + width: 32px; 449 + height: 32px; 450 + border-radius: 50%; 451 + display: flex; 452 + align-items: center; 453 + justify-content: center; 289 454 cursor: pointer; 290 - color: #888; 455 + font-size: 1rem; 456 + color: var(--text-secondary); 457 + transition: background 0.15s; 458 + } 459 + 460 + .modal-close:hover { 461 + background: var(--border); 291 462 } 292 463 293 464 .modal-body { ··· 295 466 } 296 467 297 468 .modal-product-image { 298 - height: 250px; 469 + height: 240px; 299 470 display: flex; 300 471 align-items: center; 301 472 justify-content: center; 302 - font-size: 6rem; 303 - background: linear-gradient(135deg, #f0f0ff, #e8e8ff); 304 - border-radius: 12px; 473 + border-radius: var(--radius-md); 305 474 margin-bottom: 24px; 475 + position: relative; 476 + overflow: hidden; 477 + } 478 + 479 + .modal-product-image .product-swatch { 480 + position: absolute; 481 + inset: 0; 482 + opacity: 0.12; 483 + } 484 + 485 + .modal-product-image .product-label { 486 + position: relative; 487 + z-index: 1; 488 + font-family: var(--font-display); 489 + font-size: 1rem; 490 + letter-spacing: 0.08em; 491 + text-transform: uppercase; 492 + color: var(--text-secondary); 306 493 } 307 494 308 495 .modal-product-info h2 { 309 - font-size: 1.5rem; 310 - margin-bottom: 8px; 496 + font-family: var(--font-display); 497 + font-size: 1.4rem; 498 + font-weight: 700; 499 + margin-bottom: 4px; 500 + } 501 + 502 + .modal-product-color { 503 + color: var(--text-muted); 504 + font-size: 0.88rem; 505 + margin-bottom: 12px; 311 506 } 312 507 313 508 .modal-product-price { 314 - font-size: 2rem; 315 - font-weight: 700; 316 - color: #6366f1; 317 - margin-bottom: 16px; 509 + font-family: var(--font-display); 510 + font-size: 1.8rem; 511 + font-weight: 800; 512 + color: var(--text); 513 + margin-bottom: 24px; 318 514 } 319 515 320 516 .size-selector { ··· 323 519 324 520 .size-selector label { 325 521 display: block; 522 + font-size: 0.8rem; 326 523 font-weight: 600; 327 - margin-bottom: 8px; 524 + color: var(--text-secondary); 525 + margin-bottom: 10px; 526 + text-transform: uppercase; 527 + letter-spacing: 0.05em; 328 528 } 329 529 330 530 .size-options { ··· 333 533 } 334 534 335 535 .size-option { 336 - width: 44px; 337 - height: 44px; 338 - border: 2px solid #e0e0e0; 339 - border-radius: 8px; 536 + width: 46px; 537 + height: 46px; 538 + border: 1.5px solid var(--border); 539 + border-radius: var(--radius-sm); 340 540 display: flex; 341 541 align-items: center; 342 542 justify-content: center; 343 543 cursor: pointer; 344 544 font-weight: 500; 345 - transition: all 0.2s; 545 + font-size: 0.85rem; 546 + color: var(--text-secondary); 547 + transition: all 0.15s; 346 548 } 347 549 348 550 .size-option:hover { 349 - border-color: #6366f1; 551 + border-color: var(--border-strong); 552 + color: var(--text); 350 553 } 351 554 352 555 .size-option.selected { 353 - border-color: #6366f1; 354 - background: #6366f1; 556 + border-color: var(--brand); 557 + background: var(--brand); 355 558 color: white; 356 559 } 357 560 358 561 .add-to-cart-btn { 359 562 width: 100%; 360 563 padding: 16px; 361 - background: linear-gradient(135deg, #6366f1, #8b5cf6); 564 + background: var(--brand); 362 565 color: white; 363 566 border: none; 364 - border-radius: 12px; 365 - font-size: 1.1rem; 567 + border-radius: var(--radius-md); 568 + font-family: var(--font-body); 569 + font-size: 0.95rem; 366 570 font-weight: 600; 367 571 cursor: pointer; 368 - transition: transform 0.2s, box-shadow 0.2s; 572 + transition: background 0.2s, transform 0.12s, box-shadow 0.2s; 369 573 } 370 574 371 575 .add-to-cart-btn:hover { 372 - transform: translateY(-2px); 373 - box-shadow: 0 8px 20px rgba(99, 102, 241, 0.4); 576 + background: #2d2520; 577 + box-shadow: 0 8px 24px rgba(26, 23, 21, 0.15); 374 578 } 375 579 376 - /* Cart Sidebar */ 580 + .add-to-cart-btn:active { 581 + transform: scale(0.98); 582 + } 583 + 584 + /* ─── Cart Sidebar ─── */ 377 585 .cart-sidebar { 378 586 position: fixed; 379 587 top: 0; 380 - right: -400px; 381 - width: 400px; 588 + right: -420px; 589 + width: 420px; 382 590 height: 100vh; 383 - background: white; 384 - box-shadow: -4px 0 20px rgba(0, 0, 0, 0.15); 591 + background: var(--surface); 592 + border-left: 1px solid var(--border); 385 593 z-index: 300; 386 - transition: right 0.3s ease; 594 + transition: right 0.35s cubic-bezier(0.22, 1, 0.36, 1); 387 595 display: flex; 388 596 flex-direction: column; 389 597 } ··· 394 602 395 603 .cart-header { 396 604 padding: 20px 24px; 397 - border-bottom: 1px solid #eee; 605 + border-bottom: 1px solid var(--border); 398 606 display: flex; 399 607 justify-content: space-between; 400 608 align-items: center; 401 609 } 402 610 403 611 .cart-header h2 { 404 - font-size: 1.25rem; 612 + font-family: var(--font-display); 613 + font-size: 1.1rem; 614 + font-weight: 700; 405 615 } 406 616 407 617 .cart-close { 408 - background: none; 409 - border: none; 410 - font-size: 1.5rem; 618 + background: var(--surface-muted); 619 + border: 1px solid var(--border); 620 + width: 32px; 621 + height: 32px; 622 + border-radius: 50%; 623 + display: flex; 624 + align-items: center; 625 + justify-content: center; 411 626 cursor: pointer; 412 - color: #888; 627 + font-size: 1rem; 628 + color: var(--text-secondary); 629 + transition: background 0.15s; 630 + } 631 + 632 + .cart-close:hover { 633 + background: var(--border); 413 634 } 414 635 415 636 .cart-items { 416 637 flex: 1; 417 638 overflow-y: auto; 418 639 padding: 16px 24px; 640 + } 641 + 642 + .cart-items::-webkit-scrollbar { 643 + width: 5px; 644 + } 645 + 646 + .cart-items::-webkit-scrollbar-thumb { 647 + background: var(--border-strong); 648 + border-radius: 3px; 419 649 } 420 650 421 651 .cart-empty { 422 652 text-align: center; 423 - color: #888; 424 - padding: 40px; 653 + color: var(--text-muted); 654 + padding: 48px 24px; 655 + font-size: 0.9rem; 425 656 } 426 657 427 658 .cart-item { 428 659 display: flex; 429 660 gap: 16px; 430 661 padding: 16px 0; 431 - border-bottom: 1px solid #eee; 662 + border-bottom: 1px solid var(--border); 663 + animation: fadeIn 0.25s ease; 664 + } 665 + 666 + @keyframes fadeIn { 667 + from { opacity: 0; transform: translateX(8px); } 668 + to { opacity: 1; transform: translateX(0); } 432 669 } 433 670 434 671 .cart-item-image { 435 - width: 80px; 436 - height: 80px; 437 - background: #f0f0ff; 438 - border-radius: 8px; 672 + width: 72px; 673 + height: 72px; 674 + border-radius: var(--radius-sm); 439 675 display: flex; 440 676 align-items: center; 441 677 justify-content: center; 442 - font-size: 2rem; 678 + font-size: 0.7rem; 679 + font-family: var(--font-display); 680 + letter-spacing: 0.04em; 681 + text-transform: uppercase; 682 + color: var(--text-muted); 683 + flex-shrink: 0; 684 + position: relative; 685 + overflow: hidden; 686 + } 687 + 688 + .cart-item-image .product-swatch { 689 + position: absolute; 690 + inset: 0; 691 + opacity: 0.15; 692 + } 693 + 694 + .cart-item-image span { 695 + position: relative; 696 + z-index: 1; 443 697 } 444 698 445 699 .cart-item-info { 446 700 flex: 1; 701 + min-width: 0; 447 702 } 448 703 449 704 .cart-item-name { 450 705 font-weight: 600; 451 - margin-bottom: 4px; 706 + font-size: 0.9rem; 707 + margin-bottom: 2px; 452 708 } 453 709 454 710 .cart-item-details { 455 - color: #888; 456 - font-size: 0.85rem; 457 - margin-bottom: 8px; 711 + color: var(--text-muted); 712 + font-size: 0.8rem; 713 + margin-bottom: 6px; 458 714 } 459 715 460 716 .cart-item-price { 461 717 font-weight: 600; 462 - color: #6366f1; 718 + font-family: var(--font-display); 719 + color: var(--text); 463 720 } 464 721 465 722 .cart-item-remove { 466 723 background: none; 467 724 border: none; 468 - color: #ef4444; 725 + color: var(--text-muted); 469 726 cursor: pointer; 470 - font-size: 0.85rem; 727 + font-size: 0.78rem; 728 + font-family: var(--font-body); 729 + transition: color 0.15s; 730 + align-self: flex-start; 731 + padding: 2px 0; 732 + } 733 + 734 + .cart-item-remove:hover { 735 + color: var(--danger); 471 736 } 472 737 473 738 .cart-footer { 474 739 padding: 20px 24px; 475 - border-top: 1px solid #eee; 476 - background: #fafafa; 740 + border-top: 1px solid var(--border); 741 + background: var(--surface-muted); 477 742 } 478 743 479 744 .cart-total { 480 745 display: flex; 481 746 justify-content: space-between; 482 - font-size: 1.25rem; 747 + font-size: 1.1rem; 483 748 font-weight: 600; 484 749 margin-bottom: 16px; 750 + } 751 + 752 + .cart-total span:last-child { 753 + font-family: var(--font-display); 754 + font-weight: 700; 485 755 } 486 756 487 757 .checkout-btn { 488 758 width: 100%; 489 - padding: 16px; 490 - background: linear-gradient(135deg, #22c55e, #16a34a); 759 + padding: 15px; 760 + background: var(--success); 491 761 color: white; 492 762 border: none; 493 - border-radius: 12px; 494 - font-size: 1.1rem; 763 + border-radius: var(--radius-md); 764 + font-family: var(--font-body); 765 + font-size: 0.95rem; 495 766 font-weight: 600; 496 767 cursor: pointer; 497 - transition: transform 0.2s; 768 + transition: background 0.2s, transform 0.12s; 498 769 } 499 770 500 771 .checkout-btn:hover { 501 - transform: translateY(-2px); 772 + background: #116932; 773 + } 774 + 775 + .checkout-btn:active { 776 + transform: scale(0.98); 502 777 } 503 778 504 779 .cart-overlay { 505 780 display: none; 506 781 position: fixed; 507 782 inset: 0; 508 - background: rgba(0, 0, 0, 0.3); 783 + background: rgba(26, 23, 21, 0.3); 509 784 z-index: 250; 785 + backdrop-filter: blur(3px); 510 786 } 511 787 512 788 .cart-overlay.active { 513 789 display: block; 514 790 } 515 791 516 - /* Checkout Success */ 792 + /* ─── Checkout Success ─── */ 517 793 .checkout-success { 518 794 display: none; 519 795 position: fixed; 520 796 inset: 0; 521 - background: rgba(0, 0, 0, 0.5); 797 + background: rgba(26, 23, 21, 0.45); 798 + backdrop-filter: blur(6px); 522 799 z-index: 400; 523 800 align-items: center; 524 801 justify-content: center; ··· 529 806 } 530 807 531 808 .success-card { 532 - background: white; 533 - border-radius: 20px; 809 + background: var(--surface); 810 + border-radius: var(--radius-xl); 534 811 padding: 48px; 535 812 text-align: center; 536 813 max-width: 400px; 537 - animation: modalSlide 0.3s ease; 814 + border: 1px solid var(--border); 815 + animation: modalIn 0.3s cubic-bezier(0.22, 1, 0.36, 1); 538 816 } 539 817 540 818 .success-icon { 541 - font-size: 4rem; 542 - margin-bottom: 16px; 819 + width: 64px; 820 + height: 64px; 821 + background: var(--success-bg); 822 + border-radius: 50%; 823 + display: flex; 824 + align-items: center; 825 + justify-content: center; 826 + margin: 0 auto 20px; 827 + } 828 + 829 + .success-icon svg { 830 + width: 28px; 831 + height: 28px; 832 + color: var(--success); 543 833 } 544 834 545 835 .success-card h2 { 546 - color: #22c55e; 836 + font-family: var(--font-display); 837 + font-size: 1.4rem; 838 + font-weight: 700; 839 + color: var(--text); 547 840 margin-bottom: 8px; 548 841 } 549 842 550 843 .success-card p { 551 - color: #666; 552 - margin-bottom: 24px; 844 + color: var(--text-secondary); 845 + font-size: 0.9rem; 846 + margin-bottom: 28px; 553 847 } 554 848 555 849 .continue-btn { 556 850 padding: 12px 32px; 557 - background: #6366f1; 851 + background: var(--brand); 558 852 color: white; 559 853 border: none; 560 - border-radius: 8px; 561 - font-size: 1rem; 854 + border-radius: var(--radius-md); 855 + font-family: var(--font-body); 856 + font-size: 0.9rem; 562 857 font-weight: 600; 563 858 cursor: pointer; 859 + transition: background 0.2s; 860 + } 861 + 862 + .continue-btn:hover { 863 + background: #2d2520; 564 864 } 565 865 </style> 566 866 </head> ··· 569 869 <!-- Header --> 570 870 <header class="header"> 571 871 <div class="header-inner"> 572 - <a href="#" class="logo">ShopEasy</a> 872 + <a href="#" class="logo">Thread</a> 573 873 <nav class="nav"> 574 874 <a href="#" class="active">Shop</a> 575 875 <a href="#">Men</a> ··· 578 878 </nav> 579 879 <div class="header-actions"> 580 880 <div class="search-box"> 581 - <span>🔍</span> 881 + <span class="search-icon">⌕</span> 582 882 <input type="text" placeholder="Search products..."> 583 883 </div> 584 884 <button class="cart-btn" id="cart-btn"> 585 - 🛒 885 + Bag 586 886 <span class="cart-count" id="cart-count">0</span> 587 887 </button> 588 888 </div> ··· 591 891 592 892 <!-- Hero --> 593 893 <section class="hero"> 594 - <h1>Summer Collection 2026</h1> 595 - <p>Discover the latest trends in fashion. Free shipping on orders over $50!</p> 894 + <div class="hero-inner"> 895 + <div class="hero-tag">New Season</div> 896 + <h1>Summer Collection '26</h1> 897 + <p>Discover the latest essentials. Free shipping on all orders over $50.</p> 898 + </div> 596 899 </section> 597 900 598 901 <!-- Filters --> ··· 608 911 609 912 <!-- Products --> 610 913 <section class="products"> 611 - <h2>Featured Products</h2> 612 - <div class="product-grid" id="product-grid"> 613 - <!-- Products will be inserted here --> 614 - </div> 914 + <h2 class="section-title">Featured Products</h2> 915 + <div class="product-grid" id="product-grid"></div> 615 916 </section> 616 917 617 918 <!-- Product Modal --> ··· 622 923 <button class="modal-close" id="modal-close">×</button> 623 924 </div> 624 925 <div class="modal-body"> 625 - <div class="modal-product-image" id="modal-image">👕</div> 926 + <div class="modal-product-image" id="modal-image"></div> 626 927 <div class="modal-product-info"> 627 - <h2 id="modal-name">Classic Blue T-Shirt</h2> 628 - <p id="modal-color" style="color: #888; margin-bottom: 8px;">Blue</p> 629 - <div class="modal-product-price" id="modal-price">$29.99</div> 928 + <h2 id="modal-name">Product Name</h2> 929 + <p class="modal-product-color" id="modal-color">Color</p> 930 + <div class="modal-product-price" id="modal-price">$0.00</div> 630 931 631 932 <div class="size-selector"> 632 933 <label>Select Size</label> ··· 639 940 </div> 640 941 </div> 641 942 642 - <button class="add-to-cart-btn" id="add-to-cart">Add to Cart</button> 943 + <button class="add-to-cart-btn" id="add-to-cart">Add to Bag</button> 643 944 </div> 644 945 </div> 645 946 </div> ··· 649 950 <div class="cart-overlay" id="cart-overlay"></div> 650 951 <div class="cart-sidebar" id="cart-sidebar"> 651 952 <div class="cart-header"> 652 - <h2>Your Cart</h2> 953 + <h2>Your Bag</h2> 653 954 <button class="cart-close" id="cart-close">×</button> 654 955 </div> 655 956 <div class="cart-items" id="cart-items"> 656 - <div class="cart-empty">Your cart is empty</div> 957 + <div class="cart-empty">Your bag is empty</div> 657 958 </div> 658 959 <div class="cart-footer"> 659 960 <div class="cart-total"> 660 961 <span>Total</span> 661 962 <span id="cart-total">$0.00</span> 662 963 </div> 663 - <button class="checkout-btn" id="checkout-btn">Proceed to Checkout</button> 964 + <button class="checkout-btn" id="checkout-btn">Checkout</button> 664 965 </div> 665 966 </div> 666 967 667 968 <!-- Checkout Success --> 668 969 <div class="checkout-success" id="checkout-success"> 669 970 <div class="success-card"> 670 - <div class="success-icon">✅</div> 671 - <h2>Purchase Complete!</h2> 672 - <p>Thank you for your order. Your items will be shipped soon.</p> 971 + <div class="success-icon"> 972 + <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"> 973 + <path d="M20 6L9 17l-5-5"/> 974 + </svg> 975 + </div> 976 + <h2>Order Confirmed</h2> 977 + <p>Thanks for your purchase. Your items are on their way.</p> 673 978 <button class="continue-btn" id="continue-btn">Continue Shopping</button> 674 979 </div> 675 980 </div> 676 981 677 982 <script> 678 - // Product data 983 + // Color palettes for product swatches 984 + const colorSwatches = { 985 + 'Blue': { bg: 'linear-gradient(135deg, #dbeafe, #bfdbfe)', text: '#3b82f6' }, 986 + 'Red': { bg: 'linear-gradient(135deg, #fee2e2, #fecaca)', text: '#ef4444' }, 987 + 'Black': { bg: 'linear-gradient(135deg, #e5e5e5, #d4d4d4)', text: '#404040' }, 988 + 'White': { bg: 'linear-gradient(135deg, #fafafa, #f0f0f0)', text: '#a3a3a3' }, 989 + 'Dark Blue': { bg: 'linear-gradient(135deg, #c7d2fe, #a5b4fc)', text: '#4f46e5' }, 990 + 'Light Blue': { bg: 'linear-gradient(135deg, #cffafe, #a5f3fc)', text: '#0891b2' }, 991 + 'Floral': { bg: 'linear-gradient(135deg, #fce7f3, #fbcfe8)', text: '#db2777' }, 992 + 'Green': { bg: 'linear-gradient(135deg, #d1fae5, #a7f3d0)', text: '#059669' }, 993 + 'Navy': { bg: 'linear-gradient(135deg, #c7d2fe, #a5b4fc)', text: '#3730a3' }, 994 + 'Brown': { bg: 'linear-gradient(135deg, #fde68a, #fcd34d)', text: '#92400e' }, 995 + }; 996 + 997 + const categoryLabels = { 998 + 't-shirts': 'Tee', 999 + 'jeans': 'Denim', 1000 + 'dresses': 'Dress', 1001 + 'jackets': 'Outerwear', 1002 + }; 1003 + 679 1004 const products = [ 680 - { id: 1, name: 'Classic T-Shirt', color: 'Blue', price: 29.99, category: 't-shirts', emoji: '👕', rating: 4.5 }, 681 - { id: 2, name: 'Classic T-Shirt', color: 'Red', price: 29.99, category: 't-shirts', emoji: '👕', rating: 4.3 }, 682 - { id: 3, name: 'Classic T-Shirt', color: 'Black', price: 29.99, category: 't-shirts', emoji: '👕', rating: 4.8 }, 683 - { id: 4, name: 'Premium T-Shirt', color: 'White', price: 39.99, category: 't-shirts', emoji: '👕', rating: 4.6 }, 684 - { id: 5, name: 'Slim Fit Jeans', color: 'Dark Blue', price: 59.99, category: 'jeans', emoji: '👖', rating: 4.4 }, 685 - { id: 6, name: 'Relaxed Jeans', color: 'Light Blue', price: 54.99, category: 'jeans', emoji: '👖', rating: 4.2 }, 686 - { id: 7, name: 'Summer Dress', color: 'Floral', price: 49.99, category: 'dresses', emoji: '👗', rating: 4.7 }, 687 - { id: 8, name: 'Evening Dress', color: 'Black', price: 79.99, category: 'dresses', emoji: '👗', rating: 4.9 }, 688 - { id: 9, name: 'Denim Jacket', color: 'Blue', price: 89.99, category: 'jackets', emoji: '🧥', rating: 4.5 }, 689 - { id: 10, name: 'Leather Jacket', color: 'Brown', price: 149.99, category: 'jackets', emoji: '🧥', rating: 4.8 }, 690 - { id: 11, name: 'Graphic T-Shirt', color: 'Green', price: 34.99, category: 't-shirts', emoji: '👕', rating: 4.1 }, 691 - { id: 12, name: 'V-Neck T-Shirt', color: 'Navy', price: 32.99, category: 't-shirts', emoji: '👕', rating: 4.4 }, 1005 + { id: 1, name: 'Classic T-Shirt', color: 'Blue', price: 29.99, category: 't-shirts', rating: 4.5 }, 1006 + { id: 2, name: 'Classic T-Shirt', color: 'Red', price: 29.99, category: 't-shirts', rating: 4.3 }, 1007 + { id: 3, name: 'Classic T-Shirt', color: 'Black', price: 29.99, category: 't-shirts', rating: 4.8 }, 1008 + { id: 4, name: 'Premium T-Shirt', color: 'White', price: 39.99, category: 't-shirts', rating: 4.6 }, 1009 + { id: 5, name: 'Slim Fit Jeans', color: 'Dark Blue', price: 59.99, category: 'jeans', rating: 4.4 }, 1010 + { id: 6, name: 'Relaxed Jeans', color: 'Light Blue', price: 54.99, category: 'jeans', rating: 4.2 }, 1011 + { id: 7, name: 'Summer Dress', color: 'Floral', price: 49.99, category: 'dresses', rating: 4.7 }, 1012 + { id: 8, name: 'Evening Dress', color: 'Black', price: 79.99, category: 'dresses', rating: 4.9 }, 1013 + { id: 9, name: 'Denim Jacket', color: 'Blue', price: 89.99, category: 'jackets', rating: 4.5 }, 1014 + { id: 10, name: 'Leather Jacket', color: 'Brown', price: 149.99, category: 'jackets', rating: 4.8 }, 1015 + { id: 11, name: 'Graphic T-Shirt', color: 'Green', price: 34.99, category: 't-shirts', rating: 4.1 }, 1016 + { id: 12, name: 'V-Neck T-Shirt', color: 'Navy', price: 32.99, category: 't-shirts', rating: 4.4 }, 692 1017 ]; 693 1018 694 1019 let cart = []; ··· 696 1021 let selectedSize = 'M'; 697 1022 let currentFilter = 'all'; 698 1023 699 - // DOM Elements 700 1024 const productGrid = document.getElementById('product-grid'); 701 1025 const productModal = document.getElementById('product-modal'); 702 1026 const cartSidebar = document.getElementById('cart-sidebar'); ··· 706 1030 const cartTotal = document.getElementById('cart-total'); 707 1031 const checkoutSuccess = document.getElementById('checkout-success'); 708 1032 709 - // Render products 1033 + function getSwatchStyle(color) { 1034 + const swatch = colorSwatches[color] || { bg: 'linear-gradient(135deg, #f5f5f5, #e5e5e5)', text: '#888' }; 1035 + return swatch; 1036 + } 1037 + 710 1038 function renderProducts(filter = 'all') { 711 1039 const filtered = filter === 'all' 712 1040 ? products 713 1041 : products.filter(p => p.category === filter); 714 1042 715 - productGrid.innerHTML = filtered.map(product => ` 716 - <div class="product-card" data-id="${product.id}"> 717 - <div class="product-image">${product.emoji}</div> 718 - <div class="product-info"> 719 - <div class="product-name">${product.name}</div> 720 - <div class="product-color">${product.color}</div> 721 - <div class="product-price">$${product.price.toFixed(2)}</div> 722 - <div class="product-rating"> 723 - ${'★'.repeat(Math.floor(product.rating))}${'☆'.repeat(5 - Math.floor(product.rating))} 724 - <span style="color: #888; margin-left: 4px;">(${product.rating})</span> 1043 + productGrid.innerHTML = filtered.map(product => { 1044 + const swatch = getSwatchStyle(product.color); 1045 + const label = categoryLabels[product.category] || product.category; 1046 + const fullStars = Math.floor(product.rating); 1047 + const stars = '★'.repeat(fullStars) + '☆'.repeat(5 - fullStars); 1048 + return ` 1049 + <div class="product-card" data-id="${product.id}"> 1050 + <div class="product-image"> 1051 + <div class="product-swatch" style="background:${swatch.bg}"></div> 1052 + <span class="product-label">${label}</span> 1053 + </div> 1054 + <div class="product-info"> 1055 + <div class="product-name">${product.name}</div> 1056 + <div class="product-color">${product.color}</div> 1057 + <div class="product-bottom"> 1058 + <div class="product-price">$${product.price.toFixed(2)}</div> 1059 + <div class="product-rating"> 1060 + <span class="stars">${stars}</span> 1061 + <span>${product.rating}</span> 1062 + </div> 1063 + </div> 725 1064 </div> 726 1065 </div> 727 - </div> 728 - `).join(''); 1066 + `; 1067 + }).join(''); 729 1068 730 - // Add click listeners 731 1069 document.querySelectorAll('.product-card').forEach(card => { 732 1070 card.addEventListener('click', () => openProductModal(parseInt(card.dataset.id))); 733 1071 }); 734 1072 } 735 1073 736 - // Open product modal 737 1074 function openProductModal(productId) { 738 1075 selectedProduct = products.find(p => p.id === productId); 739 1076 if (!selectedProduct) return; 740 1077 741 - document.getElementById('modal-image').textContent = selectedProduct.emoji; 742 - document.getElementById('modal-name').textContent = `${selectedProduct.name}`; 1078 + const swatch = getSwatchStyle(selectedProduct.color); 1079 + const label = categoryLabels[selectedProduct.category] || selectedProduct.category; 1080 + const modalImage = document.getElementById('modal-image'); 1081 + modalImage.innerHTML = ` 1082 + <div class="product-swatch" style="background:${swatch.bg}"></div> 1083 + <span class="product-label">${label}</span> 1084 + `; 1085 + document.getElementById('modal-name').textContent = selectedProduct.name; 743 1086 document.getElementById('modal-color').textContent = selectedProduct.color; 744 1087 document.getElementById('modal-price').textContent = `$${selectedProduct.price.toFixed(2)}`; 745 1088 746 1089 productModal.classList.add('active'); 747 1090 } 748 1091 749 - // Close modal 750 1092 document.getElementById('modal-close').addEventListener('click', () => { 751 1093 productModal.classList.remove('active'); 752 1094 }); ··· 757 1099 } 758 1100 }); 759 1101 760 - // Size selection 761 1102 document.querySelectorAll('.size-option').forEach(option => { 762 1103 option.addEventListener('click', () => { 763 1104 document.querySelectorAll('.size-option').forEach(o => o.classList.remove('selected')); ··· 766 1107 }); 767 1108 }); 768 1109 769 - // Add to cart 770 1110 document.getElementById('add-to-cart').addEventListener('click', () => { 771 1111 if (!selectedProduct) return; 772 1112 ··· 789 1129 openCart(); 790 1130 }); 791 1131 792 - // Update cart display 793 1132 function updateCart() { 794 1133 const totalItems = cart.reduce((sum, item) => sum + item.quantity, 0); 795 1134 cartCount.textContent = totalItems; 796 1135 797 1136 if (cart.length === 0) { 798 - cartItems.innerHTML = '<div class="cart-empty">Your cart is empty</div>'; 1137 + cartItems.innerHTML = '<div class="cart-empty">Your bag is empty</div>'; 799 1138 } else { 800 - cartItems.innerHTML = cart.map((item, index) => ` 801 - <div class="cart-item"> 802 - <div class="cart-item-image">${item.product.emoji}</div> 803 - <div class="cart-item-info"> 804 - <div class="cart-item-name">${item.product.name}</div> 805 - <div class="cart-item-details">${item.product.color} · Size ${item.size} · Qty ${item.quantity}</div> 806 - <div class="cart-item-price">$${(item.product.price * item.quantity).toFixed(2)}</div> 1139 + cartItems.innerHTML = cart.map((item, index) => { 1140 + const swatch = getSwatchStyle(item.product.color); 1141 + return ` 1142 + <div class="cart-item"> 1143 + <div class="cart-item-image"> 1144 + <div class="product-swatch" style="background:${swatch.bg}"></div> 1145 + <span>${(categoryLabels[item.product.category] || '').substring(0, 3)}</span> 1146 + </div> 1147 + <div class="cart-item-info"> 1148 + <div class="cart-item-name">${item.product.name}</div> 1149 + <div class="cart-item-details">${item.product.color} · ${item.size} · Qty ${item.quantity}</div> 1150 + <div class="cart-item-price">$${(item.product.price * item.quantity).toFixed(2)}</div> 1151 + </div> 1152 + <button class="cart-item-remove" data-index="${index}">Remove</button> 807 1153 </div> 808 - <button class="cart-item-remove" data-index="${index}">Remove</button> 809 - </div> 810 - `).join(''); 1154 + `; 1155 + }).join(''); 811 1156 812 - // Add remove listeners 813 1157 document.querySelectorAll('.cart-item-remove').forEach(btn => { 814 1158 btn.addEventListener('click', () => { 815 1159 cart.splice(parseInt(btn.dataset.index), 1); ··· 822 1166 cartTotal.textContent = `$${total.toFixed(2)}`; 823 1167 } 824 1168 825 - // Cart sidebar 826 1169 function openCart() { 827 1170 cartSidebar.classList.add('open'); 828 1171 cartOverlay.classList.add('active'); ··· 837 1180 document.getElementById('cart-close').addEventListener('click', closeCart); 838 1181 cartOverlay.addEventListener('click', closeCart); 839 1182 840 - // Filter tabs 841 1183 document.querySelectorAll('.filter-tab').forEach(tab => { 842 1184 tab.addEventListener('click', () => { 843 1185 document.querySelectorAll('.filter-tab').forEach(t => t.classList.remove('active')); ··· 847 1189 }); 848 1190 }); 849 1191 850 - // Checkout 851 1192 document.getElementById('checkout-btn').addEventListener('click', () => { 852 1193 if (cart.length === 0) { 853 - alert('Your cart is empty!'); 1194 + alert('Your bag is empty!'); 854 1195 return; 855 1196 } 856 1197 closeCart(); 857 1198 checkoutSuccess.classList.add('active'); 858 - 859 1199 cart = []; 860 1200 updateCart(); 861 1201 }); ··· 864 1204 checkoutSuccess.classList.remove('active'); 865 1205 }); 866 1206 867 - // Initialize 868 1207 renderProducts(); 869 1208 </script> 870 1209 </body>