Files
catalog/ts_web/elements/upl-statuspage-footer.ts
2025-06-30 07:24:15 +00:00

642 lines
25 KiB
TypeScript

import { DeesElement, property, html, customElement, type TemplateResult, css, cssManager, unsafeCSS } from '@design.estate/dees-element';
import * as domtools from '@design.estate/dees-domtools';
import { fonts, colors, shadows, borderRadius, spacing, commonStyles } from '../styles/shared.styles.js';
import { demoFunc } from './upl-statuspage-footer.demo.js';
declare global {
interface HTMLElementTagNameMap {
'upl-statuspage-footer': UplStatuspageFooter;
}
}
@customElement('upl-statuspage-footer')
export class UplStatuspageFooter extends DeesElement {
// STATIC
public static demo = demoFunc;
// INSTANCE
@property({ type: String })
public companyName: string = '';
@property({ type: String })
public legalUrl: string = '';
@property({ type: String })
public supportEmail: string = '';
@property({ type: String })
public statusPageUrl: string = '';
@property({ type: Boolean })
public whitelabel: boolean = false;
@property({ type: Number })
public lastUpdated: number | null = null;
@property({ type: Number })
public currentYear: number = new Date().getFullYear();
@property({ type: Array })
public socialLinks: Array<{ platform: string; url: string }> = [];
@property({ type: Array })
public additionalLinks: Array<{ label: string; url: string }> = [];
@property({ type: String })
public rssFeedUrl: string = '';
@property({ type: String })
public apiStatusUrl: string = '';
@property({ type: Boolean })
public loading: boolean = false;
@property({ type: String })
public errorMessage: string | null = null;
@property({ type: Boolean })
public offline: boolean = false;
@property({ type: String })
public latestStatusUpdate: string = '';
@property({ type: Boolean })
public enableSubscribe: boolean = false;
@property({ type: Boolean })
public enableReportIssue: boolean = false;
@property({ type: Boolean })
public enableLanguageSelector: boolean = false;
@property({ type: Boolean })
public enableThemeToggle: boolean = false;
@property({ type: Array })
public languageOptions: Array<{ code: string; label: string }> = [];
@property({ type: String })
public currentLanguage: string = 'en';
@property({ type: String })
public currentTheme: string = 'light';
@property({ type: Number })
public subscriberCount: number = 0;
@property({ type: Object })
public customBranding: { primaryColor?: string; logoUrl?: string; footerText?: string } | null = null;
constructor() {
super();
}
public static styles = [
domtools.elementBasic.staticStyles,
commonStyles,
css`
:host {
display: block;
background: ${colors.background.primary};
font-family: ${unsafeCSS(fonts.base)};
color: ${colors.text.primary};
font-size: 14px;
border-top: 1px solid ${colors.border.default};
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: ${unsafeCSS(spacing['2xl'])} ${unsafeCSS(spacing.lg)};
}
.footer-content {
display: flex;
flex-direction: column;
gap: ${unsafeCSS(spacing.xl)};
}
.footer-main {
display: flex;
justify-content: space-between;
align-items: start;
gap: ${unsafeCSS(spacing['2xl'])};
}
.company-info {
display: flex;
flex-direction: column;
gap: ${unsafeCSS(spacing.lg)};
}
.company-name {
font-size: 16px;
font-weight: 500;
color: ${colors.text.primary};
letter-spacing: -0.01em;
}
.company-links {
display: flex;
flex-wrap: wrap;
gap: ${unsafeCSS(spacing.lg)};
}
.footer-link {
color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
text-decoration: none;
transition: color 0.15s ease;
font-size: 13px;
font-weight: 400;
}
.footer-link:hover {
color: ${colors.text.primary};
}
.footer-actions {
display: flex;
flex-direction: column;
gap: ${unsafeCSS(spacing.md)};
}
.action-button {
padding: 8px 16px;
height: 36px;
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
background: ${cssManager.bdTheme('#ffffff', '#0a0a0a')};
border-radius: 6px;
cursor: pointer;
text-align: center;
transition: all 0.15s ease;
white-space: nowrap;
font-size: 13px;
font-weight: 400;
color: ${colors.text.primary};
font-family: ${unsafeCSS(fonts.base)};
display: inline-flex;
align-items: center;
justify-content: center;
}
.action-button:hover {
background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
border-color: ${cssManager.bdTheme('#d1d5db', '#3f3f46')};
}
.footer-bottom {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: ${unsafeCSS(spacing.lg)};
margin-top: ${unsafeCSS(spacing.lg)};
border-top: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
}
.footer-meta {
display: flex;
gap: ${unsafeCSS(spacing.lg)};
align-items: center;
flex-wrap: wrap;
}
.social-links {
display: flex;
gap: ${unsafeCSS(spacing.md)};
align-items: center;
}
.social-link {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
border-radius: 6px;
transition: all 0.15s ease;
color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
}
.social-link:hover {
color: ${colors.text.primary};
background: ${cssManager.bdTheme('#f3f4f6', '#27272a')};
}
.social-link svg {
width: 16px;
height: 16px;
fill: currentColor;
}
.copyright {
font-size: 13px;
color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
}
.last-updated {
font-size: 12px;
color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
}
.powered-by {
font-size: 12px;
color: ${cssManager.bdTheme('#9ca3af', '#71717a')};
text-align: right;
}
.powered-by a {
color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
text-decoration: none;
transition: color 0.15s ease;
}
.powered-by a:hover {
color: ${colors.text.primary};
}
.status-update {
padding: 12px 16px;
background: ${cssManager.bdTheme('#f9fafb', '#18181b')};
border: 1px solid ${cssManager.bdTheme('#e5e7eb', '#27272a')};
border-radius: 6px;
font-size: 13px;
margin-bottom: ${unsafeCSS(spacing.lg)};
line-height: 1.5;
color: ${cssManager.bdTheme('#4b5563', '#d1d5db')};
}
.language-selector {
position: relative;
}
.language-selector select {
padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.sm)};
border: 1px solid ${colors.border.default};
background: ${colors.background.primary};
color: ${colors.text.primary};
border-radius: ${unsafeCSS(borderRadius.base)};
font-size: 14px;
cursor: pointer;
font-family: ${unsafeCSS(fonts.base)};
}
.theme-toggle {
cursor: pointer;
padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.sm)};
border: 1px solid ${colors.border.default};
background: transparent;
border-radius: ${unsafeCSS(borderRadius.base)};
font-size: 14px;
transition: all 0.2s ease;
color: ${colors.text.primary};
font-family: ${unsafeCSS(fonts.base)};
}
.theme-toggle:hover {
background: ${colors.background.secondary};
border-color: ${colors.border.muted};
}
.subscriber-count {
font-size: 12px;
color: ${colors.text.muted};
margin-top: ${unsafeCSS(spacing.xs)};
}
.error-message {
padding: ${unsafeCSS(spacing.md)};
background: ${cssManager.bdTheme('#fee9e9', '#7f1d1d')};
color: ${cssManager.bdTheme('#dc2626', '#fca5a5')};
border-radius: ${unsafeCSS(borderRadius.base)};
margin-bottom: ${unsafeCSS(spacing.lg)};
font-size: 14px;
border: 1px solid ${cssManager.bdTheme('#fecaca', '#991b1b')};
}
.offline-indicator {
display: inline-flex;
align-items: center;
gap: ${unsafeCSS(spacing.xs)};
padding: ${unsafeCSS(spacing.xs)} ${unsafeCSS(spacing.md)};
background: ${colors.status.degraded};
color: white;
border-radius: ${unsafeCSS(borderRadius.full)};
font-size: 13px;
font-weight: 500;
}
.loading-skeleton {
height: 200px;
background: ${cssManager.bdTheme(
'linear-gradient(90deg, #f3f4f6 25%, #e5e7eb 50%, #f3f4f6 75%)',
'linear-gradient(90deg, #1f1f1f 25%, #262626 50%, #1f1f1f 75%)'
)};
background-size: 200% 100%;
animation: loading 1.5s infinite;
border-radius: ${unsafeCSS(borderRadius.md)};
}
@keyframes loading {
0% { background-position: 200% 0; }
100% { background-position: -200% 0; }
}
.additional-links {
display: flex;
flex-wrap: wrap;
gap: ${unsafeCSS(spacing.md)};
margin-top: ${unsafeCSS(spacing.md)};
}
.additional-link {
font-size: 13px;
color: ${cssManager.bdTheme('#6b7280', '#a1a1aa')};
text-decoration: none;
transition: color 0.15s ease;
}
.additional-link:hover {
color: ${colors.text.primary};
}
@media (max-width: 640px) {
.container {
padding: ${unsafeCSS(spacing.lg)} ${unsafeCSS(spacing.md)};
}
.footer-main {
flex-direction: column;
gap: ${unsafeCSS(spacing.lg)};
}
.footer-bottom {
flex-direction: column;
gap: ${unsafeCSS(spacing.lg)};
align-items: start;
}
.footer-meta {
flex-direction: column;
align-items: start;
gap: ${unsafeCSS(spacing.md)};
}
.company-links {
flex-direction: column;
gap: ${unsafeCSS(spacing.sm)};
}
.powered-by {
text-align: left;
}
}
`
]
public render(): TemplateResult {
if (this.loading) {
return html`
<div class="container">
<div class="loading-skeleton"></div>
</div>
`;
}
return html`
<div class="container">
<div class="footer-content">
${this.errorMessage ? html`
<div class="error-message">${this.errorMessage}</div>
` : ''}
${this.offline ? html`
<div class="offline-indicator">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M6 2V6M6 10H6.01M11 6C11 8.76142 8.76142 11 6 11C3.23858 11 1 8.76142 1 6C1 3.23858 3.23858 1 6 1C8.76142 1 11 3.23858 11 6Z" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
You are currently offline
</div>
` : ''}
${this.latestStatusUpdate ? html`
<div class="status-update">
Latest Update: ${this.latestStatusUpdate}
</div>
` : ''}
<div class="footer-main">
<div class="company-info">
${this.companyName ? html`
<div class="company-name">${this.companyName}</div>
` : ''}
<div class="company-links">
${this.legalUrl && this.isValidUrl(this.legalUrl) ? html`
<a href="${this.legalUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'legal', this.legalUrl)}>Legal</a>
` : ''}
${this.supportEmail && this.isValidEmail(this.supportEmail) ? html`
<a href="mailto:${this.supportEmail}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'support', this.supportEmail)}>Support</a>
` : ''}
${this.statusPageUrl && this.isValidUrl(this.statusPageUrl) ? html`
<a href="${this.statusPageUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'status', this.statusPageUrl)}>Status Page</a>
` : ''}
${this.rssFeedUrl && this.isValidUrl(this.rssFeedUrl) ? html`
<a href="${this.rssFeedUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'rss', this.rssFeedUrl)}>RSS Feed</a>
` : ''}
${this.apiStatusUrl && this.isValidUrl(this.apiStatusUrl) ? html`
<a href="${this.apiStatusUrl}" class="footer-link" @click=${(e: Event) => this.handleLinkClick(e, 'api', this.apiStatusUrl)}>API Status</a>
` : ''}
</div>
${this.additionalLinks && this.additionalLinks.length > 0 ? html`
<div class="additional-links">
${this.additionalLinks.map(link => html`
${link.label && link.url && this.isValidUrl(link.url) ? html`
<a href="${link.url}" class="additional-link" @click=${(e: Event) => this.handleLinkClick(e, 'additional', link.url, link.label)}>
${link.label}
</a>
` : ''}
`)}
</div>
` : ''}
</div>
<div class="footer-actions">
${this.enableSubscribe ? html`
<button class="action-button" @click=${this.handleSubscribeClick}>
Subscribe to Updates
${this.subscriberCount > 0 ? html`
<div class="subscriber-count">${this.subscriberCount.toLocaleString()} subscribers</div>
` : ''}
</button>
` : ''}
${this.enableReportIssue ? html`
<button class="action-button" @click=${this.handleReportIncidentClick}>
Report an Issue
</button>
` : ''}
${this.enableLanguageSelector && this.languageOptions.length > 0 ? html`
<div class="language-selector">
<select @change=${this.handleLanguageChange} .value=${this.currentLanguage}>
${this.languageOptions.map(option => html`
<option value="${option.code}" ?selected=${option.code === this.currentLanguage}>
${option.label}
</option>
`)}
</select>
</div>
` : ''}
${this.enableThemeToggle ? html`
<button class="theme-toggle" @click=${this.handleThemeToggle}>
${this.currentTheme === 'dark' ? '☀️' : '🌙'} ${this.currentTheme === 'dark' ? 'Light' : 'Dark'} Mode
</button>
` : ''}
</div>
</div>
<div class="footer-bottom">
<div class="footer-meta">
<div class="copyright">
© ${this.currentYear} ${this.companyName || 'Status Page'}
</div>
${this.lastUpdated ? html`
<div class="last-updated">
Last updated: ${this.formatLastUpdated()}
</div>
` : ''}
${this.socialLinks && this.socialLinks.length > 0 ? html`
<div class="social-links">
${this.socialLinks.map(social => html`
${social.platform && social.url && this.isValidUrl(social.url) ? html`
<a href="${social.url}" class="social-link" title="${social.platform}" @click=${(e: Event) => this.handleLinkClick(e, 'social', social.url, social.platform)}>
${this.getSocialIcon(social.platform)}
</a>
` : ''}
`)}
</div>
` : ''}
</div>
${!this.whitelabel ? html`
<div class="powered-by">
${this.customBranding?.footerText || html`
Powered by <a href="https://uptime.link" target="_blank" @click=${(e: Event) => this.handleLinkClick(e, 'powered-by', 'https://uptime.link')}>uptime.link</a>
`}
</div>
` : ''}
</div>
</div>
</div>
`;
}
private isValidUrl(url: string): boolean {
if (!url) return false;
try {
new URL(url);
return true;
} catch {
return url.startsWith('#') || url.startsWith('/');
}
}
private isValidEmail(email: string): boolean {
if (!email) return false;
return email.includes('@');
}
private formatLastUpdated(): string {
if (!this.lastUpdated) return 'Never';
const date = new Date(this.lastUpdated);
const now = Date.now();
const diff = now - this.lastUpdated;
if (diff < 60000) {
return 'Just now';
} else if (diff < 3600000) {
const minutes = Math.floor(diff / 60000);
return `${minutes} minute${minutes !== 1 ? 's' : ''} ago`;
} else if (diff < 86400000) {
const hours = Math.floor(diff / 3600000);
return `${hours} hour${hours !== 1 ? 's' : ''} ago`;
} else {
return date.toLocaleDateString();
}
}
private getSocialIcon(platform: string): TemplateResult {
const icons: Record<string, TemplateResult> = {
twitter: html`<svg viewBox="0 0 24 24"><path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z"/></svg>`,
github: html`<svg viewBox="0 0 24 24"><path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12"/></svg>`,
linkedin: html`<svg viewBox="0 0 24 24"><path d="M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433c-1.144 0-2.063-.926-2.063-2.065 0-1.138.92-2.063 2.063-2.063 1.14 0 2.064.925 2.064 2.063 0 1.139-.925 2.065-2.064 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z"/></svg>`,
facebook: html`<svg viewBox="0 0 24 24"><path d="M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z"/></svg>`,
youtube: html`<svg viewBox="0 0 24 24"><path d="M23.498 6.186a3.016 3.016 0 0 0-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 0 0 .502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 0 0 2.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 0 0 2.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z"/></svg>`,
instagram: html`<svg viewBox="0 0 24 24"><path d="M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zm0-2.163c-3.259 0-3.667.014-4.947.072-4.358.2-6.78 2.618-6.98 6.98-.059 1.281-.073 1.689-.073 4.948 0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98 1.281.058 1.689.072 4.948.072 3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98-1.281-.059-1.69-.073-4.949-.073zM5.838 12a6.162 6.162 0 1 1 12.324 0 6.162 6.162 0 0 1-12.324 0zM12 16a4 4 0 1 1 0-8 4 4 0 0 1 0 8zm4.965-10.405a1.44 1.44 0 1 1 2.881.001 1.44 1.44 0 0 1-2.881-.001z"/></svg>`,
slack: html`<svg viewBox="0 0 24 24"><path d="M5.042 15.165a2.528 2.528 0 0 1-2.52 2.523A2.528 2.528 0 0 1 0 15.165a2.527 2.527 0 0 1 2.522-2.52h2.52v2.52zM6.313 15.165a2.527 2.527 0 0 1 2.521-2.52 2.527 2.527 0 0 1 2.521 2.52v6.313A2.528 2.528 0 0 1 8.834 24a2.528 2.528 0 0 1-2.521-2.522v-6.313zM8.834 5.042a2.528 2.528 0 0 1-2.521-2.52A2.528 2.528 0 0 1 8.834 0a2.528 2.528 0 0 1 2.521 2.522v2.52H8.834zM8.834 6.313a2.528 2.528 0 0 1 2.521 2.521 2.528 2.528 0 0 1-2.521 2.521H2.522A2.528 2.528 0 0 1 0 8.834a2.528 2.528 0 0 1 2.522-2.521h6.312zM18.956 8.834a2.528 2.528 0 0 1 2.522-2.521A2.528 2.528 0 0 1 24 8.834a2.528 2.528 0 0 1-2.522 2.521h-2.522V8.834zM17.688 8.834a2.528 2.528 0 0 1-2.523 2.521 2.527 2.527 0 0 1-2.52-2.521V2.522A2.527 2.527 0 0 1 15.165 0a2.528 2.528 0 0 1 2.523 2.522v6.312zM15.165 18.956a2.528 2.528 0 0 1 2.523 2.522A2.528 2.528 0 0 1 15.165 24a2.527 2.527 0 0 1-2.52-2.522v-2.522h2.52zM15.165 17.688a2.527 2.527 0 0 1-2.52-2.523 2.526 2.526 0 0 1 2.52-2.52h6.313A2.527 2.527 0 0 1 24 15.165a2.528 2.528 0 0 1-2.522 2.523h-6.313z"/></svg>`,
discord: html`<svg viewBox="0 0 24 24"><path d="M20.317 4.3698a19.7913 19.7913 0 00-4.8851-1.5152.0741.0741 0 00-.0785.0371c-.211.3753-.4447.8648-.6083 1.2495-1.8447-.2762-3.68-.2762-5.4868 0-.1636-.3933-.4058-.8742-.6177-1.2495a.077.077 0 00-.0785-.037 19.7363 19.7363 0 00-4.8852 1.515.0699.0699 0 00-.0321.0277C.5334 9.0458-.319 13.5799.0992 18.0578a.0824.0824 0 00.0312.0561c2.0528 1.5076 4.0413 2.4228 5.9929 3.0294a.0777.0777 0 00.0842-.0276c.4616-.6304.8731-1.2952 1.226-1.9942a.076.076 0 00-.0416-.1057c-.6528-.2476-1.2743-.5495-1.8722-.8923a.077.077 0 01-.0076-.1277c.1258-.0943.2517-.1923.3718-.2914a.0743.0743 0 01.0776-.0105c3.9278 1.7933 8.18 1.7933 12.0614 0a.0739.0739 0 01.0785.0095c.1202.099.246.1981.3728.2924a.077.077 0 01-.0066.1276 12.2986 12.2986 0 01-1.873.8914.0766.0766 0 00-.0407.1067c.3604.698.7719 1.3628 1.225 1.9932a.076.076 0 00.0842.0286c1.961-.6067 3.9495-1.5219 6.0023-3.0294a.077.077 0 00.0313-.0552c.5004-5.177-.8382-9.6739-3.5485-13.6604a.061.061 0 00-.0312-.0286zM8.02 15.3312c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9555-2.4189 2.157-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.9555 2.4189-2.1569 2.4189zm7.9748 0c-1.1825 0-2.1569-1.0857-2.1569-2.419 0-1.3332.9554-2.4189 2.1569-2.4189 1.2108 0 2.1757 1.0952 2.1568 2.419 0 1.3332-.946 2.4189-2.1568 2.4189Z"/></svg>`,
generic: html`<svg viewBox="0 0 24 24"><path d="M13.46,12L19,17.54V19H17.54L12,13.46L6.46,19H5V17.54L10.54,12L5,6.46V5H6.46L12,10.54L17.54,5H19V6.46L13.46,12Z"/></svg>`
};
return icons[platform.toLowerCase()] || icons.generic;
}
private handleLinkClick(event: Event, type: string, url: string, label?: string) {
this.dispatchEvent(new CustomEvent('footerLinkClick', {
detail: { type, url, label },
bubbles: true,
composed: true
}));
}
private handleSubscribeClick() {
this.dispatchEvent(new CustomEvent('subscribeClick', {
bubbles: true,
composed: true
}));
}
private handleReportIncidentClick() {
this.dispatchEvent(new CustomEvent('reportIncidentClick', {
bubbles: true,
composed: true
}));
}
private handleLanguageChange(event: Event) {
const select = event.target as HTMLSelectElement;
const language = select.value;
this.currentLanguage = language;
this.dispatchEvent(new CustomEvent('languageChange', {
detail: { language },
bubbles: true,
composed: true
}));
}
private handleThemeToggle() {
const newTheme = this.currentTheme === 'dark' ? 'light' : 'dark';
this.currentTheme = newTheme;
this.dispatchEvent(new CustomEvent('themeToggle', {
detail: { theme: newTheme },
bubbles: true,
composed: true
}));
}
public dispatchReportNewIncident() {
this.handleReportIncidentClick();
}
public dispatchStatusSubscribe() {
this.handleSubscribeClick();
}
}