templates/viewer.html
20.9 KB| 1 | html |
| 2 | <!-- |
| 3 | THIS IS A TEMPLATE THAT SHOULD BE USED EVERY TIME AND MODIFIED. |
| 4 | WHAT TO KEEP: |
| 5 | ✓ Overall structure (header, sidebar, main content) |
| 6 | ✓ Anthropic branding (colors, fonts, layout) |
| 7 | ✓ Seed navigation section (always include this) |
| 8 | ✓ Self-contained artifact (everything inline) |
| 9 | |
| 10 | WHAT TO CREATIVELY EDIT: |
| 11 | ✗ The p5.js algorithm (implement YOUR vision) |
| 12 | ✗ The parameters (define what YOUR art needs) |
| 13 | ✗ The UI controls (match YOUR parameters) |
| 14 | |
| 15 | Let your philosophy guide the implementation. |
| 16 | The world is your oyster - be creative! |
| 17 | --> |
| 18 | <html lang="en"> |
| 19 | <head> |
| 20 | <meta charset="UTF-8"> |
| 21 | <meta name="viewport" content="width=device-width, initial-scale=1.0"> |
| 22 | <title>Generative Art Viewer</title> |
| 23 | <script src="https://cdnjs.cloudflare.com/ajax/libs/p5.js/1.7.0/p5.min.js"></script> |
| 24 | <link rel="preconnect" href="https://fonts.googleapis.com"> |
| 25 | <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> |
| 26 | <link href="https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600&family=Lora:wght@400;500&display=swap" rel="stylesheet"> |
| 27 | <style> |
| 28 | /* Anthropic Brand Colors */ |
| 29 | :root { |
| 30 | --anthropic-dark: #141413; |
| 31 | --anthropic-light: #faf9f5; |
| 32 | --anthropic-mid-gray: #b0aea5; |
| 33 | --anthropic-light-gray: #e8e6dc; |
| 34 | --anthropic-orange: #d97757; |
| 35 | --anthropic-blue: #6a9bcc; |
| 36 | --anthropic-green: #788c5d; |
| 37 | } |
| 38 | |
| 39 | * { |
| 40 | margin: 0; |
| 41 | padding: 0; |
| 42 | box-sizing: border-box; |
| 43 | } |
| 44 | |
| 45 | body { |
| 46 | font-family: 'Poppins', sans-serif; |
| 47 | background: linear-gradient(135deg, var(--anthropic-light) 0%, #f5f3ee 100%); |
| 48 | min-height: 100vh; |
| 49 | color: var(--anthropic-dark); |
| 50 | } |
| 51 | |
| 52 | .container { |
| 53 | display: flex; |
| 54 | min-height: 100vh; |
| 55 | padding: 20px; |
| 56 | gap: 20px; |
| 57 | } |
| 58 | |
| 59 | /* Sidebar */ |
| 60 | .sidebar { |
| 61 | width: 320px; |
| 62 | flex-shrink: 0; |
| 63 | background: rgba(255, 255, 255, 0.95); |
| 64 | backdrop-filter: blur(10px); |
| 65 | padding: 24px; |
| 66 | border-radius: 12px; |
| 67 | box-shadow: 0 10px 30px rgba(20, 20, 19, 0.1); |
| 68 | overflow-y: auto; |
| 69 | overflow-x: hidden; |
| 70 | } |
| 71 | |
| 72 | .sidebar h1 { |
| 73 | font-family: 'Lora', serif; |
| 74 | font-size: 24px; |
| 75 | font-weight: 500; |
| 76 | color: var(--anthropic-dark); |
| 77 | margin-bottom: 8px; |
| 78 | } |
| 79 | |
| 80 | .sidebar .subtitle { |
| 81 | color: var(--anthropic-mid-gray); |
| 82 | font-size: 14px; |
| 83 | margin-bottom: 32px; |
| 84 | line-height: 1.4; |
| 85 | } |
| 86 | |
| 87 | /* Control Sections */ |
| 88 | .control-section { |
| 89 | margin-bottom: 32px; |
| 90 | } |
| 91 | |
| 92 | .control-section h3 { |
| 93 | font-size: 16px; |
| 94 | font-weight: 600; |
| 95 | color: var(--anthropic-dark); |
| 96 | margin-bottom: 16px; |
| 97 | display: flex; |
| 98 | align-items: center; |
| 99 | gap: 8px; |
| 100 | } |
| 101 | |
| 102 | .control-section h3::before { |
| 103 | content: '•'; |
| 104 | color: var(--anthropic-orange); |
| 105 | font-weight: bold; |
| 106 | } |
| 107 | |
| 108 | /* Seed Controls */ |
| 109 | .seed-input { |
| 110 | width: 100%; |
| 111 | background: var(--anthropic-light); |
| 112 | padding: 12px; |
| 113 | border-radius: 8px; |
| 114 | font-family: 'Courier New', monospace; |
| 115 | font-size: 14px; |
| 116 | margin-bottom: 12px; |
| 117 | border: 1px solid var(--anthropic-light-gray); |
| 118 | text-align: center; |
| 119 | } |
| 120 | |
| 121 | .seed-input:focus { |
| 122 | outline: none; |
| 123 | border-color: var(--anthropic-orange); |
| 124 | box-shadow: 0 0 0 2px rgba(217, 119, 87, 0.1); |
| 125 | background: white; |
| 126 | } |
| 127 | |
| 128 | .seed-controls { |
| 129 | display: grid; |
| 130 | grid-template-columns: 1fr 1fr; |
| 131 | gap: 8px; |
| 132 | margin-bottom: 8px; |
| 133 | } |
| 134 | |
| 135 | .regen-button { |
| 136 | margin-bottom: 0; |
| 137 | } |
| 138 | |
| 139 | /* Parameter Controls */ |
| 140 | .control-group { |
| 141 | margin-bottom: 20px; |
| 142 | } |
| 143 | |
| 144 | .control-group label { |
| 145 | display: block; |
| 146 | font-size: 14px; |
| 147 | font-weight: 500; |
| 148 | color: var(--anthropic-dark); |
| 149 | margin-bottom: 8px; |
| 150 | } |
| 151 | |
| 152 | .slider-container { |
| 153 | display: flex; |
| 154 | align-items: center; |
| 155 | gap: 12px; |
| 156 | } |
| 157 | |
| 158 | .slider-container input[type="range"] { |
| 159 | flex: 1; |
| 160 | height: 4px; |
| 161 | background: var(--anthropic-light-gray); |
| 162 | border-radius: 2px; |
| 163 | outline: none; |
| 164 | -webkit-appearance: none; |
| 165 | } |
| 166 | |
| 167 | .slider-container input[type="range"]::-webkit-slider-thumb { |
| 168 | -webkit-appearance: none; |
| 169 | width: 16px; |
| 170 | height: 16px; |
| 171 | background: var(--anthropic-orange); |
| 172 | border-radius: 50%; |
| 173 | cursor: pointer; |
| 174 | transition: all 0.2s ease; |
| 175 | } |
| 176 | |
| 177 | .slider-container input[type="range"]::-webkit-slider-thumb:hover { |
| 178 | transform: scale(1.1); |
| 179 | background: #c86641; |
| 180 | } |
| 181 | |
| 182 | .slider-container input[type="range"]::-moz-range-thumb { |
| 183 | width: 16px; |
| 184 | height: 16px; |
| 185 | background: var(--anthropic-orange); |
| 186 | border-radius: 50%; |
| 187 | border: none; |
| 188 | cursor: pointer; |
| 189 | transition: all 0.2s ease; |
| 190 | } |
| 191 | |
| 192 | .value-display { |
| 193 | font-family: 'Courier New', monospace; |
| 194 | font-size: 12px; |
| 195 | color: var(--anthropic-mid-gray); |
| 196 | min-width: 60px; |
| 197 | text-align: right; |
| 198 | } |
| 199 | |
| 200 | /* Color Pickers */ |
| 201 | .color-group { |
| 202 | margin-bottom: 16px; |
| 203 | } |
| 204 | |
| 205 | .color-group label { |
| 206 | display: block; |
| 207 | font-size: 12px; |
| 208 | color: var(--anthropic-mid-gray); |
| 209 | margin-bottom: 4px; |
| 210 | } |
| 211 | |
| 212 | .color-picker-container { |
| 213 | display: flex; |
| 214 | align-items: center; |
| 215 | gap: 8px; |
| 216 | } |
| 217 | |
| 218 | .color-picker-container input[type="color"] { |
| 219 | width: 32px; |
| 220 | height: 32px; |
| 221 | border: none; |
| 222 | border-radius: 6px; |
| 223 | cursor: pointer; |
| 224 | background: none; |
| 225 | padding: 0; |
| 226 | } |
| 227 | |
| 228 | .color-value { |
| 229 | font-family: 'Courier New', monospace; |
| 230 | font-size: 12px; |
| 231 | color: var(--anthropic-mid-gray); |
| 232 | } |
| 233 | |
| 234 | /* Buttons */ |
| 235 | .button { |
| 236 | background: var(--anthropic-orange); |
| 237 | color: white; |
| 238 | border: none; |
| 239 | padding: 10px 16px; |
| 240 | border-radius: 6px; |
| 241 | font-size: 14px; |
| 242 | font-weight: 500; |
| 243 | cursor: pointer; |
| 244 | transition: all 0.2s ease; |
| 245 | width: 100%; |
| 246 | } |
| 247 | |
| 248 | .button:hover { |
| 249 | background: #c86641; |
| 250 | transform: translateY(-1px); |
| 251 | } |
| 252 | |
| 253 | .button:active { |
| 254 | transform: translateY(0); |
| 255 | } |
| 256 | |
| 257 | .button.secondary { |
| 258 | background: var(--anthropic-blue); |
| 259 | } |
| 260 | |
| 261 | .button.secondary:hover { |
| 262 | background: #5a8bb8; |
| 263 | } |
| 264 | |
| 265 | .button.tertiary { |
| 266 | background: var(--anthropic-green); |
| 267 | } |
| 268 | |
| 269 | .button.tertiary:hover { |
| 270 | background: #6b7b52; |
| 271 | } |
| 272 | |
| 273 | .button-row { |
| 274 | display: flex; |
| 275 | gap: 8px; |
| 276 | } |
| 277 | |
| 278 | .button-row .button { |
| 279 | flex: 1; |
| 280 | } |
| 281 | |
| 282 | /* Canvas Area */ |
| 283 | .canvas-area { |
| 284 | flex: 1; |
| 285 | display: flex; |
| 286 | align-items: center; |
| 287 | justify-content: center; |
| 288 | min-width: 0; |
| 289 | } |
| 290 | |
| 291 | #canvas-container { |
| 292 | width: 100%; |
| 293 | max-width: 1000px; |
| 294 | border-radius: 12px; |
| 295 | overflow: hidden; |
| 296 | box-shadow: 0 20px 40px rgba(20, 20, 19, 0.1); |
| 297 | background: white; |
| 298 | } |
| 299 | |
| 300 | #canvas-container canvas { |
| 301 | display: block; |
| 302 | width: 100% ; |
| 303 | height: auto ; |
| 304 | } |
| 305 | |
| 306 | /* Loading State */ |
| 307 | .loading { |
| 308 | display: flex; |
| 309 | align-items: center; |
| 310 | justify-content: center; |
| 311 | font-size: 18px; |
| 312 | color: var(--anthropic-mid-gray); |
| 313 | } |
| 314 | |
| 315 | /* Responsive - Stack on mobile */ |
| 316 | @media (max-width: 600px) { |
| 317 | .container { |
| 318 | flex-direction: column; |
| 319 | } |
| 320 | |
| 321 | .sidebar { |
| 322 | width: 100%; |
| 323 | } |
| 324 | |
| 325 | .canvas-area { |
| 326 | padding: 20px; |
| 327 | } |
| 328 | } |
| 329 | </style> |
| 330 | </head> |
| 331 | <body> |
| 332 | <div class="container"> |
| 333 | <!-- Control Sidebar --> |
| 334 | <div class="sidebar"> |
| 335 | <!-- Headers (CUSTOMIZE THIS FOR YOUR ART) --> |
| 336 | <h1>TITLE - EDIT</h1> |
| 337 | <div class="subtitle">SUBHEADER - EDIT</div> |
| 338 | |
| 339 | <!-- Seed Section (ALWAYS KEEP THIS) --> |
| 340 | <div class="control-section"> |
| 341 | <h3>Seed</h3> |
| 342 | <input type="number" id="seed-input" class="seed-input" value="12345" onchange="updateSeed()"> |
| 343 | <div class="seed-controls"> |
| 344 | <button class="button secondary" onclick="previousSeed()">← Prev</button> |
| 345 | <button class="button secondary" onclick="nextSeed()">Next →</button> |
| 346 | </div> |
| 347 | <button class="button tertiary regen-button" onclick="randomSeedAndUpdate()">↻ Random</button> |
| 348 | </div> |
| 349 | |
| 350 | <!-- Parameters Section (CUSTOMIZE THIS FOR YOUR ART) --> |
| 351 | <div class="control-section"> |
| 352 | <h3>Parameters</h3> |
| 353 | |
| 354 | <!-- Particle Count --> |
| 355 | <div class="control-group"> |
| 356 | <label>Particle Count</label> |
| 357 | <div class="slider-container"> |
| 358 | <input type="range" id="particleCount" min="1000" max="10000" step="500" value="5000" oninput="updateParam('particleCount', this.value)"> |
| 359 | <span class="value-display" id="particleCount-value">5000</span> |
| 360 | </div> |
| 361 | </div> |
| 362 | |
| 363 | <!-- Flow Speed --> |
| 364 | <div class="control-group"> |
| 365 | <label>Flow Speed</label> |
| 366 | <div class="slider-container"> |
| 367 | <input type="range" id="flowSpeed" min="0.1" max="2.0" step="0.1" value="0.5" oninput="updateParam('flowSpeed', this.value)"> |
| 368 | <span class="value-display" id="flowSpeed-value">0.5</span> |
| 369 | </div> |
| 370 | </div> |
| 371 | |
| 372 | <!-- Noise Scale --> |
| 373 | <div class="control-group"> |
| 374 | <label>Noise Scale</label> |
| 375 | <div class="slider-container"> |
| 376 | <input type="range" id="noiseScale" min="0.001" max="0.02" step="0.001" value="0.005" oninput="updateParam('noiseScale', this.value)"> |
| 377 | <span class="value-display" id="noiseScale-value">0.005</span> |
| 378 | </div> |
| 379 | </div> |
| 380 | |
| 381 | <!-- Trail Length --> |
| 382 | <div class="control-group"> |
| 383 | <label>Trail Length</label> |
| 384 | <div class="slider-container"> |
| 385 | <input type="range" id="trailLength" min="2" max="20" step="1" value="8" oninput="updateParam('trailLength', this.value)"> |
| 386 | <span class="value-display" id="trailLength-value">8</span> |
| 387 | </div> |
| 388 | </div> |
| 389 | </div> |
| 390 | |
| 391 | <!-- Colors Section (OPTIONAL - CUSTOMIZE OR REMOVE) --> |
| 392 | <div class="control-section"> |
| 393 | <h3>Colors</h3> |
| 394 | |
| 395 | <!-- Color 1 --> |
| 396 | <div class="color-group"> |
| 397 | <label>Primary Color</label> |
| 398 | <div class="color-picker-container"> |
| 399 | <input type="color" id="color1" value="#d97757" onchange="updateColor('color1', this.value)"> |
| 400 | <span class="color-value" id="color1-value">#d97757</span> |
| 401 | </div> |
| 402 | </div> |
| 403 | |
| 404 | <!-- Color 2 --> |
| 405 | <div class="color-group"> |
| 406 | <label>Secondary Color</label> |
| 407 | <div class="color-picker-container"> |
| 408 | <input type="color" id="color2" value="#6a9bcc" onchange="updateColor('color2', this.value)"> |
| 409 | <span class="color-value" id="color2-value">#6a9bcc</span> |
| 410 | </div> |
| 411 | </div> |
| 412 | |
| 413 | <!-- Color 3 --> |
| 414 | <div class="color-group"> |
| 415 | <label>Accent Color</label> |
| 416 | <div class="color-picker-container"> |
| 417 | <input type="color" id="color3" value="#788c5d" onchange="updateColor('color3', this.value)"> |
| 418 | <span class="color-value" id="color3-value">#788c5d</span> |
| 419 | </div> |
| 420 | </div> |
| 421 | </div> |
| 422 | |
| 423 | <!-- Actions Section (ALWAYS KEEP THIS) --> |
| 424 | <div class="control-section"> |
| 425 | <h3>Actions</h3> |
| 426 | <div class="button-row"> |
| 427 | <button class="button" onclick="resetParameters()">Reset</button> |
| 428 | </div> |
| 429 | </div> |
| 430 | </div> |
| 431 | |
| 432 | <!-- Main Canvas Area --> |
| 433 | <div class="canvas-area"> |
| 434 | <div id="canvas-container"> |
| 435 | <div class="loading">Initializing generative art...</div> |
| 436 | </div> |
| 437 | </div> |
| 438 | </div> |
| 439 | |
| 440 | <script> |
| 441 | // ═══════════════════════════════════════════════════════════════════════ |
| 442 | // GENERATIVE ART PARAMETERS - CUSTOMIZE FOR YOUR ALGORITHM |
| 443 | // ═══════════════════════════════════════════════════════════════════════ |
| 444 | |
| 445 | let params = { |
| 446 | seed: 12345, |
| 447 | particleCount: 5000, |
| 448 | flowSpeed: 0.5, |
| 449 | noiseScale: 0.005, |
| 450 | trailLength: 8, |
| 451 | colorPalette: ['#d97757', '#6a9bcc', '#788c5d'] |
| 452 | }; |
| 453 | |
| 454 | let defaultParams = {...params}; // Store defaults for reset |
| 455 | |
| 456 | // ═══════════════════════════════════════════════════════════════════════ |
| 457 | // P5.JS GENERATIVE ART ALGORITHM - REPLACE WITH YOUR VISION |
| 458 | // ═══════════════════════════════════════════════════════════════════════ |
| 459 | |
| 460 | let particles = []; |
| 461 | let flowField = []; |
| 462 | let cols, rows; |
| 463 | let scl = 10; // Flow field resolution |
| 464 | |
| 465 | function setup() { |
| 466 | let canvas = createCanvas(1200, 1200); |
| 467 | canvas.parent('canvas-container'); |
| 468 | |
| 469 | initializeSystem(); |
| 470 | |
| 471 | // Remove loading message |
| 472 | document.querySelector('.loading').style.display = 'none'; |
| 473 | } |
| 474 | |
| 475 | function initializeSystem() { |
| 476 | // Seed the randomness for reproducibility |
| 477 | randomSeed(params.seed); |
| 478 | noiseSeed(params.seed); |
| 479 | |
| 480 | // Clear particles and recreate |
| 481 | particles = []; |
| 482 | |
| 483 | // Initialize particles |
| 484 | for (let i = 0; i < params.particleCount; i++) { |
| 485 | particles.push(new Particle()); |
| 486 | } |
| 487 | |
| 488 | // Calculate flow field dimensions |
| 489 | cols = floor(width / scl); |
| 490 | rows = floor(height / scl); |
| 491 | |
| 492 | // Generate flow field |
| 493 | generateFlowField(); |
| 494 | |
| 495 | // Clear background |
| 496 | background(250, 249, 245); // Anthropic light background |
| 497 | } |
| 498 | |
| 499 | function generateFlowField() { |
| 500 | // fill this in |
| 501 | } |
| 502 | |
| 503 | function draw() { |
| 504 | // fill this in |
| 505 | } |
| 506 | |
| 507 | // ═══════════════════════════════════════════════════════════════════════ |
| 508 | // PARTICLE SYSTEM - CUSTOMIZE FOR YOUR ALGORITHM |
| 509 | // ═══════════════════════════════════════════════════════════════════════ |
| 510 | |
| 511 | class Particle { |
| 512 | constructor() { |
| 513 | // fill this in |
| 514 | } |
| 515 | // fill this in |
| 516 | } |
| 517 | |
| 518 | // ═══════════════════════════════════════════════════════════════════════ |
| 519 | // UI CONTROL HANDLERS - CUSTOMIZE FOR YOUR PARAMETERS |
| 520 | // ═══════════════════════════════════════════════════════════════════════ |
| 521 | |
| 522 | function updateParam(paramName, value) { |
| 523 | // fill this in |
| 524 | } |
| 525 | |
| 526 | function updateColor(colorId, value) { |
| 527 | // fill this in |
| 528 | } |
| 529 | |
| 530 | // ═══════════════════════════════════════════════════════════════════════ |
| 531 | // SEED CONTROL FUNCTIONS - ALWAYS KEEP THESE |
| 532 | // ═══════════════════════════════════════════════════════════════════════ |
| 533 | |
| 534 | function updateSeedDisplay() { |
| 535 | document.getElementById('seed-input').value = params.seed; |
| 536 | } |
| 537 | |
| 538 | function updateSeed() { |
| 539 | let input = document.getElementById('seed-input'); |
| 540 | let newSeed = parseInt(input.value); |
| 541 | if (newSeed && newSeed > 0) { |
| 542 | params.seed = newSeed; |
| 543 | initializeSystem(); |
| 544 | } else { |
| 545 | // Reset to current seed if invalid |
| 546 | updateSeedDisplay(); |
| 547 | } |
| 548 | } |
| 549 | |
| 550 | function previousSeed() { |
| 551 | params.seed = Math.max(1, params.seed - 1); |
| 552 | updateSeedDisplay(); |
| 553 | initializeSystem(); |
| 554 | } |
| 555 | |
| 556 | function nextSeed() { |
| 557 | params.seed = params.seed + 1; |
| 558 | updateSeedDisplay(); |
| 559 | initializeSystem(); |
| 560 | } |
| 561 | |
| 562 | function randomSeedAndUpdate() { |
| 563 | params.seed = Math.floor(Math.random() * 999999) + 1; |
| 564 | updateSeedDisplay(); |
| 565 | initializeSystem(); |
| 566 | } |
| 567 | |
| 568 | function resetParameters() { |
| 569 | params = {...defaultParams}; |
| 570 | |
| 571 | // Update UI elements |
| 572 | document.getElementById('particleCount').value = params.particleCount; |
| 573 | document.getElementById('particleCount-value').textContent = params.particleCount; |
| 574 | document.getElementById('flowSpeed').value = params.flowSpeed; |
| 575 | document.getElementById('flowSpeed-value').textContent = params.flowSpeed; |
| 576 | document.getElementById('noiseScale').value = params.noiseScale; |
| 577 | document.getElementById('noiseScale-value').textContent = params.noiseScale; |
| 578 | document.getElementById('trailLength').value = params.trailLength; |
| 579 | document.getElementById('trailLength-value').textContent = params.trailLength; |
| 580 | |
| 581 | // Reset colors |
| 582 | document.getElementById('color1').value = params.colorPalette[0]; |
| 583 | document.getElementById('color1-value').textContent = params.colorPalette[0]; |
| 584 | document.getElementById('color2').value = params.colorPalette[1]; |
| 585 | document.getElementById('color2-value').textContent = params.colorPalette[1]; |
| 586 | document.getElementById('color3').value = params.colorPalette[2]; |
| 587 | document.getElementById('color3-value').textContent = params.colorPalette[2]; |
| 588 | |
| 589 | updateSeedDisplay(); |
| 590 | initializeSystem(); |
| 591 | } |
| 592 | |
| 593 | // Initialize UI on load |
| 594 | window.addEventListener('load', function() { |
| 595 | updateSeedDisplay(); |
| 596 | }); |
| 597 | </script> |
| 598 | </body> |
| 599 | </html> |
文件内容是平台检查时读出的文本,只供查看。平台不会运行包里的任何脚本或程序。

