|
|
class TabletopObject extends HTMLElement { |
|
|
constructor() { |
|
|
super(); |
|
|
this.attachShadow({ mode: 'open' }); |
|
|
} |
|
|
|
|
|
connectedCallback() { |
|
|
const type = this.getAttribute('type'); |
|
|
const title = this.getAttribute('title'); |
|
|
const color = this.getAttribute('color') || 'blue'; |
|
|
const image = this.getAttribute('image'); |
|
|
|
|
|
this.shadowRoot.innerHTML = ` |
|
|
<style> |
|
|
:host { |
|
|
display: block; |
|
|
position: absolute; |
|
|
cursor: pointer; |
|
|
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); |
|
|
outline: none; |
|
|
} |
|
|
|
|
|
:host(:hover) { |
|
|
transform: translateY(-8px) rotateZ(2deg); |
|
|
filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.3)); |
|
|
z-index: 100; |
|
|
} |
|
|
|
|
|
:host(:focus-visible) { |
|
|
outline: 3px solid #3b82f6; |
|
|
outline-offset: 2px; |
|
|
} |
|
|
|
|
|
.object-container { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
/* Magazine styles */ |
|
|
.magazine { |
|
|
background: linear-gradient(135deg, ${this.getColorShades(color)[0]} 0%, ${this.getColorShades(color)[1]} 100%); |
|
|
border-radius: 4px; |
|
|
box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.magazine::before { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
inset: 0; |
|
|
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent); |
|
|
transform: translateX(-100%); |
|
|
} |
|
|
|
|
|
.magazine:hover::before { |
|
|
animation: shimmer 1.5s infinite; |
|
|
} |
|
|
|
|
|
@keyframes shimmer { |
|
|
100% { transform: translateX(100%); } |
|
|
} |
|
|
|
|
|
.magazine-title { |
|
|
writing-mode: vertical-rl; |
|
|
text-orientation: mixed; |
|
|
color: white; |
|
|
font-weight: bold; |
|
|
font-size: 14px; |
|
|
padding: 12px 8px; |
|
|
text-align: center; |
|
|
height: 100%; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
} |
|
|
|
|
|
/* Resource/file styles */ |
|
|
.file-folder { |
|
|
background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%); |
|
|
border-radius: 8px; |
|
|
box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15); |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.file-folder::after { |
|
|
content: ''; |
|
|
position: absolute; |
|
|
top: 0; |
|
|
right: 12px; |
|
|
width: 30px; |
|
|
height: 10px; |
|
|
background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%); |
|
|
border-radius: 0 0 8px 8px; |
|
|
} |
|
|
|
|
|
.file-label { |
|
|
position: absolute; |
|
|
bottom: 8px; |
|
|
left: 8px; |
|
|
right: 8px; |
|
|
background: rgba(0, 0, 0, 0.7); |
|
|
color: white; |
|
|
padding: 4px 8px; |
|
|
border-radius: 4px; |
|
|
font-size: 12px; |
|
|
} |
|
|
|
|
|
/* Art frame styles */ |
|
|
.art-frame { |
|
|
background: linear-gradient(135deg, #d97706 0%, #92400e 100%); |
|
|
padding: 12px; |
|
|
border-radius: 4px; |
|
|
box-shadow: |
|
|
inset 0 0 20px rgba(0, 0, 0, 0.3), |
|
|
0 10px 30px rgba(0, 0, 0, 0.2); |
|
|
} |
|
|
|
|
|
.art-canvas { |
|
|
background: #f3f4f6; |
|
|
border-radius: 2px; |
|
|
overflow: hidden; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.art-image { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
object-fit: cover; |
|
|
} |
|
|
|
|
|
.art-title { |
|
|
position: absolute; |
|
|
bottom: 0; |
|
|
left: 0; |
|
|
right: 0; |
|
|
background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent); |
|
|
color: white; |
|
|
padding: 12px 8px 8px; |
|
|
font-size: 12px; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
/* Demo device styles */ |
|
|
.device { |
|
|
background: #1f2937; |
|
|
border-radius: 20px; |
|
|
padding: 20px; |
|
|
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3); |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.device-screen { |
|
|
background: #000; |
|
|
border-radius: 10px; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.device-image { |
|
|
width: 100%; |
|
|
height: 100%; |
|
|
object-fit: cover; |
|
|
} |
|
|
|
|
|
.device-title { |
|
|
position: absolute; |
|
|
bottom: -32px; |
|
|
left: 50%; |
|
|
transform: translateX(-50%); |
|
|
background: rgba(255, 255, 255, 0.9); |
|
|
backdrop-filter: blur(8px); |
|
|
padding: 6px 16px; |
|
|
border-radius: 20px; |
|
|
font-size: 14px; |
|
|
font-weight: 500; |
|
|
white-space: nowrap; |
|
|
} |
|
|
</style> |
|
|
|
|
|
<div class="object-container"> |
|
|
${this.renderObject(type, title, color, image)} |
|
|
</div> |
|
|
`; |
|
|
|
|
|
this.setupInteractions(); |
|
|
} |
|
|
|
|
|
getColorShades(color) { |
|
|
const colorMap = { |
|
|
blue: ['#3b82f6', '#1d4ed8'], |
|
|
purple: ['#8b5cf6', '#6d28d9'], |
|
|
green: ['#10b981', '#047857'], |
|
|
red: ['#ef4444', '#dc2626'], |
|
|
orange: ['#f97316', '#ea580c'], |
|
|
pink: ['#ec4899', '#db2777'], |
|
|
yellow: ['#f59e0b', '#d97706'] |
|
|
}; |
|
|
return colorMap[color] || colorMap.blue; |
|
|
} |
|
|
|
|
|
renderObject(type, title, color, image) { |
|
|
switch(type) { |
|
|
case 'magazine': |
|
|
return ` |
|
|
<div class="magazine w-32 h-44"> |
|
|
<div class="magazine-title">${title}</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
case 'resource': |
|
|
return ` |
|
|
<div class="file-folder w-24 h-32"> |
|
|
<div class="file-label">${title}</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
case 'art': |
|
|
return ` |
|
|
<div class="art-frame"> |
|
|
<div class="art-canvas w-48 h-32"> |
|
|
<img src="${image}" alt="${title}" class="art-image"> |
|
|
<div class="art-title">${title}</div> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
case 'demo': |
|
|
return ` |
|
|
<div class="device w-64 h-40"> |
|
|
<div class="device-screen"> |
|
|
<img src="${image}" alt="${title}" class="device-image"> |
|
|
</div> |
|
|
<div class="device-title">${title}</div> |
|
|
</div> |
|
|
`; |
|
|
|
|
|
default: |
|
|
return '<div>Unknown object type</div>'; |
|
|
} |
|
|
} |
|
|
|
|
|
setupInteractions() { |
|
|
this.addEventListener('click', (e) => { |
|
|
this.dispatchEvent(new CustomEvent('object-click', { |
|
|
detail: { |
|
|
id: this.getAttribute('object-id'), |
|
|
type: this.getAttribute('type'), |
|
|
title: this.getAttribute('title') |
|
|
}, |
|
|
bubbles: true |
|
|
})); |
|
|
}); |
|
|
|
|
|
|
|
|
this.addEventListener('mouseenter', () => { |
|
|
this.dispatchEvent(new CustomEvent('object-hover', { |
|
|
detail: { |
|
|
id: this.getAttribute('object-id'), |
|
|
type: this.getAttribute('type'), |
|
|
title: this.getAttribute('title') |
|
|
}, |
|
|
bubbles: true |
|
|
})); |
|
|
}); |
|
|
|
|
|
this.addEventListener('mouseleave', () => { |
|
|
this.dispatchEvent(new CustomEvent('object-unhover', { |
|
|
detail: { |
|
|
id: this.getAttribute('object-id') |
|
|
}, |
|
|
bubbles: true |
|
|
})); |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
customElements.define('tabletop-object', TabletopObject); |