Paradox/TBLWebappBotPublic · Community Store Listing
Webapp Demo Bot
7 commands0 envUpdated 1mo agoCreated Dec 13, 2025
commands/test.html TWallpaper.js
javascript · 1043 lines · click line # to share
1/**#command2name: test.html TWallpaper.js3answer: 4keyboard: 5parse_mode: 6aliases: 7allow_only_group: false8need_reply: false9is_web: 010#command**/11 12<!DOCTYPE html>13<html lang="en">14<head>15 <meta charset="UTF-8" />16 <meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />17 <title>TWallpaper Demo</title>18 19 <script src="https://telegram.org/js/telegram-web-app.js"></script>20 <script src="https://unpkg.com/twallpaper@latest/dist/index.umd.js"></script>21 22 <style>23 :root {24 --panel-radius: 20px;25 --panel-bg: rgba(18, 18, 22, 0.92);26 --panel-border: rgba(255,255,255,0.08);27 --accent: #5bb8f5;28 --accent2: #4ecca3;29 --text: #f0f0f0;30 --text-muted: rgba(240,240,240,0.45);31 --section-bg: rgba(255,255,255,0.05);32 --toggle-off: rgba(255,255,255,0.15);33 --toggle-on: var(--accent);34 --slider-track: rgba(255,255,255,0.12);35 --font: 'SF Pro Display', -apple-system, 'Segoe UI', sans-serif;36 --safe-top: env(safe-area-inset-top, 0px);37 --safe-bottom: env(safe-area-inset-bottom, 0px);38 --safe-left: env(safe-area-inset-left, 0px);39 --safe-right: env(safe-area-inset-right, 0px);40 }41 42 * { margin: 0; padding: 0; box-sizing: border-box; -webkit-tap-highlight-color: transparent; }43 44 html, body {45 width: 100%; height: 100%;46 overflow: hidden;47 font-family: var(--font);48 background: #000;49 }50 51 /* ── WALLPAPER ── */52 #tw-wrap {53 position: fixed;54 inset: 0;55 width: 100%; height: 100%;56 }57 58 .tw-wrap { position: fixed; inset: 0; width: 100%; height: 100%; }59 .tw-wrap canvas {60 display: block;61 width: 100% !important;62 height: 100% !important;63 }64 .tw-pattern {65 position: absolute; inset: 0;66 width: 100%; height: 100%;67 pointer-events: none;68 background-image: var(--tw-image);69 background-size: var(--tw-size);70 background-repeat: repeat;71 opacity: var(--tw-opacity);72 background-color: var(--tw-background);73 filter: blur(var(--tw-blur));74 z-index: 1;75 }76 .tw-mask {77 -webkit-mask-image: var(--tw-image);78 mask-image: var(--tw-image);79 -webkit-mask-size: var(--tw-size);80 mask-size: var(--tw-size);81 -webkit-mask-repeat: repeat;82 mask-repeat: repeat;83 background-color: var(--tw-background) !important;84 background-image: none !important;85 }86 87 /* ── FAB TOGGLE BUTTON ── */88 #fab {89 position: fixed;90 bottom: calc(24px + var(--safe-bottom));91 right: calc(18px + var(--safe-right));92 z-index: 100;93 width: 52px; height: 52px;94 border-radius: 50%;95 background: rgba(15,15,20,0.85);96 backdrop-filter: blur(16px);97 border: 1px solid rgba(255,255,255,0.15);98 display: flex; align-items: center; justify-content: center;99 cursor: pointer;100 transition: transform 0.2s ease, box-shadow 0.2s ease;101 box-shadow: 0 4px 24px rgba(0,0,0,0.4);102 }103 #fab:active { transform: scale(0.92); }104 #fab.open { background: rgba(91,184,245,0.2); border-color: var(--accent); }105 #fab svg { transition: transform 0.3s ease; }106 #fab.open svg { transform: rotate(45deg); }107 108 /* ── PANEL BACKDROP ── */109 #backdrop {110 position: fixed; inset: 0;111 z-index: 98;112 background: rgba(0,0,0,0);113 pointer-events: none;114 transition: background 0.3s ease;115 }116 #backdrop.visible {117 background: rgba(0,0,0,0.35);118 pointer-events: all;119 }120 121 /* ── SLIDE-UP PANEL ── */122 #panel {123 position: fixed;124 left: 0; right: 0;125 bottom: 0;126 z-index: 99;127 max-height: 82vh;128 background: var(--panel-bg);129 backdrop-filter: blur(32px) saturate(1.5);130 -webkit-backdrop-filter: blur(32px) saturate(1.5);131 border-top: 1px solid var(--panel-border);132 border-radius: var(--panel-radius) var(--panel-radius) 0 0;133 transform: translateY(100%);134 transition: transform 0.38s cubic-bezier(0.32, 0.72, 0, 1);135 display: flex;136 flex-direction: column;137 overflow: hidden;138 }139 #panel.open { transform: translateY(0); }140 141 /* panel handle */142 .panel-handle {143 flex-shrink: 0;144 display: flex; justify-content: center;145 padding: 12px 0 4px;146 cursor: grab;147 }148 .panel-handle-bar {149 width: 36px; height: 4px;150 border-radius: 2px;151 background: rgba(255,255,255,0.2);152 }153 154 .panel-title {155 flex-shrink: 0;156 padding: 4px 20px 12px;157 font-size: 16px;158 font-weight: 700;159 color: var(--text);160 letter-spacing: -0.3px;161 display: flex; align-items: center; gap: 8px;162 }163 .panel-title span { font-size: 18px; }164 165 /* scrollable body */166 #panel-body {167 flex: 1;168 overflow-y: auto;169 overflow-x: hidden;170 padding: 0 16px calc(16px + var(--safe-bottom));171 -webkit-overflow-scrolling: touch;172 scrollbar-width: none;173 }174 #panel-body::-webkit-scrollbar { display: none; }175 176 /* ── SECTIONS ── */177 .section {178 background: var(--section-bg);179 border-radius: 14px;180 padding: 14px;181 margin-bottom: 10px;182 }183 .section-label {184 font-size: 10px;185 font-weight: 700;186 letter-spacing: 0.8px;187 text-transform: uppercase;188 color: var(--text-muted);189 margin-bottom: 12px;190 }191 192 /* ── COLORS ── */193 .colors-row {194 display: flex; gap: 8px; align-items: center;195 margin-bottom: 12px;196 }197 .color-slot {198 position: relative;199 flex: 1;200 }201 .color-swatch {202 width: 100%;203 aspect-ratio: 1;204 border-radius: 10px;205 border: 2px solid rgba(255,255,255,0.15);206 cursor: pointer;207 transition: transform 0.15s, border-color 0.15s;208 position: relative;209 overflow: hidden;210 }211 .color-swatch:active { transform: scale(0.92); }212 .color-swatch input[type="color"] {213 position: absolute; inset: 0;214 width: 100%; height: 100%;215 opacity: 0;216 cursor: pointer;217 border: none;218 padding: 0;219 }220 221 .hex-row {222 display: flex; gap: 6px;223 margin-bottom: 10px;224 }225 .hex-input {226 flex: 1;227 background: rgba(255,255,255,0.07);228 border: 1px solid rgba(255,255,255,0.1);229 border-radius: 8px;230 padding: 6px 8px;231 color: var(--text);232 font-size: 11px;233 font-family: 'SF Mono', 'Fira Code', monospace;234 text-align: center;235 outline: none;236 transition: border-color 0.2s;237 min-width: 0;238 }239 .hex-input:focus { border-color: var(--accent); }240 .hex-input.invalid { border-color: #ff5555 !important; }241 242 /* preset palettes */243 .presets-label {244 font-size: 10px;245 font-weight: 600;246 letter-spacing: 0.5px;247 text-transform: uppercase;248 color: var(--text-muted);249 margin-bottom: 8px;250 }251 .presets-row {252 display: flex; gap: 8px; overflow-x: auto;253 scrollbar-width: none;254 padding-bottom: 2px;255 }256 .presets-row::-webkit-scrollbar { display: none; }257 .preset-chip {258 flex-shrink: 0;259 display: flex; flex-direction: column;260 gap: 1px; cursor: pointer;261 border-radius: 10px;262 overflow: hidden;263 border: 2px solid transparent;264 width: 44px;265 transition: border-color 0.15s, transform 0.15s;266 }267 .preset-chip:active { transform: scale(0.92); }268 .preset-chip.active { border-color: var(--accent); }269 .preset-chip-swatch {270 display: grid;271 grid-template-columns: 1fr 1fr;272 width: 44px; height: 44px;273 }274 .preset-chip-swatch div { width: 100%; height: 100%; }275 .preset-name {276 font-size: 8px;277 text-align: center;278 color: var(--text-muted);279 padding: 2px 2px 4px;280 white-space: nowrap;281 overflow: hidden;282 text-overflow: ellipsis;283 background: rgba(0,0,0,0.3);284 }285 286 .btn-random {287 width: 100%;288 margin-top: 10px;289 padding: 9px;290 border-radius: 10px;291 border: 1px solid rgba(255,255,255,0.12);292 background: rgba(255,255,255,0.06);293 color: var(--text);294 font-size: 13px;295 font-weight: 600;296 cursor: pointer;297 display: flex; align-items: center; justify-content: center; gap: 6px;298 transition: background 0.15s, transform 0.15s;299 font-family: var(--font);300 }301 .btn-random:active { transform: scale(0.97); background: rgba(255,255,255,0.1); }302 303 /* ── TOGGLES ── */304 .row-toggle {305 display: flex; align-items: center; justify-content: space-between;306 padding: 4px 0;307 }308 .row-toggle + .row-toggle { margin-top: 10px; }309 .row-label { font-size: 14px; color: var(--text); font-weight: 500; }310 .toggle {311 position: relative;312 width: 44px; height: 26px;313 cursor: pointer;314 }315 .toggle input { display: none; }316 .toggle-track {317 position: absolute; inset: 0;318 border-radius: 13px;319 background: var(--toggle-off);320 transition: background 0.25s;321 }322 .toggle input:checked ~ .toggle-track { background: var(--toggle-on); }323 .toggle-thumb {324 position: absolute;325 top: 3px; left: 3px;326 width: 20px; height: 20px;327 border-radius: 50%;328 background: #fff;329 box-shadow: 0 2px 6px rgba(0,0,0,0.3);330 transition: transform 0.25s cubic-bezier(0.34, 1.56, 0.64, 1);331 }332 .toggle input:checked ~ .toggle-thumb { transform: translateX(18px); }333 334 /* ── SLIDERS ── */335 .slider-row {336 margin-top: 12px;337 }338 .slider-row-header {339 display: flex; justify-content: space-between; align-items: center;340 margin-bottom: 6px;341 }342 .slider-row-label { font-size: 13px; color: var(--text); font-weight: 500; }343 .slider-value {344 font-size: 12px;345 color: var(--accent);346 font-family: 'SF Mono', monospace;347 font-weight: 600;348 min-width: 36px;349 text-align: right;350 }351 input[type="range"] {352 width: 100%;353 height: 4px;354 border-radius: 2px;355 background: var(--slider-track);356 appearance: none;357 -webkit-appearance: none;358 outline: none;359 cursor: pointer;360 }361 input[type="range"]::-webkit-slider-thumb {362 -webkit-appearance: none;363 width: 18px; height: 18px;364 border-radius: 50%;365 background: var(--accent);366 box-shadow: 0 2px 8px rgba(91,184,245,0.4);367 cursor: pointer;368 transition: transform 0.15s;369 }370 input[type="range"]:active::-webkit-slider-thumb { transform: scale(1.2); }371 372 /* ── PATTERNS ── */373 .patterns-grid {374 display: grid;375 grid-template-columns: repeat(4, 1fr);376 gap: 8px;377 margin-top: 4px;378 }379 .pattern-item {380 border-radius: 10px;381 overflow: hidden;382 cursor: pointer;383 border: 2px solid transparent;384 transition: border-color 0.15s, transform 0.15s;385 background: rgba(255,255,255,0.05);386 aspect-ratio: 1;387 display: flex; align-items: center; justify-content: center;388 flex-direction: column;389 gap: 4px;390 position: relative;391 }392 .pattern-item:active { transform: scale(0.93); }393 .pattern-item.active { border-color: var(--accent); }394 .pattern-item.none-item { font-size: 20px; }395 .pattern-item img {396 width: 65%;397 height: 65%;398 object-fit: contain;399 opacity: 0.7;400 filter: invert(1);401 }402 .pattern-item-name {403 font-size: 8px;404 color: var(--text-muted);405 text-align: center;406 padding: 0 4px;407 line-height: 1.2;408 }409 410 /* ── RESET BUTTON ── */411 .btn-reset {412 width: 100%;413 margin-top: 4px;414 margin-bottom: 6px;415 padding: 12px;416 border-radius: 12px;417 border: 1px solid rgba(255, 100, 100, 0.3);418 background: rgba(255, 80, 80, 0.08);419 color: #ff6b6b;420 font-size: 14px;421 font-weight: 600;422 cursor: pointer;423 transition: background 0.15s, transform 0.15s;424 font-family: var(--font);425 display: flex; align-items: center; justify-content: center; gap: 6px;426 }427 .btn-reset:active { transform: scale(0.97); background: rgba(255,80,80,0.15); }428 429 /* color input bg */430 .bg-color-row {431 display: flex; align-items: center; gap: 10px;432 margin-top: 12px;433 }434 .bg-color-swatch {435 width: 32px; height: 32px;436 border-radius: 8px;437 border: 2px solid rgba(255,255,255,0.15);438 cursor: pointer;439 position: relative;440 overflow: hidden;441 flex-shrink: 0;442 }443 .bg-color-swatch input[type="color"] {444 position: absolute; inset: -4px;445 width: calc(100% + 8px); height: calc(100% + 8px);446 opacity: 0; cursor: pointer;447 }448 .bg-color-label { font-size: 13px; color: var(--text); font-weight: 500; }449 .bg-hex-input {450 flex: 1;451 background: rgba(255,255,255,0.07);452 border: 1px solid rgba(255,255,255,0.1);453 border-radius: 8px;454 padding: 6px 10px;455 color: var(--text);456 font-size: 12px;457 font-family: 'SF Mono', monospace;458 outline: none;459 transition: border-color 0.2s;460 }461 .bg-hex-input:focus { border-color: var(--accent); }462 </style>463</head>464<body>465 466<div id="tw-wrap"></div>467 468<!-- FAB toggle -->469<button id="fab" aria-label="Settings">470 <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="rgba(255,255,255,0.9)" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">471 <circle cx="12" cy="12" r="3"/>472 <path d="M19.07 4.93a10 10 0 0 1 0 14.14M4.93 4.93a10 10 0 0 0 0 14.14"/>473 <path d="M12 2v2m0 16v2M2 12h2m16 0h2"/>474 </svg>475</button>476 477<!-- Backdrop -->478<div id="backdrop"></div>479 480<!-- Panel -->481<div id="panel">482 <div class="panel-handle"><div class="panel-handle-bar"></div></div>483 <div class="panel-title"><span>🎨</span> TWallpaper Demo</div>484 485 <div id="panel-body">486 487 <!-- COLORS -->488 <div class="section">489 <div class="section-label">Colors (up to 4)</div>490 <div class="colors-row" id="swatches-row"></div>491 <div class="hex-row" id="hex-row"></div>492 493 <div class="presets-label">Presets</div>494 <div class="presets-row" id="presets-row"></div>495 496 <button class="btn-random" id="btn-random">497 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z"/></svg>498 Random Colors499 </button>500 </div>501 502 <!-- ANIMATION -->503 <div class="section">504 <div class="section-label">Animation</div>505 506 <div class="row-toggle">507 <span class="row-label">Animate</span>508 <label class="toggle">509 <input type="checkbox" id="tog-animate" checked>510 <span class="toggle-track"></span>511 <span class="toggle-thumb"></span>512 </label>513 </div>514 <div class="row-toggle">515 <span class="row-label">Scroll Animate</span>516 <label class="toggle">517 <input type="checkbox" id="tog-scroll">518 <span class="toggle-track"></span>519 <span class="toggle-thumb"></span>520 </label>521 </div>522 523 <div class="slider-row">524 <div class="slider-row-header">525 <span class="slider-row-label">FPS</span>526 <span class="slider-value" id="val-fps">60</span>527 </div>528 <input type="range" id="sl-fps" min="5" max="60" step="5" value="60">529 </div>530 531 <div class="slider-row">532 <div class="slider-row-header">533 <span class="slider-row-label">Tails Speed</span>534 <span class="slider-value" id="val-tails">60</span>535 </div>536 <input type="range" id="sl-tails" min="10" max="150" step="10" value="60">537 </div>538 </div>539 540 <!-- PATTERN -->541 <div class="section">542 <div class="section-label">Pattern</div>543 544 <div class="patterns-grid" id="patterns-grid"></div>545 546 <div class="row-toggle" style="margin-top:14px;">547 <span class="row-label">Mask Mode</span>548 <label class="toggle">549 <input type="checkbox" id="tog-mask">550 <span class="toggle-track"></span>551 <span class="toggle-thumb"></span>552 </label>553 </div>554 555 <div class="slider-row">556 <div class="slider-row-header">557 <span class="slider-row-label">Opacity</span>558 <span class="slider-value" id="val-opacity">0.45</span>559 </div>560 <input type="range" id="sl-opacity" min="0" max="1" step="0.05" value="0.45">561 </div>562 563 <div class="slider-row">564 <div class="slider-row-header">565 <span class="slider-row-label">Size (px)</span>566 <span class="slider-value" id="val-size">300</span>567 </div>568 <input type="range" id="sl-size" min="50" max="600" step="25" value="300">569 </div>570 571 <div class="slider-row">572 <div class="slider-row-header">573 <span class="slider-row-label">Blur</span>574 <span class="slider-value" id="val-blur">0</span>575 </div>576 <input type="range" id="sl-blur" min="0" max="12" step="1" value="0">577 </div>578 579 <div class="bg-color-row">580 <div class="bg-color-swatch" id="bg-swatch" style="background:#000000;">581 <input type="color" id="bg-color-picker" value="#000000">582 </div>583 <span class="bg-color-label">Background</span>584 <input class="bg-hex-input" id="bg-hex" type="text" value="#000000" maxlength="7">585 </div>586 </div>587 588 <!-- RESET -->589 <button class="btn-reset" id="btn-reset">590 <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round"><path d="M3 12a9 9 0 1 0 9-9 9.75 9.75 0 0 0-6.74 2.74L3 8"/><path d="M3 3v5h5"/></svg>591 Reset to Default592 </button>593 594 </div><!-- /panel-body -->595</div><!-- /panel -->596 597<script>598// ── TELEGRAM INIT ──599const tg = window.Telegram?.WebApp;600if (tg) {601 tg.ready();602 tg.expand();603 tg.requestFullscreen();604 tg.disableVerticalSwipes?.();605 // Use TG safe area CSS vars if available (Bot API 8.0+)606 if (tg.safeAreaInset) {607 document.documentElement.style.setProperty('--safe-top', tg.safeAreaInset.top + 'px');608 document.documentElement.style.setProperty('--safe-bottom', tg.safeAreaInset.bottom + 'px');609 document.documentElement.style.setProperty('--safe-left', tg.safeAreaInset.left + 'px');610 document.documentElement.style.setProperty('--safe-right', tg.safeAreaInset.right + 'px');611 }612}613 614// ── DATA ──615const PATTERNS = [616 { name: 'None', file: null },617 { name: 'Banana', file: 'banana.svg' },618 { name: 'Cars', file: 'cars.svg' },619 { name: 'Cats', file: 'cats.svg' },620 { name: 'Christmas', file: 'christmas.svg' },621 { name: 'Ducks', file: 'ducks.svg' },622 { name: 'Fish', file: 'fish.svg' },623 { name: 'Flowers', file: 'flowers.svg' },624 { name: 'Food', file: 'food.svg' },625 { name: 'Games', file: 'games.svg' },626 { name: 'Hearts', file: 'hearts.svg' },627 { name: 'House', file: 'house.svg' },628 { name: 'Leaves', file: 'leaves.svg' },629 { name: 'Money', file: 'money.svg' },630 { name: 'Music', file: 'music.svg' },631 { name: 'Pizza', file: 'pizza.svg' },632 { name: 'Snowflake', file: 'snowflake.svg' },633 { name: 'Stars', file: 'stars.svg' },634 { name: 'Sport', file: 'sport.svg' },635 { name: 'Travel', file: 'travel.svg' },636];637 638const PRESETS = [639 { name: 'TG Green', colors: ['#dbddbb', '#6ba587', '#d5d88d', '#88b884'] },640 { name: 'Ocean', colors: ['#085f63', '#47a8bd', '#a7d5d8', '#e6f2f2'] },641 { name: 'Sunset', colors: ['#ff6b6b', '#ffa07a', '#ffd700', '#ff8c00'] },642 { name: 'Purple', colors: ['#667eea', '#764ba2', '#f093fb', '#c471f5'] },643 { name: 'Night', colors: ['#0f0c29', '#302b63', '#24243e', '#434343'] },644 { name: 'Mint', colors: ['#00b09b', '#96c93d', '#43e97b', '#38f9d7'] },645 { name: 'Rose', colors: ['#f5a7c7', '#f7c5d5', '#f093a0', '#e06c8a'] },646 { name: 'Gold', colors: ['#f7971e', '#ffd200', '#da8e15', '#b8850a'] },647];648 649const DEFAULT_CONFIG = {650 colors: ['#dbddbb', '#6ba587', '#d5d88d', '#88b884'],651 fps: 60,652 tails: 60,653 animate: true,654 scrollAnimate: false,655 pattern: {656 image: null,657 mask: false,658 background: '#000000',659 size: '300px',660 blur: 0,661 opacity: 0.45662 }663};664 665let config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));666 667// ── WALLPAPER INIT ──668const container = document.getElementById('tw-wrap');669const { TWallpaper } = window.TWallpaper;670const wallpaper = new TWallpaper(container, {671 colors: config.colors,672 fps: config.fps,673 tails: config.tails,674 animate: config.animate,675 scrollAnimate: config.scrollAnimate,676 pattern: config.pattern.image ? config.pattern : undefined677});678wallpaper.init();679 680// ── BUILD COLOR SWATCHES ──681const swatchesRow = document.getElementById('swatches-row');682const hexRow = document.getElementById('hex-row');683 684config.colors.forEach((col, i) => {685 // swatch686 const slot = document.createElement('div');687 slot.className = 'color-slot';688 const swatch = document.createElement('div');689 swatch.className = 'color-swatch';690 swatch.style.background = col;691 swatch.id = `swatch-${i}`;692 const picker = document.createElement('input');693 picker.type = 'color';694 picker.value = col;695 picker.addEventListener('input', e => setColor(i, e.target.value));696 swatch.appendChild(picker);697 slot.appendChild(swatch);698 swatchesRow.appendChild(slot);699 700 // hex input701 const hex = document.createElement('input');702 hex.className = 'hex-input';703 hex.type = 'text';704 hex.value = col;705 hex.maxLength = 7;706 hex.placeholder = '#rrggbb';707 hex.id = `hex-${i}`;708 hex.addEventListener('input', e => {709 let v = e.target.value.trim();710 if (!v.startsWith('#')) v = '#' + v;711 if (/^#[0-9a-fA-F]{6}$/.test(v)) {712 hex.classList.remove('invalid');713 setColor(i, v, false);714 } else {715 hex.classList.add('invalid');716 }717 });718 hex.addEventListener('blur', e => {719 let v = e.target.value.trim();720 if (!v.startsWith('#')) v = '#' + v;721 if (!/^#[0-9a-fA-F]{6}$/.test(v)) {722 hex.value = config.colors[i];723 hex.classList.remove('invalid');724 }725 });726 hexRow.appendChild(hex);727});728 729function setColor(index, value, updateHex = true) {730 config.colors[index] = value;731 document.getElementById(`swatch-${index}`).style.background = value;732 if (updateHex) document.getElementById(`hex-${index}`).value = value;733 applyColors();734 clearActivePreset();735}736 737function applyColors() {738 wallpaper.updateColors(config.colors);739}740 741// ── PRESETS ──742const presetsRow = document.getElementById('presets-row');743PRESETS.forEach((p, pi) => {744 const chip = document.createElement('div');745 chip.className = 'preset-chip';746 chip.id = `preset-${pi}`;747 const grid = document.createElement('div');748 grid.className = 'preset-chip-swatch';749 p.colors.forEach(c => {750 const d = document.createElement('div');751 d.style.background = c;752 grid.appendChild(d);753 });754 const name = document.createElement('div');755 name.className = 'preset-name';756 name.textContent = p.name;757 chip.appendChild(grid);758 chip.appendChild(name);759 chip.addEventListener('click', () => applyPreset(pi));760 presetsRow.appendChild(chip);761});762 763function applyPreset(pi) {764 const p = PRESETS[pi];765 p.colors.forEach((c, i) => {766 config.colors[i] = c;767 document.getElementById(`swatch-${i}`).style.background = c;768 document.getElementById(`hex-${i}`).value = c;769 document.querySelector(`#swatch-${i} input`).value = c;770 });771 applyColors();772 document.querySelectorAll('.preset-chip').forEach(el => el.classList.remove('active'));773 document.getElementById(`preset-${pi}`).classList.add('active');774 haptic();775}776 777function clearActivePreset() {778 document.querySelectorAll('.preset-chip').forEach(el => el.classList.remove('active'));779}780 781// ── RANDOM ──782document.getElementById('btn-random').addEventListener('click', () => {783 config.colors = Array.from({length: 4}, () => '#' + Math.floor(Math.random()*0xffffff).toString(16).padStart(6,'0'));784 config.colors.forEach((c, i) => {785 document.getElementById(`swatch-${i}`).style.background = c;786 document.getElementById(`hex-${i}`).value = c;787 document.querySelector(`#swatch-${i} input`).value = c;788 });789 applyColors();790 clearActivePreset();791 haptic();792});793 794// ── ANIMATION TOGGLES ──795document.getElementById('tog-animate').addEventListener('change', e => {796 config.animate = e.target.checked;797 wallpaper.animate(config.animate);798 haptic('light');799});800document.getElementById('tog-scroll').addEventListener('change', e => {801 config.scrollAnimate = e.target.checked;802 wallpaper.scrollAnimate(config.scrollAnimate);803 haptic('light');804});805 806// ── SLIDERS ──807function makeSlider(id, valId, suffix, callback) {808 const sl = document.getElementById(id);809 const vl = document.getElementById(valId);810 sl.addEventListener('input', e => {811 vl.textContent = e.target.value + (suffix || '');812 callback(parseFloat(e.target.value));813 });814}815 816makeSlider('sl-fps', 'val-fps', '', v => {817 config.fps = v;818 wallpaper.updateFrametime(v);819});820makeSlider('sl-tails', 'val-tails', '', v => {821 config.tails = v;822 wallpaper.updateTails(v);823});824makeSlider('sl-opacity', 'val-opacity', '', v => {825 config.pattern.opacity = v;826 applyPattern();827});828makeSlider('sl-size', 'val-size', 'px', v => {829 config.pattern.size = v + 'px';830 applyPattern();831});832makeSlider('sl-blur', 'val-blur', '', v => {833 config.pattern.blur = v;834 applyPattern();835});836 837// ── MASK TOGGLE ──838document.getElementById('tog-mask').addEventListener('change', e => {839 config.pattern.mask = e.target.checked;840 applyPattern();841 haptic('light');842});843 844// ── BG COLOR ──845const bgPicker = document.getElementById('bg-color-picker');846const bgHex = document.getElementById('bg-hex');847const bgSwatch = document.getElementById('bg-swatch');848 849bgPicker.addEventListener('input', e => {850 config.pattern.background = e.target.value;851 bgSwatch.style.background = e.target.value;852 bgHex.value = e.target.value;853 applyPattern();854});855bgHex.addEventListener('input', e => {856 let v = e.target.value.trim();857 if (!v.startsWith('#')) v = '#' + v;858 if (/^#[0-9a-fA-F]{6}$/.test(v)) {859 config.pattern.background = v;860 bgSwatch.style.background = v;861 bgPicker.value = v;862 applyPattern();863 }864});865 866// ── PATTERNS GRID ──867const patternsGrid = document.getElementById('patterns-grid');868let activePatternIndex = 0;869 870PATTERNS.forEach((p, pi) => {871 const item = document.createElement('div');872 item.className = 'pattern-item' + (pi === 0 ? ' none-item active' : '');873 item.id = `pat-${pi}`;874 875 if (pi === 0) {876 item.textContent = '✕';877 const lbl = document.createElement('div');878 lbl.className = 'pattern-item-name';879 lbl.textContent = 'None';880 item.appendChild(lbl);881 } else {882 const img = document.createElement('img');883 img.src = `https://twallpaper.js.org/patterns/${p.file}`;884 img.alt = p.name;885 img.loading = 'lazy';886 img.onerror = () => { img.style.display='none'; };887 const lbl = document.createElement('div');888 lbl.className = 'pattern-item-name';889 lbl.textContent = p.name;890 item.appendChild(img);891 item.appendChild(lbl);892 }893 894 item.addEventListener('click', () => {895 activePatternIndex = pi;896 document.querySelectorAll('.pattern-item').forEach(el => el.classList.remove('active'));897 item.classList.add('active');898 config.pattern.image = p.file ? `https://twallpaper.js.org/patterns/${p.file}` : null;899 applyPattern();900 haptic('light');901 });902 903 patternsGrid.appendChild(item);904});905 906function applyPattern() {907 if (!config.pattern.image) {908 // re-init without pattern to fully remove it909 wallpaper.init({910 colors: config.colors,911 fps: config.fps,912 tails: config.tails,913 animate: config.animate,914 scrollAnimate: config.scrollAnimate,915 });916 return;917 }918 wallpaper.updatePattern({919 image: config.pattern.image,920 mask: config.pattern.mask,921 background: config.pattern.background,922 size: config.pattern.size,923 blur: config.pattern.blur,924 opacity: config.pattern.opacity,925 });926 // if pattern div not created yet, re-init927 if (!container.querySelector('.tw-pattern')) {928 wallpaper.init({929 colors: config.colors,930 fps: config.fps,931 tails: config.tails,932 animate: config.animate,933 scrollAnimate: config.scrollAnimate,934 pattern: {935 image: config.pattern.image,936 mask: config.pattern.mask,937 background: config.pattern.background,938 size: config.pattern.size,939 blur: config.pattern.blur,940 opacity: config.pattern.opacity,941 }942 });943 }944}945 946// ── RESET ──947document.getElementById('btn-reset').addEventListener('click', () => {948 config = JSON.parse(JSON.stringify(DEFAULT_CONFIG));949 950 // colors951 config.colors.forEach((c, i) => {952 document.getElementById(`swatch-${i}`).style.background = c;953 document.getElementById(`hex-${i}`).value = c;954 document.querySelector(`#swatch-${i} input`).value = c;955 });956 clearActivePreset();957 document.getElementById(`preset-0`)?.classList.add('active'); // TG Green958 959 // animation960 document.getElementById('tog-animate').checked = true;961 document.getElementById('tog-scroll').checked = false;962 document.getElementById('sl-fps').value = 60;963 document.getElementById('val-fps').textContent = '60';964 document.getElementById('sl-tails').value = 60;965 document.getElementById('val-tails').textContent = '60';966 967 // pattern968 activePatternIndex = 0;969 document.querySelectorAll('.pattern-item').forEach(el => el.classList.remove('active'));970 document.getElementById('pat-0').classList.add('active');971 document.getElementById('tog-mask').checked = false;972 document.getElementById('sl-opacity').value = 0.45;973 document.getElementById('val-opacity').textContent = '0.45';974 document.getElementById('sl-size').value = 300;975 document.getElementById('val-size').textContent = '300px';976 document.getElementById('sl-blur').value = 0;977 document.getElementById('val-blur').textContent = '0';978 bgPicker.value = '#000000';979 bgHex.value = '#000000';980 bgSwatch.style.background = '#000000';981 982 // re-init wallpaper983 wallpaper.init({984 colors: config.colors,985 fps: config.fps,986 tails: config.tails,987 animate: true,988 scrollAnimate: false,989 });990 991 haptic('medium');992});993 994// ── FAB + PANEL ──995const fab = document.getElementById('fab');996const panel = document.getElementById('panel');997const backdrop = document.getElementById('backdrop');998 999function openPanel() {1000 panel.classList.add('open');1001 backdrop.classList.add('visible');1002 fab.classList.add('open');1003}1004function closePanel() {1005 panel.classList.remove('open');1006 backdrop.classList.remove('visible');1007 fab.classList.remove('open');1008}1009 1010fab.addEventListener('click', () => {1011 panel.classList.contains('open') ? closePanel() : openPanel();1012 haptic('light');1013});1014backdrop.addEventListener('click', closePanel);1015 1016// ── DRAG TO CLOSE panel ──1017let startY = 0, startScrollTop = 0, dragging = false;1018const panelBody = document.getElementById('panel-body');1019const handle = document.querySelector('.panel-handle');1020 1021handle.addEventListener('touchstart', e => {1022 startY = e.touches[0].clientY;1023 dragging = true;1024}, { passive: true });1025 1026window.addEventListener('touchmove', e => {1027 if (!dragging) return;1028 const dy = e.touches[0].clientY - startY;1029 if (dy > 60) { closePanel(); dragging = false; }1030}, { passive: true });1031 1032window.addEventListener('touchend', () => { dragging = false; });1033 1034// ── HAPTIC ──1035function haptic(style = 'light') {1036 tg?.HapticFeedback?.impactOccurred?.(style);1037}1038 1039// Mark first preset active on load1040document.getElementById('preset-0').classList.add('active');1041</script>1042</body>1043</html>