Paradox/TBLWebappBotPublic · Community Store Listing

Webapp Demo Bot

ProfileTelegram
7 commands0 envUpdated 1mo agoCreated Dec 13, 2025
Back to folder

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>