chattyness/stock/index.html

752 lines
22 KiB
HTML

<!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 class="prop-category">
<h3>Screens</h3>
<div class="prop-items" id="screen-props" role="group" aria-label="Screen props"></div>
</div>
<div class="prop-category">
<h3>Keyboards</h3>
<div class="prop-items" id="keyboard-props" role="group" aria-label="Keyboard 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', 'businesscard'],
goodpol: ['cccp', 'china', 'palestine'],
screen: ['projector-screen', 'projector-screen-with-stand', 'projector-remote-control'],
keyboard: ['standard']
};
// 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');
const screenContainer = document.getElementById('screen-props');
const keyboardContainer = document.getElementById('keyboard-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);
}
for (const name of props.screen) {
await loadPropPreview('screen', name, screenContainer);
}
for (const name of props.keyboard) {
await loadPropPreview('keyboard', name, keyboardContainer);
}
// 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>