glassheart-ui-vanilla
Version: 
GlassHeart UI - Vanilla JavaScript components
1,449 lines (1,438 loc) • 52.3 kB
JavaScript
'use strict';
class GlassCard {
    constructor(options = {}) {
        this.options = {
            size: 'md',
            variant: 'default',
            glass: 'medium',
            liquid: false,
            interactive: false,
            loading: false,
            ...options
        };
        this.element = this.createElement();
    }
    createElement() {
        const card = document.createElement('div');
        card.className = this.getClassNames();
        if (this.options.content) {
            card.innerHTML = this.options.content;
        }
        return card;
    }
    getClassNames() {
        const classes = ['gh-card'];
        // Size classes
        if (this.options.size) {
            classes.push(`gh-card-${this.options.size}`);
        }
        // Variant classes
        if (this.options.variant) {
            classes.push(`gh-card-${this.options.variant}`);
        }
        // Glass effect
        if (this.options.glass) {
            classes.push(`gh-glass-${this.options.glass}`);
        }
        // Liquid effect
        if (this.options.liquid) {
            classes.push('gh-liquid-flow');
        }
        // Interactive
        if (this.options.interactive) {
            classes.push('gh-interactive');
        }
        // Loading state
        if (this.options.loading) {
            classes.push('gh-loading');
        }
        return classes.join(' ');
    }
    render(container) {
        const target = typeof container === 'string'
            ? document.querySelector(container)
            : container;
        if (target) {
            target.appendChild(this.element);
        }
    }
    destroy() {
        if (this.element.parentNode) {
            this.element.parentNode.removeChild(this.element);
        }
    }
    getElement() {
        return this.element;
    }
}
/**
 * Creating the displacement map that is used by feDisplacementMap filter.
 * Uses dynamic dimensions based on element size.
 */
const getDisplacementMap = ({ width, height, radius, depth }) => {
    // 使用實際尺寸,確保精確對齊
    const actualWidth = Math.max(width, 1);
    const actualHeight = Math.max(height, 1);
    // 計算邊框半徑的百分比
    const radiusXPercent = (radius / actualWidth) * 100;
    const radiusYPercent = (radius / actualHeight) * 100;
    return "data:image/svg+xml;utf8," +
        encodeURIComponent(`<svg height="${actualHeight}" width="${actualWidth}" viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg">
      <style>
          .mix { mix-blend-mode: screen; }
      </style>
      <defs>
          <linearGradient 
            id="Y" 
            x1="0" 
            x2="0" 
            y1="${Math.max(radiusYPercent * 0.5, 2)}%" 
            y2="${Math.min(100 - radiusYPercent * 0.5, 98)}%">
              <stop offset="0%" stop-color="#0F0" />
              <stop offset="100%" stop-color="#000" />
          </linearGradient>
          <linearGradient 
            id="X" 
            x1="${Math.max(radiusXPercent * 0.5, 2)}%" 
            x2="${Math.min(100 - radiusXPercent * 0.5, 98)}%"
            y1="0" 
            y2="0">
              <stop offset="0%" stop-color="#F00" />
              <stop offset="100%" stop-color="#000" />
          </linearGradient>
      </defs>
      <rect x="0" y="0" height="${actualHeight}" width="${actualWidth}" fill="#808080" />
      <g filter="blur(0.5px)">
        <rect x="0" y="0" height="${actualHeight}" width="${actualWidth}" fill="#000080" />
        <rect
            x="0"
            y="0"
            height="${actualHeight}"
            width="${actualWidth}"
            fill="url(#Y)"
            class="mix"
        />
        <rect
            x="0"
            y="0"
            height="${actualHeight}"
            width="${actualWidth}"
            fill="url(#X)"
            class="mix"
        />
        <rect
            x="${Math.max(depth, 1)}"
            y="${Math.max(depth, 1)}"
            height="${actualHeight - 2 * Math.max(depth, 1)}"
            width="${actualWidth - 2 * Math.max(depth, 1)}"
            fill="#808080"
            rx="${radius}"
            ry="${radius}"
            filter="blur(${Math.max(depth * 0.3, 0.5)}px)"
        />
      </g>
  </svg>`);
};
/**
 * SVG 快取工具,用於優化 Liquid Glass 效能的 SVG 生成
 */
class SVGCache {
    constructor() {
        this.cache = new Map();
        this.maxSize = 100; // 最大快取條目數
        this.maxAge = 5 * 60 * 1000; // 5分鐘過期
    }
    generateKey(key) {
        return `${key.width}x${key.height}_r${key.radius}_d${key.depth}_s${key.strength}_c${key.chromaticAberration}`;
    }
    isExpired(entry) {
        return Date.now() - entry.timestamp > this.maxAge;
    }
    cleanup() {
        const now = Date.now();
        const entries = Array.from(this.cache.entries());
        // 移除過期條目
        entries.forEach(([key, entry]) => {
            if (now - entry.timestamp > this.maxAge) {
                this.cache.delete(key);
            }
        });
        // 如果仍然超過最大大小,移除最舊的條目
        if (this.cache.size > this.maxSize) {
            const sortedEntries = entries
                .filter(([key]) => this.cache.has(key))
                .sort((a, b) => a[1].timestamp - b[1].timestamp);
            const toRemove = sortedEntries.slice(0, this.cache.size - this.maxSize);
            toRemove.forEach(([key]) => this.cache.delete(key));
        }
    }
    get(key) {
        const cacheKey = this.generateKey(key);
        const entry = this.cache.get(cacheKey);
        if (!entry || this.isExpired(entry)) {
            if (entry) {
                this.cache.delete(cacheKey);
            }
            return null;
        }
        return entry.url;
    }
    set(key, url) {
        const cacheKey = this.generateKey(key);
        this.cache.set(cacheKey, {
            url,
            timestamp: Date.now(),
        });
        // 定期清理快取
        if (this.cache.size > this.maxSize * 0.8) {
            this.cleanup();
        }
    }
    clear() {
        this.cache.clear();
    }
    size() {
        return this.cache.size;
    }
}
// 導出單例實例
const svgCache = new SVGCache();
/**
 * Creating the displacement filter.
 * Uses dynamic dimensions based on element size.
 * The file complexity is due to the experimental "chromatic aberration" effect;
 * filters from first `feColorMatrix` to last `feBlend` can be removed if the effect is not needed.
 */
const getDisplacementFilter = ({ width, height, radius, depth, strength = 100, chromaticAberration = 0, }) => {
    // 使用實際尺寸,確保精確對齊
    const actualWidth = Math.max(width, 1);
    const actualHeight = Math.max(height, 1);
    // 檢查快取
    const cacheKey = {
        width: actualWidth,
        height: actualHeight,
        radius,
        depth,
        strength,
        chromaticAberration,
    };
    const cached = svgCache.get(cacheKey);
    if (cached) {
        return cached;
    }
    // 生成新的 SVG
    const svgContent = `data:image/svg+xml;utf8,` +
        encodeURIComponent(`<svg height="${actualHeight}" width="${actualWidth}" viewBox="0 0 ${actualWidth} ${actualHeight}" xmlns="http://www.w3.org/2000/svg">
      <defs>
          <filter id="displace" color-interpolation-filters="sRGB" x="0%" y="0%" width="100%" height="100%">
              <feImage x="0" y="0" height="${actualHeight}" width="${actualWidth}" href="${getDisplacementMap({ width: actualWidth, height: actualHeight, radius, depth })}" result="displacementMap" />
              <feDisplacementMap
                  in="SourceGraphic"
                  in2="displacementMap"
                  scale="${strength + chromaticAberration * 2}"
                  xChannelSelector="R"
                  yChannelSelector="G"
              />
              <feColorMatrix
              type="matrix"
              values="1 0 0 0 0
                      0 0 0 0 0
                      0 0 0 0 0
                      0 0 0 1 0"
              result="displacedR"
                      />
              <feDisplacementMap
                  in="SourceGraphic"
                  in2="displacementMap"
                  scale="${strength + chromaticAberration}"
                  xChannelSelector="R"
                  yChannelSelector="G"
              />
              <feColorMatrix
              type="matrix"
              values="0 0 0 0 0
                      0 1 0 0 0
                      0 0 0 0 0
                      0 0 0 1 0"
              result="displacedG"
                      />
              <feDisplacementMap
                      in="SourceGraphic"
                      in2="displacementMap"
                      scale="${strength}"
                      xChannelSelector="R"
                      yChannelSelector="G"
                  />
                  <feColorMatrix
                  type="matrix"
                  values="0 0 0 0 0
                          0 0 0 0 0
                          0 0 1 0 0
                          0 0 0 1 0"
                  result="displacedB"
                          />
                <feBlend in="displacedR" in2="displacedG" mode="screen"/>
                <feBlend in2="displacedB" mode="screen"/>
          </filter>
      </defs>
  </svg>`) +
        "#displace";
    // 快取結果
    svgCache.set(cacheKey, svgContent);
    return svgContent;
};
const useLiquidGlass = (options = {}) => {
    const { depth: baseDepth = 8, strength = 100, chromaticAberration = 0, blur = 2, } = options;
    let elementRef = null;
    let state = {
        clicked: false,
        hovered: false,
        depth: baseDepth,
    };
    let dimensions = { width: 0, height: 0, radius: 0 };
    let debounceTimeout = null;
    let lastStyle = null;
    // 優化事件處理器,減少不必要的狀態更新
    const handleMouseDown = () => {
        if (!state.clicked) {
            state.clicked = true;
            updateStyle();
        }
    };
    const handleMouseUp = () => {
        if (state.clicked) {
            state.clicked = false;
            updateStyle();
        }
    };
    // 使用防抖優化 hover 事件
    const handleMouseEnter = () => {
        if (debounceTimeout) {
            clearTimeout(debounceTimeout);
        }
        debounceTimeout = window.setTimeout(() => {
            if (!state.hovered) {
                state.hovered = true;
            }
        }, 16); // 約 60fps
    };
    const handleMouseLeave = () => {
        if (debounceTimeout) {
            clearTimeout(debounceTimeout);
            debounceTimeout = null;
        }
        if (state.hovered || state.clicked) {
            state.hovered = false;
            state.clicked = false;
            updateStyle();
        }
    };
    const updateDimensions = () => {
        if (elementRef) {
            const rect = elementRef.getBoundingClientRect();
            const computedStyle = window.getComputedStyle(elementRef);
            const borderRadius = parseFloat(computedStyle.borderRadius) || 0;
            // 確保獲取精確的像素值
            const width = Math.round(rect.width);
            const height = Math.round(rect.height);
            dimensions = {
                width,
                height,
                radius: borderRadius,
            };
            updateStyle();
        }
    };
    // 使用 requestAnimationFrame 優化尺寸更新
    const updateWithRAF = () => {
        requestAnimationFrame(updateDimensions);
    };
    const updateStyle = () => {
        if (elementRef) {
            const currentDepth = state.clicked ? baseDepth / 0.7 : baseDepth;
            if (dimensions.width === 0 || dimensions.height === 0) {
                const backdropFilter = `blur(${blur / 2}px) brightness(1.1) saturate(1.5)`;
                if (lastStyle !== backdropFilter) {
                    elementRef.style.backdropFilter = backdropFilter;
                    lastStyle = backdropFilter;
                }
                return;
            }
            const displacementFilterUrl = getDisplacementFilter({
                width: dimensions.width,
                height: dimensions.height,
                radius: dimensions.radius,
                depth: currentDepth,
                strength,
                chromaticAberration,
            });
            const backdropFilter = `blur(${blur / 2}px) url('${displacementFilterUrl}') blur(${blur}px) brightness(1.1) saturate(1.5)`;
            // 只在樣式真正改變時才更新
            if (lastStyle !== backdropFilter) {
                elementRef.style.backdropFilter = backdropFilter;
                lastStyle = backdropFilter;
            }
        }
    };
    const getLiquidGlassStyle = () => {
        const currentDepth = state.clicked ? baseDepth / 0.7 : baseDepth;
        const displacementFilterUrl = getDisplacementFilter({
            width: dimensions.width,
            height: dimensions.height,
            radius: dimensions.radius,
            depth: currentDepth,
            strength,
            chromaticAberration,
        });
        const backdropFilter = `blur(${blur / 2}px) url('${displacementFilterUrl}') blur(${blur}px) brightness(1.1) saturate(1.5)`;
        return { backdropFilter };
    };
    const setupEventListeners = (element) => {
        element.addEventListener('mousedown', handleMouseDown);
        element.addEventListener('mouseup', handleMouseUp);
        element.addEventListener('mouseenter', handleMouseEnter);
        element.addEventListener('mouseleave', handleMouseLeave);
        window.addEventListener('resize', updateWithRAF);
    };
    const cleanup = () => {
        // 清理防抖定時器
        if (debounceTimeout) {
            clearTimeout(debounceTimeout);
            debounceTimeout = null;
        }
        if (elementRef) {
            elementRef.removeEventListener('mousedown', handleMouseDown);
            elementRef.removeEventListener('mouseup', handleMouseUp);
            elementRef.removeEventListener('mouseenter', handleMouseEnter);
            elementRef.removeEventListener('mouseleave', handleMouseLeave);
        }
        window.removeEventListener('resize', updateWithRAF);
    };
    // Set element ref and setup listeners
    const setElementRef = (element) => {
        elementRef = element;
        setupEventListeners(element);
        updateWithRAF();
    };
    return {
        get elementRef() { return elementRef; },
        set elementRef(element) {
            if (element) {
                setElementRef(element);
            }
        },
        state,
        dimensions,
        getLiquidGlassStyle,
        handleMouseDown,
        handleMouseUp,
        handleMouseEnter,
        handleMouseLeave,
        cleanup,
    };
};
class GlassButton {
    constructor(options = {}) {
        this.options = {
            variant: 'default',
            shape: 'default',
            size: 'md',
            glass: 'medium',
            liquidGlass: false,
            liquidGlassOptions: {},
            loading: false,
            disabled: false,
            ...options
        };
        this.element = this.createElement();
        this.setupLiquidGlass();
    }
    createElement() {
        const button = document.createElement('button');
        button.className = this.getClassNames();
        button.textContent = this.options.text || 'Button';
        button.disabled = this.options.disabled || this.options.loading || false;
        if (this.options.loading) {
            const spinner = document.createElement('span');
            spinner.className = 'gh-loading-spinner';
            button.appendChild(spinner);
        }
        if (this.options.onClick) {
            button.addEventListener('click', this.options.onClick);
        }
        if (this.options.onFocus) {
            button.addEventListener('focus', this.options.onFocus);
        }
        if (this.options.onBlur) {
            button.addEventListener('blur', this.options.onBlur);
        }
        if (this.options.onMouseEnter) {
            button.addEventListener('mouseenter', this.options.onMouseEnter);
        }
        if (this.options.onMouseLeave) {
            button.addEventListener('mouseleave', this.options.onMouseLeave);
        }
        return button;
    }
    setupLiquidGlass() {
        if (this.options.liquidGlass) {
            this.liquidGlassHook = useLiquidGlass({
                depth: 8,
                strength: 100,
                chromaticAberration: 0,
                blur: 2,
                ...this.options.liquidGlassOptions,
            });
            this.liquidGlassHook.elementRef = this.element;
        }
    }
    getClassNames() {
        const classes = ['gh-btn'];
        // Variant classes
        classes.push(`gh-btn-${this.options.variant}`);
        // Shape classes
        if (this.options.shape !== 'default') {
            classes.push(`gh-btn-${this.options.shape}`);
        }
        // Size classes
        classes.push(`gh-btn-${this.options.size}`);
        // Glass classes
        classes.push(`gh-glass-${this.options.glass}`);
        // Liquid glass classes
        if (this.options.liquidGlass) {
            classes.push('gh-btn-liquid-glass');
        }
        // Loading classes
        if (this.options.loading) {
            classes.push('gh-btn-loading');
        }
        return classes.filter(Boolean).join(' ');
    }
    getElement() {
        return this.element;
    }
    updateOptions(newOptions) {
        this.options = { ...this.options, ...newOptions };
        this.element.className = this.getClassNames();
        this.element.disabled = this.options.disabled || this.options.loading || false;
        // Update text content
        if (newOptions.text !== undefined) {
            this.element.textContent = this.options.text || 'Button';
        }
        // Update loading state
        if (newOptions.loading !== undefined) {
            const existingSpinner = this.element.querySelector('.gh-loading-spinner');
            if (this.options.loading && !existingSpinner) {
                const spinner = document.createElement('span');
                spinner.className = 'gh-loading-spinner';
                this.element.appendChild(spinner);
            }
            else if (!this.options.loading && existingSpinner) {
                existingSpinner.remove();
            }
        }
        // Update liquid glass
        if (newOptions.liquidGlass !== undefined) {
            if (this.options.liquidGlass && !this.liquidGlassHook) {
                this.setupLiquidGlass();
            }
            else if (!this.options.liquidGlass && this.liquidGlassHook) {
                this.liquidGlassHook.cleanup();
                this.liquidGlassHook = undefined;
            }
        }
    }
    destroy() {
        if (this.liquidGlassHook) {
            this.liquidGlassHook.cleanup();
        }
        this.element.remove();
    }
}
class GlassInput {
    constructor(options = {}) {
        this.options = {
            type: 'text',
            size: 'md',
            variant: 'default',
            glass: 'medium',
            liquid: false,
            error: false,
            disabled: false,
            ...options
        };
        this.element = this.createElement();
    }
    createElement() {
        const input = document.createElement('input');
        input.className = this.getClassNames();
        input.type = this.options.type || 'text';
        input.placeholder = this.options.placeholder || '';
        input.disabled = this.options.disabled || false;
        if (this.options.value) {
            input.value = this.options.value;
        }
        if (this.options.onChange) {
            input.addEventListener('input', this.options.onChange);
        }
        if (this.options.onFocus) {
            input.addEventListener('focus', this.options.onFocus);
        }
        if (this.options.onBlur) {
            input.addEventListener('blur', this.options.onBlur);
        }
        return input;
    }
    getClassNames() {
        const classes = ['gh-input'];
        // Size classes
        if (this.options.size) {
            classes.push(`gh-input-${this.options.size}`);
        }
        // Variant classes
        if (this.options.variant) {
            classes.push(`gh-input-${this.options.variant}`);
        }
        // Glass effect
        if (this.options.glass) {
            classes.push(`gh-glass-${this.options.glass}`);
        }
        // Liquid effect
        if (this.options.liquid) {
            classes.push('gh-liquid-flow');
        }
        // Error state
        if (this.options.error) {
            classes.push('gh-input-error');
        }
        // Disabled state
        if (this.options.disabled) {
            classes.push('gh-disabled');
        }
        return classes.join(' ');
    }
    render(container) {
        const target = typeof container === 'string'
            ? document.querySelector(container)
            : container;
        if (target) {
            target.appendChild(this.element);
        }
    }
    destroy() {
        if (this.element.parentNode) {
            this.element.parentNode.removeChild(this.element);
        }
    }
    getElement() {
        return this.element;
    }
    getValue() {
        return this.element.value;
    }
    setValue(value) {
        this.element.value = value;
    }
    setDisabled(disabled) {
        this.element.disabled = disabled;
    }
}
class GlassTypography {
    constructor(container, options) {
        this.isLoaded = false;
        this.dimensions = { width: 0, height: 0 };
        this.element = container;
        this.options = {
            variant: 'p',
            size: 'md',
            weight: 'normal',
            glass: 'medium',
            liquid: false,
            gradient: false,
            animated: false,
            className: '',
            style: {},
            fontFamily: 'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
            letterSpacing: 0,
            lineHeight: 1.2,
            textAlign: 'left',
            textShadow: true,
            glow: false,
            glowColor: '#ffffff',
            glowIntensity: 0.8,
            blur: 20,
            opacity: 0.2,
            saturation: 180,
            brightness: 1.2,
            contrast: 1.1,
            ...options,
            children: options.children || '',
        };
        this.init();
    }
    init() {
        this.createElements();
        this.setupEventListeners();
        this.updateDimensions();
        this.drawGlassText();
    }
    createElements() {
        // 創建容器
        this.element.className = this.getComponentClasses();
        this.element.style.cssText = this.getContainerStyle();
        // 創建 Canvas
        this.canvas = document.createElement('canvas');
        this.canvas.className = 'gh-typography-canvas';
        this.canvas.style.cssText = this.getCanvasStyle();
        // 創建 Fallback
        this.fallback = document.createElement('div');
        this.fallback.className = 'gh-typography-fallback';
        this.fallback.style.cssText = this.getFallbackStyle();
        this.fallback.textContent = this.options.children;
        // 添加元素到容器
        this.element.appendChild(this.canvas);
        this.element.appendChild(this.fallback);
    }
    setupEventListeners() {
        // 監聽窗口大小變化
        window.addEventListener('resize', () => this.updateDimensions());
        // 使用 ResizeObserver 監聽容器大小變化
        if (window.ResizeObserver) {
            this.resizeObserver = new ResizeObserver(() => this.updateDimensions());
            this.resizeObserver.observe(this.element);
        }
        // 動畫控制
        if (this.options.animated && this.options.liquid) {
            this.animate();
        }
    }
    updateDimensions() {
        const rect = this.element.getBoundingClientRect();
        this.dimensions = { width: rect.width, height: rect.height };
        if (this.dimensions.width > 0 && this.dimensions.height > 0) {
            this.drawGlassText();
        }
    }
    // 計算字體大小
    getFontSize() {
        const sizeMap = {
            xs: 12,
            sm: 14,
            md: 16,
            lg: 18,
            xl: 20,
            '2xl': 24,
            '3xl': 30,
            '4xl': 36,
            '5xl': 48,
            '6xl': 60,
        };
        return sizeMap[this.options.size] || 16;
    }
    // 計算字體重量
    getFontWeight() {
        const weightMap = {
            light: 300,
            normal: 400,
            medium: 500,
            semibold: 600,
            bold: 700,
            extrabold: 800,
            black: 900,
        };
        return weightMap[this.options.weight] || 400;
    }
    // 計算玻璃效果參數
    getGlassParams() {
        const glassMap = {
            light: { opacity: 0.15, blur: 15, saturation: 150, brightness: 1.3, contrast: 1.2 },
            medium: { opacity: 0.25, blur: 25, saturation: 200, brightness: 1.4, contrast: 1.3 },
            heavy: { opacity: 0.35, blur: 35, saturation: 250, brightness: 1.5, contrast: 1.4 },
        };
        return glassMap[this.options.glass] || glassMap.medium;
    }
    // 測量文字尺寸
    measureText(text, font) {
        const canvas = document.createElement('canvas');
        const ctx = canvas.getContext('2d');
        if (!ctx)
            return { width: 0, height: 0 };
        ctx.font = font;
        const metrics = ctx.measureText(text);
        const width = metrics.width;
        const ascent = metrics.actualBoundingBoxAscent || metrics.fontBoundingBoxAscent || 0;
        const descent = metrics.actualBoundingBoxDescent || metrics.fontBoundingBoxDescent || 0;
        const height = ascent + descent;
        const fontSize = this.getFontSize();
        const extraPadding = fontSize * 0.3;
        return { width, height: height + extraPadding };
    }
    // 繪製高級毛玻璃文字
    drawGlassText() {
        if (!this.canvas || !this.element || !this.options.children)
            return;
        const ctx = this.canvas.getContext('2d');
        if (!ctx)
            return;
        const fontSize = this.getFontSize();
        const fontWeight = this.getFontWeight();
        const font = `${fontWeight} ${fontSize}px ${this.options.fontFamily}`;
        const glassParams = this.getGlassParams();
        // 測量文字
        const textMetrics = this.measureText(this.options.children, font);
        const textWidth = textMetrics.width;
        const textHeight = textMetrics.height;
        // 設置畫布尺寸
        const rect = this.element.getBoundingClientRect();
        const dpr = window.devicePixelRatio || 1;
        const width = rect.width;
        const minHeight = Math.max(rect.height, textHeight + 40);
        this.canvas.width = width * dpr;
        this.canvas.height = minHeight * dpr;
        this.canvas.style.width = `${width}px`;
        this.canvas.style.height = `${minHeight}px`;
        ctx.scale(dpr, dpr);
        // 計算文字位置
        let x = 0;
        let y = minHeight / 2;
        switch (this.options.textAlign) {
            case 'center':
                x = width / 2 - textWidth / 2;
                break;
            case 'right':
                x = width - textWidth;
                break;
            case 'justify':
                x = 0;
                break;
            default:
                x = 0;
        }
        // 清除畫布
        ctx.clearRect(0, 0, width, minHeight);
        // 創建多層毛玻璃效果
        const layers = 4;
        for (let layer = 0; layer < layers; layer++) {
            glassParams.blur + (layer * 3);
            // 設置字體
            ctx.font = font;
            ctx.textAlign = this.options.textAlign;
            ctx.textBaseline = 'middle';
            ctx.letterSpacing = `${this.options.letterSpacing}px`;
            // 創建複雜的漸變效果
            const gradientObj = ctx.createLinearGradient(x - textWidth * 0.2, y - textHeight * 0.6, x + textWidth * 1.2, y + textHeight * 0.6);
            // 根據玻璃強度調整漸變
            const baseOpacity = glassParams.opacity * (1 - layer * 0.2);
            const gradientStops = [
                { pos: 0, alpha: baseOpacity * 0.9 },
                { pos: 0.2, alpha: baseOpacity * 0.7 },
                { pos: 0.4, alpha: baseOpacity * 0.5 },
                { pos: 0.6, alpha: baseOpacity * 0.6 },
                { pos: 0.8, alpha: baseOpacity * 0.8 },
                { pos: 1, alpha: baseOpacity * 0.9 }
            ];
            gradientStops.forEach(stop => {
                gradientObj.addColorStop(stop.pos, `rgba(255, 255, 255, ${stop.alpha})`);
            });
            ctx.fillStyle = gradientObj;
            // 繪製多層陰影(深度效果)
            if (layer === 0) {
                if (this.options.glow) {
                    // 發光效果
                    ctx.shadowColor = this.options.glowColor;
                    ctx.shadowBlur = this.options.glowIntensity * 40;
                    ctx.shadowOffsetX = 0;
                    ctx.shadowOffsetY = 0;
                }
                else {
                    // 外層深陰影
                    ctx.shadowColor = 'rgba(0, 0, 0, 0.6)';
                    ctx.shadowBlur = 12;
                    ctx.shadowOffsetX = 4;
                    ctx.shadowOffsetY = 4;
                }
            }
            else if (layer === 1) {
                // 中層陰影
                ctx.shadowColor = 'rgba(0, 0, 0, 0.3)';
                ctx.shadowBlur = 6;
                ctx.shadowOffsetX = 2;
                ctx.shadowOffsetY = 2;
            }
            else if (layer === 2) {
                // 內層陰影
                ctx.shadowColor = 'rgba(0, 0, 0, 0.15)';
                ctx.shadowBlur = 3;
                ctx.shadowOffsetX = 1;
                ctx.shadowOffsetY = 1;
            }
            else {
                ctx.shadowColor = 'transparent';
                ctx.shadowBlur = 0;
                ctx.shadowOffsetX = 0;
                ctx.shadowOffsetY = 0;
            }
            // 繪製文字
            ctx.fillText(this.options.children, x, y);
            // 重置陰影
            ctx.shadowColor = 'transparent';
            ctx.shadowBlur = 0;
            ctx.shadowOffsetX = 0;
            ctx.shadowOffsetY = 0;
        }
        // 應用高級毛玻璃效果
        const imageData = ctx.getImageData(0, 0, this.canvas.width, this.canvas.height);
        const data = imageData.data;
        for (let i = 0; i < data.length; i += 4) {
            const r = data[i] || 0;
            const g = data[i + 1] || 0;
            const b = data[i + 2] || 0;
            const a = data[i + 3] || 0;
            if (a > 0) {
                const intensity = a / 255;
                // 應用強烈的亮度調整
                data[i] = Math.min(255, r * this.options.brightness * 1.4);
                data[i + 1] = Math.min(255, g * this.options.brightness * 1.4);
                data[i + 2] = Math.min(255, b * this.options.brightness * 1.4);
                // 應用對比度調整
                data[i] = Math.min(255, (data[i] - 128) * this.options.contrast + 128);
                data[i + 1] = Math.min(255, (data[i + 1] - 128) * this.options.contrast + 128);
                data[i + 2] = Math.min(255, (data[i + 2] - 128) * this.options.contrast + 128);
                // 應用飽和度調整
                const gray = (data[i] + data[i + 1] + data[i + 2]) / 3;
                data[i] = Math.min(255, gray + (data[i] - gray) * (this.options.saturation / 100));
                data[i + 1] = Math.min(255, gray + (data[i + 1] - gray) * (this.options.saturation / 100));
                data[i + 2] = Math.min(255, gray + (data[i + 2] - gray) * (this.options.saturation / 100));
                // 創建玻璃透明度效果
                const glassOpacity = Math.min(255, a * (0.6 + intensity * 0.4));
                data[i + 3] = glassOpacity;
            }
        }
        ctx.putImageData(imageData, 0, 0);
        // 應用最終模糊效果
        ctx.filter = `blur(${glassParams.blur * 0.3}px) saturate(${glassParams.saturation}%) brightness(${glassParams.brightness}) contrast(${glassParams.contrast})`;
        ctx.globalAlpha = 0.95;
        ctx.drawImage(this.canvas, 0, 0);
        // 重置濾鏡
        ctx.filter = 'none';
        ctx.globalAlpha = 1;
        this.isLoaded = true;
        this.updateElementClasses();
    }
    // 動畫循環
    animate() {
        if (this.options.animated && this.options.liquid) {
            this.drawGlassText();
            this.animationRef = requestAnimationFrame(() => this.animate());
        }
    }
    // 組件類名
    getComponentClasses() {
        return [
            'gh-typography',
            `gh-typography-${this.options.variant}`,
            `gh-typography-${this.options.size}`,
            `gh-typography-${this.options.weight}`,
            `gh-glass-${this.options.glass}`,
            this.options.liquid ? 'gh-liquid-flow' : '',
            this.options.gradient ? 'gh-gradient' : '',
            this.options.animated ? 'gh-animated' : '',
            this.options.glow ? 'gh-glow' : '',
            this.isLoaded ? 'gh-loaded' : '',
            this.options.className,
        ].filter(Boolean).join(' ');
    }
    // 更新元素類名
    updateElementClasses() {
        this.element.className = this.getComponentClasses();
    }
    // 容器樣式
    getContainerStyle() {
        const style = {
            ...this.options.style,
            fontFamily: this.options.fontFamily,
            letterSpacing: `${this.options.letterSpacing}px`,
            lineHeight: this.options.lineHeight,
            textAlign: this.options.textAlign,
            position: 'relative',
            display: 'inline-block',
            minHeight: Math.max(this.getFontSize() * this.options.lineHeight, this.getFontSize() + 30),
            padding: '15px 0',
        };
        return Object.entries(style)
            .map(([key, value]) => `${key}: ${value}`)
            .join('; ');
    }
    // Canvas 樣式
    getCanvasStyle() {
        const style = {
            position: 'absolute',
            top: '0',
            left: '0',
            width: '100%',
            height: '100%',
            pointerEvents: 'none',
            zIndex: '1',
        };
        return Object.entries(style)
            .map(([key, value]) => `${key}: ${value}`)
            .join('; ');
    }
    // Fallback 樣式
    getFallbackStyle() {
        const style = {
            opacity: this.isLoaded ? '0' : '1',
            position: 'relative',
            zIndex: '2',
            fontFamily: this.options.fontFamily,
            fontSize: `${this.getFontSize()}px`,
            fontWeight: this.getFontWeight(),
            letterSpacing: `${this.options.letterSpacing}px`,
            lineHeight: this.options.lineHeight,
            textAlign: this.options.textAlign,
            color: 'transparent',
        };
        return Object.entries(style)
            .map(([key, value]) => `${key}: ${value}`)
            .join('; ');
    }
    // 更新選項
    updateOptions(newOptions) {
        this.options = { ...this.options, ...newOptions };
        this.element.className = this.getComponentClasses();
        this.element.style.cssText = this.getContainerStyle();
        this.fallback.style.cssText = this.getFallbackStyle();
        this.fallback.textContent = this.options.children;
        if (this.options.animated && this.options.liquid) {
            this.animate();
        }
        else if (this.animationRef) {
            cancelAnimationFrame(this.animationRef);
        }
        this.drawGlassText();
    }
    // 銷毀
    destroy() {
        if (this.animationRef) {
            cancelAnimationFrame(this.animationRef);
        }
        if (this.resizeObserver) {
            this.resizeObserver.disconnect();
        }
        window.removeEventListener('resize', () => this.updateDimensions());
        // 清理 DOM
        if (this.canvas.parentNode) {
            this.canvas.parentNode.removeChild(this.canvas);
        }
        if (this.fallback.parentNode) {
            this.fallback.parentNode.removeChild(this.fallback);
        }
    }
}
// 工廠函數
function createGlassTypography(container, options) {
    return new GlassTypography(container, options);
}
class GlassContainer {
    constructor(options = {}) {
        this.options = {
            size: 'md',
            variant: 'default',
            glass: 'medium',
            interactive: false,
            liquid: false,
            animated: false,
            padding: 'md',
            margin: 'none',
            rounded: 'md',
            shadow: 'md',
            overflow: 'visible',
            position: 'static',
            className: '',
            content: '',
            children: [],
            ...options,
        };
        this.element = this.createElement();
        this.setupEventListeners();
    }
    createElement() {
        const element = document.createElement('div');
        // Set classes
        const classes = this.getContainerClasses();
        element.className = classes;
        // Set styles
        const styles = this.getContainerStyles();
        Object.assign(element.style, styles);
        // Set content
        if (this.options.content) {
            element.innerHTML = this.options.content;
        }
        // Append children
        if (this.options.children && this.options.children.length > 0) {
            this.options.children.forEach(child => {
                element.appendChild(child);
            });
        }
        return element;
    }
    getContainerClasses() {
        const baseClasses = 'gh-container';
        const sizeClasses = `gh-container-${this.options.size}`;
        const variantClasses = this.options.variant !== 'default' ? `gh-container-${this.options.variant}` : '';
        const glassClasses = `gh-glass-${this.options.glass}`;
        const interactiveClasses = this.options.interactive ? 'gh-container-interactive' : '';
        const liquidClasses = this.options.liquid ? 'gh-container-liquid' : '';
        const animatedClasses = this.options.animated ? 'gh-container-animated' : '';
        const paddingClasses = this.options.padding !== 'none' ? `gh-p-${this.options.padding}` : '';
        const marginClasses = this.options.margin !== 'none' ? `gh-m-${this.options.margin}` : '';
        const roundedClasses = this.options.rounded !== 'none' ? `gh-rounded-${this.options.rounded}` : '';
        const shadowClasses = this.options.shadow !== 'none' ? `gh-shadow-${this.options.shadow}` : '';
        const overflowClasses = this.options.overflow !== 'visible' ? `gh-overflow-${this.options.overflow}` : '';
        const positionClasses = this.options.position !== 'static' ? `gh-position-${this.options.position}` : '';
        return [
            baseClasses,
            sizeClasses,
            variantClasses,
            glassClasses,
            interactiveClasses,
            liquidClasses,
            animatedClasses,
            paddingClasses,
            marginClasses,
            roundedClasses,
            shadowClasses,
            overflowClasses,
            positionClasses,
            this.options.className,
        ]
            .filter(Boolean)
            .join(' ');
    }
    getContainerStyles() {
        const styles = {};
        if (this.options.zIndex !== undefined) {
            styles.zIndex = this.options.zIndex.toString();
        }
        return styles;
    }
    setupEventListeners() {
        if (this.options.onClick) {
            this.element.addEventListener('click', this.options.onClick);
        }
        if (this.options.onMouseEnter) {
            this.element.addEventListener('mouseenter', this.options.onMouseEnter);
        }
        if (this.options.onMouseLeave) {
            this.element.addEventListener('mouseleave', this.options.onMouseLeave);
        }
    }
    // Public methods
    render(selector) {
        const target = typeof selector === 'string'
            ? document.querySelector(selector)
            : selector;
        if (target) {
            target.appendChild(this.element);
        }
        else {
            console.error('Target element not found:', selector);
        }
    }
    appendTo(element) {
        element.appendChild(this.element);
    }
    remove() {
        if (this.element.parentNode) {
            this.element.parentNode.removeChild(this.element);
        }
    }
    update(options) {
        Object.assign(this.options, options);
        // Recreate element with new options
        const newElement = this.createElement();
        if (this.element.parentNode) {
            this.element.parentNode.replaceChild(newElement, this.element);
        }
        this.element = newElement;
        this.setupEventListeners();
    }
    getElement() {
        return this.element;
    }
    // Getters for current state
    get size() {
        return this.options.size || 'md';
    }
    get variant() {
        return this.options.variant || 'default';
    }
    get glass() {
        return this.options.glass || 'medium';
    }
    get interactive() {
        return this.options.interactive || false;
    }
    get liquid() {
        return this.options.liquid || false;
    }
    get animated() {
        return this.options.animated || false;
    }
    // Static factory methods
    static create(options = {}) {
        return new GlassContainer(options);
    }
    static fromElement(element, options = {}) {
        const container = new GlassContainer(options);
        container.element = element;
        container.setupEventListeners();
        return container;
    }
}
class GlassNavigation {
    constructor(options = {}) {
        this.isOpen = false;
        this.isScrolled = false;
        this.items = [];
        this.options = {
            variant: 'default',
            glass: 'medium',
            position: 'top',
            size: 'md',
            sticky: false,
            fixed: false,
            liquid: false,
            animated: false,
            blur: true,
            shadow: 'md',
            padding: 'md',
            rounded: 'full',
            className: '',
            ...options,
        };
        this.element = this.createElement();
        this.setupEventListeners();
    }
    createElement() {
        const element = document.createElement('nav');
        // Set classes
        const classes = this.getNavigationClasses();
        element.className = classes;
        // Set styles
        const styles = this.getNavigationStyles();
        Object.assign(element.style, styles);
        // Create container
        const container = document.createElement('div');
        container.className = 'gh-navigation-container';
        element.appendChild(container);
        return element;
    }
    getNavigationClasses() {
        const baseClasses = 'gh-navigation';
        const variantClasses = `gh-navigation-${this.options.variant}`;
        const glassClasses = `gh-glass-${this.options.glass}`;
        const positionClasses = `gh-navigation-${this.options.position}`;
        const sizeClasses = `gh-navigation-${this.options.size}`;
        const stickyClasses = this.options.sticky ? 'gh-navigation-sticky' : '';
        const fixedClasses = this.options.fixed ? 'gh-navigation-fixed' : '';
        const liquidClasses = this.options.liquid ? 'gh-navigation-liquid' : '';
        const animatedClasses = this.options.animated ? 'gh-navigation-animated' : '';
        const blurClasses = this.options.blur ? 'gh-navigation-blur' : '';
        const shadowClasses = this.options.shadow !== 'none' ? `gh-shadow-${this.options.shadow}` : '';
        const paddingClasses = this.options.padding !== 'none' ? `gh-p-${this.options.padding}` : '';
        const roundedClasses = this.options.rounded !== 'none' ? `gh-rounded-${this.options.rounded}` : '';
        const scrolledClasses = this.isScrolled ? 'gh-navigation-scrolled' : '';
        return [
            baseClasses,
            variantClasses,
            glassClasses,
            positionClasses,
            sizeClasses,
            stickyClasses,
            fixedClasses,
            liquidClasses,
            animatedClasses,
            blurClasses,
            shadowClasses,
            paddingClasses,
            roundedClasses,
            scrolledClasses,
            this.options.className,
        ]
            .filter(Boolean)
            .join(' ');
    }
    getNavigationStyles() {
        const styles = {};
        if (this.options.zIndex !== undefined) {
            styles.zIndex = this.options.zIndex.toString();
        }
        return styles;
    }
    setupEventListeners() {
        if (this.options.sticky || this.options.fixed) {
            window.addEventListener('scroll', this.handleScroll.bind(this));
        }
    }
    handleScroll() {
        this.isScrolled = window.scrollY > 10;
        this.updateClasses();
    }
    updateClasses() {
        this.element.className = this.getNavigationClasses();
    }
    // Public methods
    addBrand(text, href, onClick) {
        const brand = document.createElement(href ? 'a' : 'div');
        brand.className = 'gh-navigation-brand';
        brand.textContent = text;
        if (href) {
            brand.href = href;
        }
        if (onClick) {
            brand.addEventListener('click', onClick);
        }
        const container = this.element.querySelector('.gh-navigation-container');
        if (container) {
            container.appendChild(brand);
        }
    }
    addItem(options) {
        const item = new GlassNavigationItem(options);
        this.items.push(item);
        const container = this.element.querySelector('.gh-navigation-container');
        if (container) {
            // Create menu if it doesn't exist
            let menu = container.querySelector('.gh-navigation-menu');
            if (!menu) {
                menu = document.createElement('div');
                menu.className = 'gh-navigation-menu';
                container.appendChild(menu);
            }
            menu.appendChild(item.getElement());
        }
        return item;
    }
    addToggle() {
        const toggle = document.createElement('button');
        toggle.className = 'gh-navigation-toggle';
        toggle.setAttribute('aria-label', 'Toggle navigation menu');
        // Add hamburger lines
        for (let i = 0; i < 3; i++) {
            const line = document.createElement('span');
            line.className = 'gh-navigation-toggle-line';
            toggle.appendChild(line);
        }
        toggle.addEventListener('click', () => {
            this.toggle();
        });
        const container = this.element.querySelector('.gh-navigation-container');
        if (container) {
            container.appendChild(toggle);
        }
    }
    toggle() {
        this.isOpen = !this.isOpen;
        this.updateMenuState();
        this.options.onToggle?.(this.isOpen);
    }
    updateMenuState() {
        const menu = this.element.querySelector('.gh-navigation-menu');
        const toggle = this.element.querySelector('.gh-navigation-toggle');
        if (menu) {
            if (this.isOpen) {
                menu.classList.add('gh-navigation-menu-open');
            }
            else {
                menu.classList.remove('gh-navigation-menu-open');
            }
        }
        if (toggle) {
            if (this.isOpen) {
                toggle.classList.add('gh-navigation-toggle-open');
            }
            else {
                toggle.classList.remove('gh-navigation-toggle-open');
            }
        }
    }
    render(selector) {
        const target = typeof selector === 'string'
            ? document.querySelector(selector)
            : selector;
        if (target) {
            target.appendChild(this.element);
        }
        else {
            console.error('Target element not found:', selector);
        }
    }
    appendTo(element) {
        element.appendChild(this.element);
    }
    remove() {
        if (this.element.parentNode) {
            this.element.parentNode.removeChild(this.element);
        }
    }
    update(options) {
        Object.assign(this.options, options);
        this.updateClasses();
    }
    getElement() {
        return this.element;
    }
    // Static factory methods
    static create(options = {}) {
        return new GlassNavigation(options);
    }
}
class GlassNavigationItem {
    constructor(options) {
        this.options = {
            active: false,
            disabled: false,
            ...options,
        };
        this.element = this.createElement();
        this.setupEventListeners();
    }
    createElement() {
        const isLink = !!this.options.href && !this.options.disabled;
        const element = isLink ? document.createElement('a') : document.createElement('button');
        const baseClasses = 'gh-navigation-item';
        const activeClasses = this.options.active ? 'gh-navigation-item-active' : '';
        const disabledClasses = this.options.disabled ? 'gh-navigation-item-disabled' : '';
        element.className = [baseClasses, activeClasses, disabledClasses]
            .filter(Boolean)
            .join(' ');
        if (isLink) {
            element.href = this.options.href;
        }
        if (this.options.disabled && element instanceof HTMLButtonElement) {
            element.disabled = true;
        }
        // Add icon
        if (this.options.icon) {
            const icon = document.createElement('span');
            icon.className = 'gh-navigation-item-icon';
            icon.textContent = this.options.icon;
            element.appendChild(icon);
        }
        // Add text
        const text = document.createElement('span');
        text.className = 'gh-navigation-item-text';
        text.textContent = this.options.text;
        element.appendChild(text);
        // Add badge
        if (this.options.badge) {
            const badge = document.createElement('span');
            badge.className = 'gh-navigation-item-badge';
            badge.textContent = this.options.badge.toString();
            element.appendChild(badge);
        }
        return element;
    }
    setupEventListeners() {
        t