微件

TierListMaker:修订间差异

来自卡厄思梦境WIKI

律Rhyme留言 | 贡献
无编辑摘要
律Rhyme留言 | 贡献
无编辑摘要
第155行: 第155行:
   // Utility: ideal text color for background
   // Utility: ideal text color for background
   function idealTextColor(hex) {
   function idealTextColor(hex) {
    if (!hex) return '#fff';
     const c = hex.replace('#','');
     const c = hex.replace('#','');
     let r, g, b;
     let r, g, b;
第166行: 第167行:
       b = parseInt(c.substring(4,6), 16);
       b = parseInt(c.substring(4,6), 16);
     }
     }
    // relative luminance
     const luminance = (0.2126*r + 0.7152*g + 0.0722*b) / 255;
     const luminance = (0.2126*r + 0.7152*g + 0.0722*b) / 255;
     return luminance > 0.55 ? '#000' : '#fff';
     return luminance > 0.55 ? '#000' : '#fff';
第180行: 第180行:
       const color = th.getAttribute('data-color') || '#888888';
       const color = th.getAttribute('data-color') || '#888888';
       applyThColor(th, color);
       applyThColor(th, color);
      // Open palette when clicking header area (but not the editable label)
       th.addEventListener('click', (e) => {
       th.addEventListener('click', (e) => {
        if (e.target.closest('.tier-label')) {
          // allow editing text without opening palette
          return;
        }
         openColorPalette(th, e);
         openColorPalette(th, e);
       });
       });
第267行: 第272行:
       zone.classList.remove('over');
       zone.classList.remove('over');
       if (!draggedEl) return;
       if (!draggedEl) return;
      // move element
       zone.appendChild(draggedEl);
       zone.appendChild(draggedEl);
      // normalize styling when moved
       draggedEl.style.pointerEvents = 'auto';
       draggedEl.style.pointerEvents = 'auto';
     });
     });
   }
   }
   function initDragAndDrop() {
   function initDragAndDrop() {
    // make existing avatars draggable
     document.querySelectorAll('.avatar-frame').forEach(setupDraggable);
     document.querySelectorAll('.avatar-frame').forEach(setupDraggable);
    // dropzones
     document.querySelectorAll('.tier-dropzone').forEach(setupDropzone);
     document.querySelectorAll('.tier-dropzone').forEach(setupDropzone);
     const pool = document.getElementById('char-pool');
     const pool = document.getElementById('char-pool');
第322行: 第323行:
     table.tBodies[0].appendChild(tr);
     table.tBodies[0].appendChild(tr);
     // Enable interactions
     // Enable interactions
     th.addEventListener('click', (e) => openColorPalette(th, e));
     th.addEventListener('click', (e) => {
      if (e.target.closest('.tier-label')) return;
      openColorPalette(th, e);
    });
     setupDropzone(dropzone);
     setupDropzone(dropzone);
     // Keep data-tier synced with label edits
     // Keep data-tier synced with label edits
第329行: 第333行:
     });
     });
   }
   }
   // Export to PNG by drawing canvas (no external libs)
  // Reset: move all avatars back to pool
  function resetTierList() {
    const pool = document.getElementById('char-pool');
    if (!pool) return;
    document.querySelectorAll('#tierlist-table .tier-dropzone .avatar-frame').forEach(av => {
      pool.appendChild(av);
    });
  }
   // Export to PNG by drawing canvas (preserving backgrounds)
   function exportPNG() {
   function exportPNG() {
     const table = document.getElementById('tierlist-table');
     const table = document.getElementById('tierlist-table');
     const tableRect = table.getBoundingClientRect();
     const tableRect = table.getBoundingClientRect();
     const thWidth = table.querySelector('th.tier-th')?.getBoundingClientRect().width || 120;
     const thEl = table.querySelector('th.tier-th');
    const thWidth = thEl ? Math.ceil(thEl.getBoundingClientRect().width) : 120;
     // Collect rows
     // Collect rows
     const rows = Array.from(table.querySelectorAll('tr'));
     const rows = Array.from(table.querySelectorAll('tr'));
第345行: 第358行:
       const color = th.getAttribute('data-color') || '#888';
       const color = th.getAttribute('data-color') || '#888';
       const textColor = idealTextColor(color);
       const textColor = idealTextColor(color);
      const dzBg = getComputedStyle(dz).backgroundColor || '#ffffff';
       // collect avatars in this dropzone
       // collect avatars in this dropzone
       const avatars = Array.from(dz.querySelectorAll('.avatar-frame')).map(av => {
       const avatars = Array.from(dz.querySelectorAll('.avatar-frame')).map(av => {
第358行: 第372行:
         };
         };
       });
       });
       return { thRect, dzRect, height, label, color, textColor, avatars };
       return { thRect, dzRect, height, label, color, textColor, dzBg, avatars };
     });
     });
     const totalHeight = rowInfo.reduce((acc, r) => acc + r.height, 0);
     const totalHeight = Math.ceil(rowInfo.reduce((acc, r) => acc + r.height, 0));
     const width = tableRect.width;
     const width = Math.ceil(tableRect.width);
     const canvas = document.createElement('canvas');
     const canvas = document.createElement('canvas');
     canvas.width = Math.ceil(width);
     canvas.width = width;
     canvas.height = Math.ceil(totalHeight);
     canvas.height = totalHeight;
     const ctx = canvas.getContext('2d');
     const ctx = canvas.getContext('2d');
     // Background
     // Full page background (match body)
     ctx.fillStyle = '#ffffff';
     const bodyBg = getComputedStyle(document.body).backgroundColor || '#ffffff';
    ctx.fillStyle = bodyBg;
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     ctx.fillRect(0, 0, canvas.width, canvas.height);
     // Load all images
     // Load all images
第376行: 第391行:
           loadTasks.push(new Promise((resolve) => {
           loadTasks.push(new Promise((resolve) => {
             const img = new Image();
             const img = new Image();
            // try anonymous to avoid taint if same-origin allows
            img.crossOrigin = 'anonymous';
             img.onload = () => resolve({ a, img });
             img.onload = () => resolve({ a, img });
             img.onerror = () => resolve({ a, img: null });
             img.onerror = () => resolve({ a, img: null });
第392行: 第409行:
       rows.forEach((row, idx) => {
       rows.forEach((row, idx) => {
         const info = rowInfo[idx];
         const info = rowInfo[idx];
         // Left header
         // Left header background
         ctx.fillStyle = info.color;
         ctx.fillStyle = info.color;
         ctx.fillRect(0, yCursor, thWidth, info.height);
         ctx.fillRect(0, yCursor, thWidth, info.height);
        // Right dropzone background
        ctx.fillStyle = info.dzBg;
        ctx.fillRect(thWidth, yCursor, width - thWidth, info.height);
         // Label text centered
         // Label text centered
         ctx.fillStyle = info.textColor;
         ctx.fillStyle = info.textColor;
第400行: 第420行:
         ctx.textAlign = 'center';
         ctx.textAlign = 'center';
         ctx.textBaseline = 'middle';
         ctx.textBaseline = 'middle';
         ctx.fillText(info.label, thWidth / 2, yCursor + info.height / 2);
        const labelText = (info.label || '').trim();
         // Draw avatars (absolute positions relative to table)
         ctx.fillText(labelText, Math.floor(thWidth / 2), Math.floor(yCursor + info.height / 2));
         // Draw avatars
         info.avatars.forEach(a => {
         info.avatars.forEach(a => {
           const img = imgMap.get(a.imgSrc);
           const img = imgMap.get(a.imgSrc);
第407行: 第428行:
             const x = Math.max(thWidth + 6, Math.round(a.x));
             const x = Math.max(thWidth + 6, Math.round(a.x));
             const y = Math.round(a.y);
             const y = Math.round(a.y);
            // If rect outside current row vertical span, clamp within this row
             const rowTop = yCursor;
             const rowTop = yCursor;
             const rowBottom = yCursor + info.height - a.h - 6;
             const rowBottom = yCursor + info.height - a.h - 6;
第445行: 第465行:
         document.body.removeChild(a);
         document.body.removeChild(a);
       } catch (e) {
       } catch (e) {
        // Fallback: open in new tab
         const url = canvas.toDataURL('image/png');
         const url = canvas.toDataURL('image/png');
         window.open(url, '_blank');
         window.open(url, '_blank');
第468行: 第487行:
     const addRowBtn = document.getElementById('add-row');
     const addRowBtn = document.getElementById('add-row');
     const saveBtn = document.getElementById('save-png');
     const saveBtn = document.getElementById('save-png');
    const resetBtn = document.getElementById('reset-all');
     addRowBtn && addRowBtn.addEventListener('click', addRow);
     addRowBtn && addRowBtn.addEventListener('click', addRow);
     saveBtn && saveBtn.addEventListener('click', exportPNG);
     saveBtn && saveBtn.addEventListener('click', exportPNG);
    resetBtn && resetBtn.addEventListener('click', resetTierList);
   }
   }
   if (document.readyState === 'complete' || document.readyState === 'interactive') {
   if (document.readyState === 'complete' || document.readyState === 'interactive') {

2025年10月20日 (一) 22:34的版本