onfroy2/tabletop-gallery-explorer
0
1class TabletopObject extends HTMLElement {2 constructor() {3 super();4 this.attachShadow({ mode: 'open' });5 }6 7 connectedCallback() {8 const type = this.getAttribute('type');9 const title = this.getAttribute('title');10 const color = this.getAttribute('color') || 'blue';11 const image = this.getAttribute('image');12 13 this.shadowRoot.innerHTML = `14 <style>15 :host {16 display: block;17 position: absolute;18 cursor: pointer;19 transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);20 outline: none;21 }22 23 :host(:hover) {24 transform: translateY(-8px) rotateZ(2deg);25 filter: drop-shadow(0 20px 40px rgba(0, 0, 0, 0.3));26 z-index: 100;27 }28 29 :host(:focus-visible) {30 outline: 3px solid #3b82f6;31 outline-offset: 2px;32 }33 34 .object-container {35 width: 100%;36 height: 100%;37 position: relative;38 }39 40 /* Magazine styles */41 .magazine {42 background: linear-gradient(135deg, ${this.getColorShades(color)[0]} 0%, ${this.getColorShades(color)[1]} 100%);43 border-radius: 4px;44 box-shadow: 0 8px 16px rgba(0, 0, 0, 0.2);45 position: relative;46 overflow: hidden;47 }48 49 .magazine::before {50 content: '';51 position: absolute;52 inset: 0;53 background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.2), transparent);54 transform: translateX(-100%);55 }56 57 .magazine:hover::before {58 animation: shimmer 1.5s infinite;59 }60 61 @keyframes shimmer {62 100% { transform: translateX(100%); }63 }64 65 .magazine-title {66 writing-mode: vertical-rl;67 text-orientation: mixed;68 color: white;69 font-weight: bold;70 font-size: 14px;71 padding: 12px 8px;72 text-align: center;73 height: 100%;74 display: flex;75 align-items: center;76 justify-content: center;77 }78 79 /* Resource/file styles */80 .file-folder {81 background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%);82 border-radius: 8px;83 box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);84 position: relative;85 }86 87 .file-folder::after {88 content: '';89 position: absolute;90 top: 0;91 right: 12px;92 width: 30px;93 height: 10px;94 background: linear-gradient(135deg, #f59e0b 0%, #d97706 100%);95 border-radius: 0 0 8px 8px;96 }97 98 .file-label {99 position: absolute;100 bottom: 8px;101 left: 8px;102 right: 8px;103 background: rgba(0, 0, 0, 0.7);104 color: white;105 padding: 4px 8px;106 border-radius: 4px;107 font-size: 12px;108 }109 110 /* Art frame styles */111 .art-frame {112 background: linear-gradient(135deg, #d97706 0%, #92400e 100%);113 padding: 12px;114 border-radius: 4px;115 box-shadow: 116 inset 0 0 20px rgba(0, 0, 0, 0.3),117 0 10px 30px rgba(0, 0, 0, 0.2);118 }119 120 .art-canvas {121 background: #f3f4f6;122 border-radius: 2px;123 overflow: hidden;124 position: relative;125 }126 127 .art-image {128 width: 100%;129 height: 100%;130 object-fit: cover;131 }132 133 .art-title {134 position: absolute;135 bottom: 0;136 left: 0;137 right: 0;138 background: linear-gradient(to top, rgba(0, 0, 0, 0.8), transparent);139 color: white;140 padding: 12px 8px 8px;141 font-size: 12px;142 font-weight: bold;143 }144 145 /* Demo device styles */146 .device {147 background: #1f2937;148 border-radius: 20px;149 padding: 20px;150 box-shadow: 0 20px 40px rgba(0, 0, 0, 0.3);151 position: relative;152 }153 154 .device-screen {155 background: #000;156 border-radius: 10px;157 overflow: hidden;158 }159 160 .device-image {161 width: 100%;162 height: 100%;163 object-fit: cover;164 }165 166 .device-title {167 position: absolute;168 bottom: -32px;169 left: 50%;170 transform: translateX(-50%);171 background: rgba(255, 255, 255, 0.9);172 backdrop-filter: blur(8px);173 padding: 6px 16px;174 border-radius: 20px;175 font-size: 14px;176 font-weight: 500;177 white-space: nowrap;178 }179 </style>180 181 <div class="object-container">182 ${this.renderObject(type, title, color, image)}183 </div>184 `;185 186 this.setupInteractions();187 }188 189 getColorShades(color) {190 const colorMap = {191 blue: ['#3b82f6', '#1d4ed8'],192 purple: ['#8b5cf6', '#6d28d9'],193 green: ['#10b981', '#047857'],194 red: ['#ef4444', '#dc2626'],195 orange: ['#f97316', '#ea580c'],196 pink: ['#ec4899', '#db2777'],197 yellow: ['#f59e0b', '#d97706']198 };199 return colorMap[color] || colorMap.blue;200 }201 202 renderObject(type, title, color, image) {203 switch(type) {204 case 'magazine':205 return `206 <div class="magazine w-32 h-44">207 <div class="magazine-title">${title}</div>208 </div>209 `;210 211 case 'resource':212 return `213 <div class="file-folder w-24 h-32">214 <div class="file-label">${title}</div>215 </div>216 `;217 218 case 'art':219 return `220 <div class="art-frame">221 <div class="art-canvas w-48 h-32">222 <img src="${image}" alt="${title}" class="art-image">223 <div class="art-title">${title}</div>224 </div>225 </div>226 `;227 228 case 'demo':229 return `230 <div class="device w-64 h-40">231 <div class="device-screen">232 <img src="${image}" alt="${title}" class="device-image">233 </div>234 <div class="device-title">${title}</div>235 </div>236 `;237 238 default:239 return '<div>Unknown object type</div>';240 }241 }242 243 setupInteractions() {244 this.addEventListener('click', (e) => {245 this.dispatchEvent(new CustomEvent('object-click', {246 detail: {247 id: this.getAttribute('object-id'),248 type: this.getAttribute('type'),249 title: this.getAttribute('title')250 },251 bubbles: true252 }));253 });254 255 // Adding hover preview256 this.addEventListener('mouseenter', () => {257 this.dispatchEvent(new CustomEvent('object-hover', {258 detail: {259 id: this.getAttribute('object-id'),260 type: this.getAttribute('type'),261 title: this.getAttribute('title')262 },263 bubbles: true264 }));265 });266 267 this.addEventListener('mouseleave', () => {268 this.dispatchEvent(new CustomEvent('object-unhover', {269 detail: {270 id: this.getAttribute('object-id')271 },272 bubbles: true273 }));274 });275 }276}277 278customElements.define('tabletop-object', TabletopObject);