import { cssManager, colors } from './shared.js'; import { DeesElement, customElement, html, css, type TemplateResult, } from '@design.estate/dees-element'; import * as csInterfaces from '@consent.software/interfaces'; import * as csWebclient from '@consent.software/webclient'; import { delayFor } from '@push.rocks/smartdelay'; declare global { interface HTMLElementTagNameMap { 'consentsoftware-cookieconsent': ConsentsoftwareCookieconsent; } } @customElement('consentsoftware-cookieconsent') export class ConsentsoftwareCookieconsent extends DeesElement { public static demo = () => html``; public csWebclientInstance = new csWebclient.CsWebclient(); public csWebclientRan = false; private isMobile = false; public static styles = [ cssManager.defaultStyles, css` :host { font-size: 14px; user-select: none; } .pageOverlay { position: fixed; inset: 0; display: grid; align-items: center; justify-content: center; z-index: 1000; background: rgba(0, 0, 0, 0); backdrop-filter: blur(0px); transition: background 0.3s ease, backdrop-filter 0.3s ease; } .pageOverlay.shake { background: rgba(0, 0, 0, 0.6) !important; } .modalBox { display: block; color: ${cssManager.bdTheme(colors.light.foreground, colors.dark.foreground)}; background: ${cssManager.bdTheme(colors.light.background, colors.dark.background)}; box-shadow: 0 0 0 1px ${cssManager.bdTheme(colors.light.border, colors.dark.border)}, 0 16px 70px ${cssManager.bdTheme('rgba(0, 0, 0, 0.15)', 'rgba(0, 0, 0, 0.35)')}; position: relative; border-radius: 8px; max-width: 520px; min-width: 320px; box-sizing: border-box; overflow: hidden; will-change: transform, opacity; transition: transform 0.35s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.3s ease; transform: scale(0.92) translateY(20px); opacity: 0; } .modalBox.visible { transform: scale(1) translateY(0); opacity: 1; } .modalBox.hiding { transform: scale(0.95) translateY(10px); opacity: 0; } /* Mobile: slide up from bottom */ @media (max-width: 560px) { .pageOverlay { align-items: flex-end; } .modalBox { max-width: 100%; min-width: 100%; border-radius: 16px 16px 0 0; transform: translateY(100%); opacity: 1; } .modalBox.visible { transform: translateY(0); } .modalBox.hiding { transform: translateY(100%); } } .modalBox.shake { animation: shake 120ms 2 linear; } @keyframes shake { 0% { transform: translateX(3px); } 50% { transform: translateX(-3px); } 100% { transform: translateX(0); } } :host([show='false']) { display: none; } :host([show='true']) { display: block; } .content { margin: auto; } .text-container { padding: 12px 16px; font-size: 0.9em; line-height: 1.5; color: ${cssManager.bdTheme(colors.light.mutedForeground, colors.dark.mutedForeground)}; } .text-container a { color: ${cssManager.bdTheme(colors.light.foreground, colors.dark.foreground)}; text-decoration: underline; text-underline-offset: 2px; } .text-container a:hover { opacity: 0.8; } .button-container { padding: 12px 16px 16px; display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; } @media (max-width: 560px) { .button-container { grid-template-columns: 1fr; padding-bottom: max(16px, env(safe-area-inset-bottom)); } } .consent-button { border-radius: 6px; background: ${cssManager.bdTheme(colors.light.secondary, colors.dark.secondary)}; border: 1px solid ${cssManager.bdTheme(colors.light.border, colors.dark.border)}; padding: 8px 16px; font-size: 0.85em; font-weight: 500; text-align: center; cursor: pointer; transition: all 0.15s ease; color: ${cssManager.bdTheme(colors.light.secondaryForeground, colors.dark.secondaryForeground)}; } .consent-button:hover { background: ${cssManager.bdTheme(colors.light.accent, colors.dark.accent)}; border-color: ${cssManager.bdTheme(colors.light.ring, colors.dark.ring)}; } .consent-button:active { transform: scale(0.98); } .consent-button:last-child { background: ${cssManager.bdTheme(colors.light.primary, colors.dark.primary)}; color: ${cssManager.bdTheme(colors.light.primaryForeground, colors.dark.primaryForeground)}; border-color: ${cssManager.bdTheme(colors.light.primary, colors.dark.primary)}; } .consent-button:last-child:hover { opacity: 0.9; } .info-container { text-align: center; padding: 10px 16px; background: ${cssManager.bdTheme(colors.light.muted, colors.dark.muted)}; border-top: 1px solid ${cssManager.bdTheme(colors.light.border, colors.dark.border)}; font-size: 0.75em; color: ${cssManager.bdTheme(colors.light.mutedForeground, colors.dark.mutedForeground)}; } .info-container a { color: ${cssManager.bdTheme(colors.light.foreground, colors.dark.foreground)}; text-decoration: none; } .info-container a:hover { text-decoration: underline; } `, ]; constructor() { super(); this.setAttribute('show', 'false'); } public render(): TemplateResult { return html`
`; } /** * Lifecycle method called when the element is connected to the DOM. * Displays the consent banner if no cookie levels are set. */ public async connectedCallback() { await super.connectedCallback(); // Check if mobile this.isMobile = window.matchMedia('(max-width: 560px)').matches; const cookieLevel = await this.csWebclientInstance.getCookieLevels(); if (!cookieLevel) { this.setAttribute('show', 'true'); // Small delay to ensure DOM is ready, then animate in await delayFor(50); this.animateIn(); } else { this.setAttribute('show', 'false'); } } public async firstUpdated() { const acceptedCookieLevels = await this.csWebclientInstance.getCookieLevels(); if (!this.csWebclientRan && acceptedCookieLevels) { this.csWebclientRan = true; await this.csWebclientInstance.getAndRunConsentTuples(); } } private animateIn() { const pageOverlay = this.shadowRoot?.querySelector('.pageOverlay') as HTMLDivElement; const modalBox = this.shadowRoot?.querySelector('.modalBox') as HTMLDivElement; if (pageOverlay) { pageOverlay.style.background = 'rgba(0, 0, 0, 0.5)'; pageOverlay.style.backdropFilter = 'blur(4px)'; } if (modalBox) { modalBox.classList.remove('hiding'); modalBox.classList.add('visible'); } } private async animateOut() { const pageOverlay = this.shadowRoot?.querySelector('.pageOverlay') as HTMLDivElement; const modalBox = this.shadowRoot?.querySelector('.modalBox') as HTMLDivElement; if (pageOverlay) { pageOverlay.style.background = 'rgba(0, 0, 0, 0)'; pageOverlay.style.backdropFilter = 'blur(0px)'; } if (modalBox) { modalBox.classList.remove('visible'); modalBox.classList.add('hiding'); } await delayFor(350); } /** * Handles consent button clicks, sets cookie levels, and hides the banner. */ private async handleConsentButtonClick( _event: MouseEvent, levelsArg: csInterfaces.TCookieLevel[] ) { console.log(`Set level to ${levelsArg}`); await this.animateOut(); // Save user consent preferences await this.csWebclientInstance.setCookieLevels(levelsArg); this.setAttribute('show', 'false'); // Run consent scripts if (!this.csWebclientRan) { this.csWebclientRan = true; await this.csWebclientInstance.getAndRunConsentTuples(); } } /** * Handles clicks on the page overlay. If clicked outside the modal, * triggers a shake animation as feedback. */ private async pageOverlayClick(e: MouseEvent) { if (e.target === e.currentTarget) { const modalBox = this.shadowRoot?.querySelector('.modalBox') as HTMLDivElement; if (modalBox) { modalBox.classList.add('shake'); await delayFor(300); modalBox.classList.remove('shake'); } } } }