database schema adjustments to server/realm/scene
This commit is contained in:
parent
a102c96bb4
commit
09590edd95
79 changed files with 7100 additions and 100 deletions
732
stock/index.html
Normal file
732
stock/index.html
Normal file
|
|
@ -0,0 +1,732 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Stock Assets Viewer</title>
|
||||
<style>
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
background: #1a1a2e;
|
||||
color: #eee;
|
||||
min-height: 100vh;
|
||||
margin: 0;
|
||||
padding: 2rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
margin: 0;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
h2 {
|
||||
margin: 0;
|
||||
font-size: 1rem;
|
||||
font-weight: 400;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
/* Tab navigation */
|
||||
.tabs {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tab-btn {
|
||||
padding: 0.75rem 1.5rem;
|
||||
background: #2a2a4e;
|
||||
border: 2px solid #333;
|
||||
border-radius: 8px 8px 0 0;
|
||||
color: #eee;
|
||||
cursor: pointer;
|
||||
font-size: 1rem;
|
||||
transition: background 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.tab-btn:hover {
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.tab-btn:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.tab-btn.active {
|
||||
background: #3a3a5e;
|
||||
border-color: #4ECDC4;
|
||||
border-bottom-color: #3a3a5e;
|
||||
}
|
||||
|
||||
.tab-content {
|
||||
display: none;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 2rem;
|
||||
width: 100%;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.tab-content.active {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
/* Avatar section */
|
||||
.avatar-container {
|
||||
position: relative;
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
}
|
||||
|
||||
.avatar-layer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.avatar-layer svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.control-section {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.color-picker-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
label {
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
input[type="color"] {
|
||||
width: 60px;
|
||||
height: 40px;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.presets {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.preset-btn {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border: 2px solid #333;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s, border-color 0.15s;
|
||||
}
|
||||
|
||||
.preset-btn:hover {
|
||||
transform: scale(1.1);
|
||||
border-color: #fff;
|
||||
}
|
||||
|
||||
.preset-btn:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.emotions {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
max-width: 500px;
|
||||
}
|
||||
|
||||
.emotion-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: #2a2a4e;
|
||||
border: 2px solid #333;
|
||||
border-radius: 8px;
|
||||
color: #eee;
|
||||
cursor: pointer;
|
||||
transition: transform 0.15s, border-color 0.15s, background 0.15s;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.emotion-btn:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: #666;
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.emotion-btn:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.emotion-btn.active {
|
||||
border-color: #4ECDC4;
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.emotion-btn .hotkey {
|
||||
font-size: 0.65rem;
|
||||
color: #888;
|
||||
background: #1a1a2e;
|
||||
padding: 0.1rem 0.3rem;
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
.hint {
|
||||
font-size: 0.75rem;
|
||||
color: #666;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
/* Props section */
|
||||
.props-grid {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
justify-content: center;
|
||||
max-width: 600px;
|
||||
}
|
||||
|
||||
.prop-category {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.prop-category h3 {
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
color: #4ECDC4;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
|
||||
.prop-items {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.prop-card {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 1rem;
|
||||
background: #2a2a4e;
|
||||
border: 2px solid #333;
|
||||
border-radius: 12px;
|
||||
transition: transform 0.15s, border-color 0.15s;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.prop-card:hover {
|
||||
transform: scale(1.05);
|
||||
border-color: #4ECDC4;
|
||||
}
|
||||
|
||||
.prop-card:focus {
|
||||
outline: 2px solid #fff;
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.prop-card.selected {
|
||||
border-color: #4ECDC4;
|
||||
background: #3a3a5e;
|
||||
}
|
||||
|
||||
.prop-preview {
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
}
|
||||
|
||||
.prop-preview svg {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.prop-name {
|
||||
font-size: 0.75rem;
|
||||
color: #aaa;
|
||||
text-transform: capitalize;
|
||||
}
|
||||
|
||||
/* Selected prop display */
|
||||
.selected-prop-container {
|
||||
width: 192px;
|
||||
height: 192px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: #2a2a4e;
|
||||
border-radius: 12px;
|
||||
border: 2px solid #333;
|
||||
}
|
||||
|
||||
.selected-prop-container svg {
|
||||
width: 160px;
|
||||
height: 160px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Stock Assets Viewer</h1>
|
||||
|
||||
<nav class="tabs" role="tablist">
|
||||
<button class="tab-btn active" data-tab="avatars" role="tab" aria-selected="true">Avatars</button>
|
||||
<button class="tab-btn" data-tab="props" role="tab" aria-selected="false">Props</button>
|
||||
<button class="tab-btn" data-tab="flags" role="tab" aria-selected="false">Flags</button>
|
||||
</nav>
|
||||
|
||||
<!-- Avatars Tab -->
|
||||
<section id="avatars-tab" class="tab-content active" role="tabpanel" aria-labelledby="avatars-tab-btn">
|
||||
<div class="avatar-container">
|
||||
<div id="face-layer" class="avatar-layer"></div>
|
||||
<div id="expression-layer" class="avatar-layer"></div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<div class="control-section">
|
||||
<h2>Face Color</h2>
|
||||
<div class="color-picker-row">
|
||||
<input type="color" id="colorPicker" value="#FFCC00" aria-label="Face color picker">
|
||||
</div>
|
||||
<div class="presets" role="group" aria-label="Color presets">
|
||||
<button class="preset-btn" style="background: #FFCC00;" data-color="#FFCC00" title="Yellow" aria-label="Yellow"></button>
|
||||
<button class="preset-btn" style="background: #FF6B6B;" data-color="#FF6B6B" title="Red" aria-label="Red"></button>
|
||||
<button class="preset-btn" style="background: #4ECDC4;" data-color="#4ECDC4" title="Teal" aria-label="Teal"></button>
|
||||
<button class="preset-btn" style="background: #95E1D3;" data-color="#95E1D3" title="Mint" aria-label="Mint"></button>
|
||||
<button class="preset-btn" style="background: #DDA0DD;" data-color="#DDA0DD" title="Plum" aria-label="Plum"></button>
|
||||
<button class="preset-btn" style="background: #87CEEB;" data-color="#87CEEB" title="Sky Blue" aria-label="Sky Blue"></button>
|
||||
<button class="preset-btn" style="background: #FFB347;" data-color="#FFB347" title="Orange" aria-label="Orange"></button>
|
||||
<button class="preset-btn" style="background: #98D8C8;" data-color="#98D8C8" title="Seafoam" aria-label="Seafoam"></button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<h2>Emotion</h2>
|
||||
<div class="emotions" role="group" aria-label="Emotion selection">
|
||||
<button class="emotion-btn" data-emotion="neutral" data-key="0">
|
||||
<span>Neutral</span>
|
||||
<span class="hotkey">0</span>
|
||||
</button>
|
||||
<button class="emotion-btn active" data-emotion="smile" data-key="1">
|
||||
<span>Happy</span>
|
||||
<span class="hotkey">1</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="sad" data-key="2">
|
||||
<span>Sad</span>
|
||||
<span class="hotkey">2</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="angry" data-key="3">
|
||||
<span>Angry</span>
|
||||
<span class="hotkey">3</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="surprised" data-key="4">
|
||||
<span>Surprised</span>
|
||||
<span class="hotkey">4</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="thinking" data-key="5">
|
||||
<span>Thinking</span>
|
||||
<span class="hotkey">5</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="laughing" data-key="6">
|
||||
<span>Laughing</span>
|
||||
<span class="hotkey">6</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="wink" data-key="7">
|
||||
<span>Wink</span>
|
||||
<span class="hotkey">7</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="sleeping" data-key="8">
|
||||
<span>Sleeping</span>
|
||||
<span class="hotkey">8</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="confused" data-key="9">
|
||||
<span>Confused</span>
|
||||
<span class="hotkey">9</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="crying" data-key="c">
|
||||
<span>Crying</span>
|
||||
<span class="hotkey">C</span>
|
||||
</button>
|
||||
<button class="emotion-btn" data-emotion="love" data-key="l">
|
||||
<span>Love</span>
|
||||
<span class="hotkey">L</span>
|
||||
</button>
|
||||
</div>
|
||||
<p class="hint">Press 0-9, C, L to switch emotions</p>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Props Tab -->
|
||||
<section id="props-tab" class="tab-content" role="tabpanel" aria-labelledby="props-tab-btn">
|
||||
<div class="selected-prop-container" id="selected-prop" aria-live="polite"></div>
|
||||
|
||||
<div class="props-grid">
|
||||
<div class="prop-category">
|
||||
<h3>Hookahs</h3>
|
||||
<div class="prop-items" id="hookah-props" role="group" aria-label="Hookah props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Coffee</h3>
|
||||
<div class="prop-items" id="coffee-props" role="group" aria-label="Coffee props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Sodas</h3>
|
||||
<div class="prop-items" id="soda-props" role="group" aria-label="Soda props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Tea</h3>
|
||||
<div class="prop-items" id="tea-props" role="group" aria-label="Tea props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Misc</h3>
|
||||
<div class="prop-items" id="misc-props" role="group" aria-label="Miscellaneous props"></div>
|
||||
</div>
|
||||
|
||||
<div class="prop-category">
|
||||
<h3>Good Pol</h3>
|
||||
<div class="prop-items" id="goodpol-props" role="group" aria-label="Good Pol props"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- Flags Tab -->
|
||||
<section id="flags-tab" class="tab-content" role="tabpanel" aria-labelledby="flags-tab-btn">
|
||||
<div class="selected-prop-container" id="selected-flag" aria-live="polite"></div>
|
||||
|
||||
<div class="props-grid">
|
||||
<div class="prop-category">
|
||||
<h3>National Flags</h3>
|
||||
<div class="prop-items" id="flag-items" role="group" aria-label="National flags"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<script>
|
||||
// Tab switching
|
||||
const tabBtns = document.querySelectorAll('.tab-btn');
|
||||
const tabContents = document.querySelectorAll('.tab-content');
|
||||
|
||||
function switchToTab(tabId, updateHash = true) {
|
||||
const btn = document.querySelector(`.tab-btn[data-tab="${tabId}"]`);
|
||||
if (!btn) return;
|
||||
|
||||
tabBtns.forEach(b => {
|
||||
b.classList.remove('active');
|
||||
b.setAttribute('aria-selected', 'false');
|
||||
});
|
||||
btn.classList.add('active');
|
||||
btn.setAttribute('aria-selected', 'true');
|
||||
|
||||
tabContents.forEach(content => {
|
||||
content.classList.remove('active');
|
||||
});
|
||||
document.getElementById(`${tabId}-tab`).classList.add('active');
|
||||
|
||||
if (updateHash) {
|
||||
window.location.hash = `tab=${tabId}`;
|
||||
}
|
||||
}
|
||||
|
||||
function getTabFromHash() {
|
||||
const hash = window.location.hash;
|
||||
if (!hash) return null;
|
||||
const match = hash.match(/^#tab=(\w+)$/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
tabBtns.forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
switchToTab(btn.dataset.tab);
|
||||
});
|
||||
});
|
||||
|
||||
window.addEventListener('hashchange', () => {
|
||||
const tabId = getTabFromHash();
|
||||
if (tabId) {
|
||||
switchToTab(tabId, false);
|
||||
}
|
||||
});
|
||||
|
||||
// Avatar functionality
|
||||
let currentColor = '#FFCC00';
|
||||
let currentEmotion = 'smile';
|
||||
|
||||
function lightenColor(hex, percent) {
|
||||
const num = parseInt(hex.replace('#', ''), 16);
|
||||
const amt = Math.round(2.55 * percent);
|
||||
const R = Math.min(255, (num >> 16) + amt);
|
||||
const G = Math.min(255, ((num >> 8) & 0x00FF) + amt);
|
||||
const B = Math.min(255, (num & 0x0000FF) + amt);
|
||||
return `#${(1 << 24 | R << 16 | G << 8 | B).toString(16).slice(1).toUpperCase()}`;
|
||||
}
|
||||
|
||||
function darkenColor(hex, percent) {
|
||||
const num = parseInt(hex.replace('#', ''), 16);
|
||||
const amt = Math.round(2.55 * percent);
|
||||
const R = Math.max(0, (num >> 16) - amt);
|
||||
const G = Math.max(0, ((num >> 8) & 0x00FF) - amt);
|
||||
const B = Math.max(0, (num & 0x0000FF) - amt);
|
||||
return `#${(1 << 24 | R << 16 | G << 8 | B).toString(16).slice(1).toUpperCase()}`;
|
||||
}
|
||||
|
||||
async function loadSVG(url, containerId) {
|
||||
const response = await fetch(url);
|
||||
const svgText = await response.text();
|
||||
document.getElementById(containerId).innerHTML = svgText;
|
||||
}
|
||||
|
||||
function updateFaceColor(primaryColor) {
|
||||
currentColor = primaryColor;
|
||||
const faceLayer = document.getElementById('face-layer');
|
||||
const svg = faceLayer.querySelector('svg');
|
||||
if (!svg) return;
|
||||
|
||||
const highlight = lightenColor(primaryColor, 25);
|
||||
const shadow = darkenColor(primaryColor, 20);
|
||||
|
||||
svg.style.setProperty('--face-primary', primaryColor);
|
||||
svg.style.setProperty('--face-highlight', highlight);
|
||||
svg.style.setProperty('--face-shadow', shadow);
|
||||
}
|
||||
|
||||
async function setEmotion(emotion) {
|
||||
currentEmotion = emotion;
|
||||
await loadSVG(`avatar/${emotion}.svg`, 'expression-layer');
|
||||
|
||||
document.querySelectorAll('.emotion-btn').forEach(btn => {
|
||||
btn.classList.toggle('active', btn.dataset.emotion === emotion);
|
||||
});
|
||||
}
|
||||
|
||||
// Props functionality
|
||||
const props = {
|
||||
hookah: ['traditional', 'modern', 'mini', 'ornate', 'tall'],
|
||||
coffee: ['espresso', 'latte', 'iced', 'frenchpress', 'pourover', 'turkish', 'cup-empty'],
|
||||
soda: ['cola', 'lemonlime', 'orange', 'grape', 'rootbeer'],
|
||||
tea: ['iced', 'pot', 'cup', 'cup-empty', 'bag'],
|
||||
misc: ['iou', 'signed-dollar', 'thankyou', 'yousuck'],
|
||||
goodpol: ['cccp', 'china', 'palestine']
|
||||
};
|
||||
|
||||
// Flags
|
||||
const flags = ['chinese'];
|
||||
|
||||
let selectedProp = null;
|
||||
let selectedFlag = null;
|
||||
|
||||
async function loadFlagPreview(name, container) {
|
||||
const filename = `flags/${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
|
||||
const card = document.createElement('button');
|
||||
card.className = 'prop-card';
|
||||
card.dataset.flag = name;
|
||||
card.setAttribute('aria-label', `${name} flag`);
|
||||
|
||||
const preview = document.createElement('div');
|
||||
preview.className = 'prop-preview';
|
||||
preview.innerHTML = svgText;
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'prop-name';
|
||||
label.textContent = name.replace(/([A-Z])/g, ' $1').trim();
|
||||
|
||||
card.appendChild(preview);
|
||||
card.appendChild(label);
|
||||
|
||||
card.addEventListener('click', () => selectFlag(name, card));
|
||||
|
||||
container.appendChild(card);
|
||||
}
|
||||
|
||||
async function selectFlag(name, card) {
|
||||
document.querySelectorAll('#flag-items .prop-card').forEach(c => c.classList.remove('selected'));
|
||||
card.classList.add('selected');
|
||||
|
||||
const filename = `flags/${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
document.getElementById('selected-flag').innerHTML = svgText;
|
||||
selectedFlag = name;
|
||||
}
|
||||
|
||||
async function loadPropPreview(category, name, container) {
|
||||
const filename = `props/${category}-${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
|
||||
const card = document.createElement('button');
|
||||
card.className = 'prop-card';
|
||||
card.dataset.prop = `${category}-${name}`;
|
||||
card.setAttribute('aria-label', `${name} ${category}`);
|
||||
|
||||
const preview = document.createElement('div');
|
||||
preview.className = 'prop-preview';
|
||||
preview.innerHTML = svgText;
|
||||
|
||||
const label = document.createElement('span');
|
||||
label.className = 'prop-name';
|
||||
label.textContent = name.replace(/([A-Z])/g, ' $1').trim();
|
||||
|
||||
card.appendChild(preview);
|
||||
card.appendChild(label);
|
||||
|
||||
card.addEventListener('click', () => selectProp(category, name, card));
|
||||
|
||||
container.appendChild(card);
|
||||
}
|
||||
|
||||
async function selectProp(category, name, card) {
|
||||
document.querySelectorAll('.prop-card').forEach(c => c.classList.remove('selected'));
|
||||
card.classList.add('selected');
|
||||
|
||||
const filename = `props/${category}-${name}.svg`;
|
||||
const response = await fetch(filename);
|
||||
const svgText = await response.text();
|
||||
document.getElementById('selected-prop').innerHTML = svgText;
|
||||
selectedProp = `${category}-${name}`;
|
||||
}
|
||||
|
||||
async function initProps() {
|
||||
const hookahContainer = document.getElementById('hookah-props');
|
||||
const coffeeContainer = document.getElementById('coffee-props');
|
||||
const sodaContainer = document.getElementById('soda-props');
|
||||
const teaContainer = document.getElementById('tea-props');
|
||||
const miscContainer = document.getElementById('misc-props');
|
||||
const goodpolContainer = document.getElementById('goodpol-props');
|
||||
|
||||
for (const name of props.hookah) {
|
||||
await loadPropPreview('hookah', name, hookahContainer);
|
||||
}
|
||||
for (const name of props.coffee) {
|
||||
await loadPropPreview('coffee', name, coffeeContainer);
|
||||
}
|
||||
for (const name of props.soda) {
|
||||
await loadPropPreview('soda', name, sodaContainer);
|
||||
}
|
||||
for (const name of props.tea) {
|
||||
await loadPropPreview('tea', name, teaContainer);
|
||||
}
|
||||
for (const name of props.misc) {
|
||||
await loadPropPreview('misc', name, miscContainer);
|
||||
}
|
||||
for (const name of props.goodpol) {
|
||||
await loadPropPreview('goodpol', name, goodpolContainer);
|
||||
}
|
||||
|
||||
// Select first prop by default
|
||||
const firstCard = document.querySelector('#props-tab .prop-card');
|
||||
if (firstCard) {
|
||||
selectProp('hookah', 'traditional', firstCard);
|
||||
}
|
||||
|
||||
// Load flags
|
||||
const flagContainer = document.getElementById('flag-items');
|
||||
for (const name of flags) {
|
||||
await loadFlagPreview(name, flagContainer);
|
||||
}
|
||||
|
||||
// Select first flag by default
|
||||
const firstFlagCard = document.querySelector('#flag-items .prop-card');
|
||||
if (firstFlagCard) {
|
||||
selectFlag('chinese', firstFlagCard);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize
|
||||
async function init() {
|
||||
// Check for tab in URL hash and switch to it
|
||||
const initialTab = getTabFromHash();
|
||||
if (initialTab) {
|
||||
switchToTab(initialTab, false);
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
loadSVG('avatar/face.svg', 'face-layer'),
|
||||
loadSVG('avatar/smile.svg', 'expression-layer')
|
||||
]);
|
||||
|
||||
updateFaceColor('#FFCC00');
|
||||
|
||||
const colorPicker = document.getElementById('colorPicker');
|
||||
colorPicker.addEventListener('input', (e) => {
|
||||
updateFaceColor(e.target.value);
|
||||
});
|
||||
|
||||
document.querySelectorAll('.preset-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
const color = btn.dataset.color;
|
||||
colorPicker.value = color;
|
||||
updateFaceColor(color);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll('.emotion-btn').forEach(btn => {
|
||||
btn.addEventListener('click', () => {
|
||||
setEmotion(btn.dataset.emotion);
|
||||
});
|
||||
});
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.target.tagName === 'INPUT') return;
|
||||
|
||||
const key = e.key.toLowerCase();
|
||||
if ((key >= '0' && key <= '9') || key === 'c' || key === 'l') {
|
||||
const btn = document.querySelector(`.emotion-btn[data-key="${key}"]`);
|
||||
if (btn) {
|
||||
setEmotion(btn.dataset.emotion);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
await initProps();
|
||||
}
|
||||
|
||||
init();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue