Initial commit
This commit is contained in:
@@ -0,0 +1,675 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Asset Comparison Demo - Same Game, Different Assets</title>
|
||||
<style>
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
background: #0f0f1a;
|
||||
color: #e2e8f0;
|
||||
min-height: 100vh;
|
||||
}
|
||||
|
||||
.header {
|
||||
background: #1a1a2e;
|
||||
padding: 1.5rem 2rem;
|
||||
border-bottom: 1px solid #334155;
|
||||
}
|
||||
|
||||
.header h1 {
|
||||
font-size: 1.5rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.header p {
|
||||
color: #94a3b8;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.container {
|
||||
max-width: 1600px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(2, 1fr);
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
@media (max-width: 1200px) {
|
||||
.grid { grid-template-columns: 1fr; }
|
||||
}
|
||||
|
||||
.game-panel {
|
||||
background: #1a1a2e;
|
||||
border-radius: 0.75rem;
|
||||
overflow: hidden;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
|
||||
.panel-header {
|
||||
padding: 1rem 1.5rem;
|
||||
border-bottom: 1px solid #334155;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.panel-header h2 {
|
||||
font-size: 1.125rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.badge {
|
||||
padding: 0.25rem 0.75rem;
|
||||
background: #6366f1;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.badge.space { background: #3b82f6; }
|
||||
.badge.nature { background: #22c55e; }
|
||||
.badge.spooky { background: #8b5cf6; }
|
||||
.badge.retro { background: #f59e0b; }
|
||||
|
||||
.game-frame {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
aspect-ratio: 16/10;
|
||||
background: #0f0f1a;
|
||||
}
|
||||
|
||||
.game-frame canvas {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.panel-footer {
|
||||
padding: 1rem 1.5rem;
|
||||
border-top: 1px solid #334155;
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.tag {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: #252542;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
color: #94a3b8;
|
||||
}
|
||||
|
||||
.controls {
|
||||
margin-top: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: #1a1a2e;
|
||||
border-radius: 0.75rem;
|
||||
border: 1px solid #334155;
|
||||
}
|
||||
|
||||
.controls h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.control-row {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn {
|
||||
padding: 0.625rem 1.25rem;
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background: #818cf8;
|
||||
}
|
||||
|
||||
.btn.secondary {
|
||||
background: #334155;
|
||||
}
|
||||
|
||||
.btn.secondary:hover {
|
||||
background: #475569;
|
||||
}
|
||||
|
||||
.code-section {
|
||||
margin-top: 2rem;
|
||||
}
|
||||
|
||||
.code-section h3 {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.code-tabs {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.code-tab {
|
||||
padding: 0.5rem 1rem;
|
||||
background: #252542;
|
||||
border: none;
|
||||
color: #94a3b8;
|
||||
border-radius: 0.375rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.code-tab.active {
|
||||
background: #6366f1;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.code-block {
|
||||
background: #0f0f1a;
|
||||
border: 1px solid #334155;
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
font-family: 'Monaco', 'Menlo', monospace;
|
||||
font-size: 0.75rem;
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.instructions {
|
||||
margin-top: 2rem;
|
||||
padding: 1.5rem;
|
||||
background: linear-gradient(135deg, #6366f1, #8b5cf6);
|
||||
border-radius: 0.75rem;
|
||||
}
|
||||
|
||||
.instructions h3 {
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.instructions p {
|
||||
opacity: 0.9;
|
||||
font-size: 0.9rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<header class="header">
|
||||
<h1>Asset Comparison Demo</h1>
|
||||
<p>The same dodge-the-obstacles game rendered with 4 different asset combinations</p>
|
||||
</header>
|
||||
|
||||
<div class="container">
|
||||
<div class="grid">
|
||||
<!-- Space Theme -->
|
||||
<div class="game-panel">
|
||||
<div class="panel-header">
|
||||
<h2><span>Space Dodger</span></h2>
|
||||
<span class="badge space">Space Theme</span>
|
||||
</div>
|
||||
<div class="game-frame">
|
||||
<canvas id="game1" width="640" height="400"></canvas>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="tag">space/nebula</span>
|
||||
<span class="tag">Epic music</span>
|
||||
<span class="tag">spaceship-cockpit</span>
|
||||
<span class="tag">HEAD_ONLY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Nature Theme -->
|
||||
<div class="game-panel">
|
||||
<div class="panel-header">
|
||||
<h2><span>Forest Runner</span></h2>
|
||||
<span class="badge nature">Nature Theme</span>
|
||||
</div>
|
||||
<div class="game-frame">
|
||||
<canvas id="game2" width="640" height="400"></canvas>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="tag">nature/forest</span>
|
||||
<span class="tag">Playful music</span>
|
||||
<span class="tag">platformer-standard</span>
|
||||
<span class="tag">FULL_BODY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Spooky Theme -->
|
||||
<div class="game-panel">
|
||||
<div class="panel-header">
|
||||
<h2><span>Haunted Escape</span></h2>
|
||||
<span class="badge spooky">Spooky Theme</span>
|
||||
</div>
|
||||
<div class="game-frame">
|
||||
<canvas id="game3" width="640" height="400"></canvas>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="tag">spooky/hauntedHouse</span>
|
||||
<span class="tag">Mysterious music</span>
|
||||
<span class="tag">adventure-hero</span>
|
||||
<span class="tag">FULL_BODY</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Retro Theme -->
|
||||
<div class="game-panel">
|
||||
<div class="panel-header">
|
||||
<h2><span>Pixel Dash</span></h2>
|
||||
<span class="badge retro">Retro Theme</span>
|
||||
</div>
|
||||
<div class="game-frame">
|
||||
<canvas id="game4" width="640" height="400"></canvas>
|
||||
</div>
|
||||
<div class="panel-footer">
|
||||
<span class="tag">retro/synthwave</span>
|
||||
<span class="tag">Quirky music</span>
|
||||
<span class="tag">runner</span>
|
||||
<span class="tag">FULL_BODY</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="controls">
|
||||
<h3>Global Controls</h3>
|
||||
<div class="control-row">
|
||||
<button class="btn" onclick="startAll()">Start All Games</button>
|
||||
<button class="btn secondary" onclick="pauseAll()">Pause All</button>
|
||||
<button class="btn secondary" onclick="resetAll()">Reset All</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="instructions">
|
||||
<h3>How to Play</h3>
|
||||
<p>
|
||||
Use <strong>Arrow Keys</strong> or <strong>WASD</strong> to move. Avoid the obstacles!
|
||||
Each panel shows the same game logic with different visual assets.
|
||||
Notice how the background, music mood, avatar context, and rendering mode
|
||||
completely change the feel of the game while the core mechanics remain identical.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="code-section">
|
||||
<h3>Configuration Code</h3>
|
||||
<div class="code-tabs">
|
||||
<button class="code-tab active" onclick="showCode('space')">Space</button>
|
||||
<button class="code-tab" onclick="showCode('nature')">Nature</button>
|
||||
<button class="code-tab" onclick="showCode('spooky')">Spooky</button>
|
||||
<button class="code-tab" onclick="showCode('retro')">Retro</button>
|
||||
</div>
|
||||
<div class="code-block" id="codeDisplay">const GAME_CONFIG = {
|
||||
title: "Space Dodger",
|
||||
assets: {
|
||||
music: { mood: "Epic", energy: 8, enabled: true },
|
||||
background: { theme: "space", variant: "nebula", animated: true },
|
||||
avatarContext: {
|
||||
mode: "HEAD_ONLY",
|
||||
context: "spaceship-cockpit",
|
||||
showAccessories: true
|
||||
}
|
||||
}
|
||||
};</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Asset Libraries -->
|
||||
<script src="/assets/backgrounds/backgroundEngine.js"></script>
|
||||
<script src="/assets/backgrounds/effects.js"></script>
|
||||
<script src="/assets/backgrounds/themes/space.js"></script>
|
||||
<script src="/assets/backgrounds/themes/nature.js"></script>
|
||||
<script src="/assets/backgrounds/themes/spooky.js"></script>
|
||||
<script src="/assets/backgrounds/themes/retro.js"></script>
|
||||
<script src="/assets/backgrounds/backgroundCatalog.js"></script>
|
||||
<script src="/assets/avatar/avatarData.js"></script>
|
||||
<script src="/assets/avatar/accessories.js"></script>
|
||||
<script src="/assets/avatar/animations.js"></script>
|
||||
<script src="/assets/avatar/avatarRenderer.js"></script>
|
||||
<script src="/assets/avatar/accessoryRenderer.js"></script>
|
||||
<script src="/assets/avatar/avatarSystem.js"></script>
|
||||
<script src="/assets/avatar/contexts/vehicles.js"></script>
|
||||
<script src="/assets/avatar/contexts/pure.js"></script>
|
||||
<script src="/assets/avatar/contextRenderer.js"></script>
|
||||
|
||||
<script>
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// COMPARISON DEMO - Same game, 4 different asset sets
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
const CONFIGS = {
|
||||
space: {
|
||||
title: "Space Dodger",
|
||||
assets: {
|
||||
music: { mood: "Epic", energy: 8 },
|
||||
background: { theme: "space", variant: "nebula" },
|
||||
avatar: { mode: "HEAD_ONLY", context: "spaceship-cockpit" }
|
||||
},
|
||||
colors: {
|
||||
player: '#3b82f6',
|
||||
obstacle: '#ef4444',
|
||||
coin: '#fbbf24'
|
||||
}
|
||||
},
|
||||
nature: {
|
||||
title: "Forest Runner",
|
||||
assets: {
|
||||
music: { mood: "Playful", energy: 6 },
|
||||
background: { theme: "nature", variant: "forest" },
|
||||
avatar: { mode: "FULL_BODY", context: "platformer-standard" }
|
||||
},
|
||||
colors: {
|
||||
player: '#22c55e',
|
||||
obstacle: '#a16207',
|
||||
coin: '#fbbf24'
|
||||
}
|
||||
},
|
||||
spooky: {
|
||||
title: "Haunted Escape",
|
||||
assets: {
|
||||
music: { mood: "Mysterious", energy: 5 },
|
||||
background: { theme: "spooky", variant: "hauntedHouse" },
|
||||
avatar: { mode: "FULL_BODY", context: "adventure-hero" }
|
||||
},
|
||||
colors: {
|
||||
player: '#a855f7',
|
||||
obstacle: '#7c3aed',
|
||||
coin: '#c084fc'
|
||||
}
|
||||
},
|
||||
retro: {
|
||||
title: "Pixel Dash",
|
||||
assets: {
|
||||
music: { mood: "Quirky", energy: 7 },
|
||||
background: { theme: "retro", variant: "synthwave" },
|
||||
avatar: { mode: "FULL_BODY", context: "runner" }
|
||||
},
|
||||
colors: {
|
||||
player: '#f59e0b',
|
||||
obstacle: '#ec4899',
|
||||
coin: '#06b6d4'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const playerAvatar = {
|
||||
faceShape: 'round',
|
||||
skinTone: 'medium',
|
||||
hairStyle: 'short',
|
||||
hairColor: '#3d2314',
|
||||
eyeShape: 'round',
|
||||
eyeColor: '#4a7c59'
|
||||
};
|
||||
|
||||
// Game instances
|
||||
const games = {};
|
||||
const keys = {};
|
||||
|
||||
// Input handling
|
||||
document.addEventListener('keydown', e => { keys[e.code] = true; });
|
||||
document.addEventListener('keyup', e => { keys[e.code] = false; });
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// GAME CLASS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
class DodgeGame {
|
||||
constructor(canvasId, config) {
|
||||
this.canvas = document.getElementById(canvasId);
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.config = config;
|
||||
|
||||
this.reset();
|
||||
this.running = false;
|
||||
this.lastTime = 0;
|
||||
}
|
||||
|
||||
reset() {
|
||||
this.playerX = this.canvas.width / 2;
|
||||
this.playerY = this.canvas.height - 80;
|
||||
this.score = 0;
|
||||
this.obstacles = [];
|
||||
this.coins = [];
|
||||
this.animTime = 0;
|
||||
this.spawnTimer = 0;
|
||||
this.coinTimer = 0;
|
||||
}
|
||||
|
||||
start() {
|
||||
this.running = true;
|
||||
this.lastTime = performance.now();
|
||||
this.loop();
|
||||
}
|
||||
|
||||
pause() {
|
||||
this.running = false;
|
||||
}
|
||||
|
||||
loop() {
|
||||
if (!this.running) return;
|
||||
|
||||
const now = performance.now();
|
||||
const dt = Math.min((now - this.lastTime) / 1000, 0.05);
|
||||
this.lastTime = now;
|
||||
|
||||
this.update(dt);
|
||||
this.render();
|
||||
|
||||
requestAnimationFrame(() => this.loop());
|
||||
}
|
||||
|
||||
update(dt) {
|
||||
const speed = 200;
|
||||
|
||||
// Player movement
|
||||
if (keys['ArrowLeft'] || keys['KeyA']) {
|
||||
this.playerX = Math.max(30, this.playerX - speed * dt);
|
||||
}
|
||||
if (keys['ArrowRight'] || keys['KeyD']) {
|
||||
this.playerX = Math.min(this.canvas.width - 30, this.playerX + speed * dt);
|
||||
}
|
||||
|
||||
// Spawn obstacles
|
||||
this.spawnTimer += dt;
|
||||
if (this.spawnTimer > 1.5) {
|
||||
this.spawnTimer = 0;
|
||||
this.obstacles.push({
|
||||
x: 50 + Math.random() * (this.canvas.width - 100),
|
||||
y: -30,
|
||||
size: 20 + Math.random() * 20
|
||||
});
|
||||
}
|
||||
|
||||
// Spawn coins
|
||||
this.coinTimer += dt;
|
||||
if (this.coinTimer > 2) {
|
||||
this.coinTimer = 0;
|
||||
this.coins.push({
|
||||
x: 50 + Math.random() * (this.canvas.width - 100),
|
||||
y: -20
|
||||
});
|
||||
}
|
||||
|
||||
// Update obstacles
|
||||
this.obstacles = this.obstacles.filter(o => {
|
||||
o.y += 150 * dt;
|
||||
|
||||
// Collision
|
||||
const dx = this.playerX - o.x;
|
||||
const dy = this.playerY - o.y;
|
||||
if (Math.sqrt(dx*dx + dy*dy) < o.size + 25) {
|
||||
this.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
return o.y < this.canvas.height + 50;
|
||||
});
|
||||
|
||||
// Update coins
|
||||
this.coins = this.coins.filter(c => {
|
||||
c.y += 120 * dt;
|
||||
|
||||
// Collection
|
||||
const dx = this.playerX - c.x;
|
||||
const dy = this.playerY - c.y;
|
||||
if (Math.sqrt(dx*dx + dy*dy) < 35) {
|
||||
this.score += 10;
|
||||
return false;
|
||||
}
|
||||
|
||||
return c.y < this.canvas.height + 30;
|
||||
});
|
||||
|
||||
this.score += Math.floor(dt * 5);
|
||||
this.animTime += dt * 1000;
|
||||
}
|
||||
|
||||
render() {
|
||||
const { ctx, canvas, config } = this;
|
||||
|
||||
// Background
|
||||
if (window.BackgroundEngine) {
|
||||
window.BackgroundEngine.render(
|
||||
ctx,
|
||||
config.assets.background.theme,
|
||||
config.assets.background.variant,
|
||||
canvas.width,
|
||||
canvas.height,
|
||||
this.animTime
|
||||
);
|
||||
} else {
|
||||
ctx.fillStyle = '#1a1a2e';
|
||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||
}
|
||||
|
||||
// Obstacles
|
||||
ctx.fillStyle = config.colors.obstacle;
|
||||
this.obstacles.forEach(o => {
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(o.x, o.y - o.size);
|
||||
ctx.lineTo(o.x + o.size, o.y + o.size);
|
||||
ctx.lineTo(o.x - o.size, o.y + o.size);
|
||||
ctx.closePath();
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// Coins
|
||||
ctx.fillStyle = config.colors.coin;
|
||||
this.coins.forEach(c => {
|
||||
ctx.beginPath();
|
||||
ctx.arc(c.x, c.y, 12, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
|
||||
// Player
|
||||
ctx.save();
|
||||
ctx.translate(this.playerX, this.playerY);
|
||||
|
||||
const mode = config.assets.avatar.mode;
|
||||
const dims = {
|
||||
'HEAD_ONLY': { w: 64, h: 64, offsetY: 32 },
|
||||
'HEAD_AND_SHOULDERS': { w: 64, h: 96, offsetY: 48 },
|
||||
'UPPER_BODY': { w: 64, h: 128, offsetY: 64 },
|
||||
'FULL_BODY': { w: 64, h: 160, offsetY: 80 }
|
||||
};
|
||||
const d = dims[mode];
|
||||
|
||||
if (window.AvatarSystem) {
|
||||
ctx.translate(-32, -d.offsetY);
|
||||
window.AvatarSystem.render(
|
||||
ctx,
|
||||
playerAvatar,
|
||||
mode,
|
||||
'idle',
|
||||
this.animTime
|
||||
);
|
||||
} else {
|
||||
// Fallback
|
||||
ctx.fillStyle = config.colors.player;
|
||||
ctx.beginPath();
|
||||
ctx.arc(0, 0, 25, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
}
|
||||
|
||||
ctx.restore();
|
||||
|
||||
// Score
|
||||
ctx.fillStyle = 'white';
|
||||
ctx.font = 'bold 16px Arial';
|
||||
ctx.textAlign = 'left';
|
||||
ctx.fillText(`Score: ${this.score}`, 10, 25);
|
||||
}
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// CONTROLS
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
function startAll() {
|
||||
Object.values(games).forEach(g => g.start());
|
||||
}
|
||||
|
||||
function pauseAll() {
|
||||
Object.values(games).forEach(g => g.pause());
|
||||
}
|
||||
|
||||
function resetAll() {
|
||||
Object.values(games).forEach(g => {
|
||||
g.reset();
|
||||
g.render();
|
||||
});
|
||||
}
|
||||
|
||||
function showCode(theme) {
|
||||
document.querySelectorAll('.code-tab').forEach(t => t.classList.remove('active'));
|
||||
event.target.classList.add('active');
|
||||
|
||||
const config = CONFIGS[theme];
|
||||
const code = `const GAME_CONFIG = {
|
||||
title: "${config.title}",
|
||||
assets: {
|
||||
music: { mood: "${config.assets.music.mood}", energy: ${config.assets.music.energy}, enabled: true },
|
||||
background: { theme: "${config.assets.background.theme}", variant: "${config.assets.background.variant}", animated: true },
|
||||
avatarContext: {
|
||||
mode: "${config.assets.avatar.mode}",
|
||||
context: "${config.assets.avatar.context}",
|
||||
showAccessories: true
|
||||
}
|
||||
}
|
||||
};`;
|
||||
document.getElementById('codeDisplay').textContent = code;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
// INITIALIZE
|
||||
// ═══════════════════════════════════════════════════════════════════════
|
||||
|
||||
window.addEventListener('load', () => {
|
||||
// Create game instances
|
||||
games.space = new DodgeGame('game1', CONFIGS.space);
|
||||
games.nature = new DodgeGame('game2', CONFIGS.nature);
|
||||
games.spooky = new DodgeGame('game3', CONFIGS.spooky);
|
||||
games.retro = new DodgeGame('game4', CONFIGS.retro);
|
||||
|
||||
// Initial render
|
||||
Object.values(games).forEach(g => g.render());
|
||||
|
||||
// Auto-start after a moment
|
||||
setTimeout(startAll, 500);
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user