import { DeesElement, customElement, html, property, css, cssManager, state, type TemplateResult, } from '@design.estate/dees-element'; import * as colors from '../00colors.js'; import { cssGeistFontFamily } from '../00fonts.js'; import { zIndexRegistry } from '../00zindex.js'; import '../dees-icon.js'; import '../dees-button.js'; import '../dees-windowlayer.js'; import { DeesWindowLayer } from '../dees-windowlayer.js'; import { ImageCropper } from './profilepicture.cropper.js'; import type { ProfileShape } from './dees-input-profilepicture.js'; @customElement('dees-profilepicture-modal') export class ProfilePictureModal extends DeesElement { @property({ type: String }) public initialImage: string = ''; @property({ type: String }) public shape: ProfileShape = 'round'; @property({ type: Number }) public outputSize: number = 800; @property({ type: Number }) public outputQuality: number = 0.95; @state() private currentStep: 'crop' | 'preview' = 'crop'; @state() private croppedImage: string = ''; @state() private isProcessing: boolean = false; private cropper: ImageCropper | null = null; private windowLayer: any; private zIndex: number = 0; public static styles = [ cssManager.defaultStyles, css` :host { font-family: ${cssGeistFontFamily}; color: ${cssManager.bdTheme('#333', '#fff')}; position: fixed; top: 0; left: 0; right: 0; bottom: 0; display: flex; align-items: center; justify-content: center; z-index: var(--z-index); } .modal-container { background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')}; border-radius: 12px; border: 1px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.08)')}; box-shadow: ${cssManager.bdTheme( '0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04)', '0 20px 25px -5px rgba(0, 0, 0, 0.3), 0 10px 10px -5px rgba(0, 0, 0, 0.2)' )}; width: 480px; max-width: calc(100vw - 32px); display: flex; flex-direction: column; overflow: hidden; transform: translateY(10px) scale(0.98); opacity: 0; animation: modalShow 0.25s cubic-bezier(0.4, 0, 0.2, 1) forwards; } @keyframes modalShow { to { opacity: 1; transform: translateY(0px) scale(1); } } .modal-header { height: 52px; padding: 0 20px; border-bottom: 1px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.06)')}; display: flex; align-items: center; justify-content: center; position: relative; flex-shrink: 0; } .modal-title { font-size: 15px; font-weight: 600; color: ${cssManager.bdTheme('#09090b', '#fafafa')}; letter-spacing: -0.01em; } .close-button { position: absolute; right: 10px; top: 50%; transform: translateY(-50%); width: 32px; height: 32px; border: none; background: transparent; cursor: pointer; border-radius: 8px; display: flex; align-items: center; justify-content: center; color: ${cssManager.bdTheme('#71717a', '#71717a')}; transition: all 0.15s ease; } .close-button:hover { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.05)', 'rgba(255, 255, 255, 0.05)')}; color: ${cssManager.bdTheme('#09090b', '#fafafa')}; } .close-button:active { background: ${cssManager.bdTheme('rgba(0, 0, 0, 0.08)', 'rgba(255, 255, 255, 0.08)')}; } .modal-body { flex: 1; padding: 24px; overflow-y: auto; display: flex; flex-direction: column; align-items: center; gap: 20px; } .cropper-container { width: 100%; max-width: 360px; aspect-ratio: 1; position: relative; background: ${cssManager.bdTheme('#000000', '#000000')}; border-radius: 12px; overflow: hidden; box-shadow: ${cssManager.bdTheme( 'inset 0 2px 4px rgba(0, 0, 0, 0.06)', 'inset 0 2px 4px rgba(0, 0, 0, 0.2)' )}; } .preview-container { display: flex; flex-direction: column; align-items: center; gap: 20px; } .preview-image { width: 180px; height: 180px; object-fit: cover; border: 4px solid ${cssManager.bdTheme('#ffffff', '#18181b')}; box-shadow: ${cssManager.bdTheme( '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)', '0 10px 15px -3px rgba(0, 0, 0, 0.3), 0 4px 6px -2px rgba(0, 0, 0, 0.2)' )}; } .preview-image.round { border-radius: 50%; } .preview-image.square { border-radius: 16px; } .success-message { display: flex; align-items: center; gap: 10px; padding: 10px 20px; background: ${cssManager.bdTheme('#10b981', '#10b981')}; color: white; border-radius: 100px; font-weight: 500; font-size: 14px; animation: successPulse 0.4s ease-out; } @keyframes successPulse { 0% { transform: scale(0.9); opacity: 0; } 50% { transform: scale(1.02); } 100% { transform: scale(1); opacity: 1; } } .modal-footer { padding: 20px 24px; border-top: 1px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.06)', 'rgba(255, 255, 255, 0.06)')}; display: flex; gap: 10px; justify-content: flex-end; } .instructions { text-align: center; color: ${cssManager.bdTheme('#71717a', '#a1a1aa')}; font-size: 13px; line-height: 1.5; max-width: 320px; } .loading-spinner { width: 40px; height: 40px; border: 3px solid ${cssManager.bdTheme('rgba(0, 0, 0, 0.1)', 'rgba(255, 255, 255, 0.1)')}; border-top-color: ${cssManager.bdTheme('#3b82f6', '#60a5fa')}; border-radius: 50%; animation: spin 0.6s linear infinite; } @keyframes spin { to { transform: rotate(360deg); } } @media (max-width: 768px) { .modal-container { width: calc(100vw - 32px); margin: 16px; } .modal-body { padding: 24px; } } `, ]; async connectedCallback() { super.connectedCallback(); // Create window layer first (it will get its own z-index) this.windowLayer = await DeesWindowLayer.createAndShow({ blur: true, }); this.windowLayer.addEventListener('click', () => this.close()); // Now get z-index for modal (will be above window layer) this.zIndex = zIndexRegistry.getNextZIndex(); this.style.setProperty('--z-index', this.zIndex.toString()); // Register with z-index registry zIndexRegistry.register(this, this.zIndex); } async disconnectedCallback() { super.disconnectedCallback(); // Cleanup if (this.cropper) { this.cropper.destroy(); } if (this.windowLayer) { await this.windowLayer.destroy(); } // Unregister from z-index registry zIndexRegistry.unregister(this); } render(): TemplateResult { return html`