// Create and inject styles
const styles = `
:root {
--background: rgb(253, 251, 247);
--foreground: rgb(74, 59, 51);
--card: rgb(248, 244, 238);
--card-foreground: rgb(74, 59, 51);
--primary: rgb(180, 83, 9);
--primary-foreground: rgb(255, 255, 255);
--secondary: rgb(228, 192, 144);
--secondary-foreground: rgb(87, 83, 78);
--muted: rgb(241, 233, 218);
--muted-foreground: rgb(120, 113, 108);
--accent: rgb(242, 218, 186);
--border: rgb(228, 217, 188);
--ring: rgb(180, 83, 9);
--font-sans: Oxanium, sans-serif;
--font-serif: Merriweather, serif;
--radius: 0.3rem;
--shadow-sm: 0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 1px 2px -1px hsl(28 18% 25% / 0.18);
--shadow-md: 0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 2px 4px -1px hsl(28 18% 25% / 0.18);
--shadow-lg: 0px 2px 3px 0px hsl(28 18% 25% / 0.18), 0px 4px 6px -1px hsl(28 18% 25% / 0.18);
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: var(--font-sans);
background: var(--background);
color: var(--foreground);
min-height: 100vh;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 1.5rem;
}
header {
padding: 1.5rem 0;
border-bottom: 1px solid var(--border);
background: var(--background);
}
.header-content {
display: flex;
justify-content: center;
align-items: center;
}
.logo {
font-size: 1.5rem;
font-weight: 700;
color: var(--primary);
text-decoration: none;
letter-spacing: -0.02em;
}
.hero {
text-align: center;
padding: 4rem 0 3rem;
}
.hero h1 {
font-size: 3rem;
font-weight: 700;
margin-bottom: 1rem;
line-height: 1.1;
background: linear-gradient(135deg, var(--primary), var(--secondary-foreground));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.hero p {
font-size: 1.25rem;
color: var(--muted-foreground);
max-width: 600px;
margin: 0 auto 1rem;
font-family: var(--font-serif);
}
.hero .subtitle {
font-size: 0.95rem;
color: var(--muted-foreground);
font-family: var(--font-sans);
}
.cards-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
padding: 3rem 0 4rem;
}
.card {
background: var(--card);
border: 1px solid var(--border);
border-radius: calc(var(--radius) + 4px);
padding: 2rem;
box-shadow: var(--shadow-md);
transition: all 0.3s ease;
cursor: pointer;
text-decoration: none;
color: var(--foreground);
position: relative;
overflow: hidden;
display: block;
}
.card::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 4px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
transform: scaleX(0);
transition: transform 0.3s ease;
transform-origin: left;
}
.card:hover {
transform: translateY(-8px);
box-shadow: var(--shadow-lg);
border-color: var(--primary);
}
.card:hover::before {
transform: scaleX(1);
}
.card-icon {
width: 60px;
height: 60px;
background: var(--accent);
border-radius: var(--radius);
display: flex;
align-items: center;
justify-content: center;
font-size: 2rem;
margin-bottom: 1.5rem;
box-shadow: var(--shadow-sm);
}
.card h2 {
font-size: 1.5rem;
font-weight: 700;
margin-bottom: 0.75rem;
color: var(--foreground);
}
.card p {
color: var(--muted-foreground);
line-height: 1.6;
margin-bottom: 1.5rem;
font-size: 0.95rem;
}
.card-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding-top: 1rem;
border-top: 1px solid var(--border);
}
.card-tag {
background: var(--muted);
padding: 0.25rem 0.75rem;
border-radius: calc(var(--radius) - 2px);
font-size: 0.75rem;
font-weight: 600;
color: var(--foreground);
}
.card-arrow {
color: var(--primary);
font-size: 1.5rem;
font-weight: 700;
transition: transform 0.3s ease;
}
.card:hover .card-arrow {
transform: translateX(5px);
}
@media (max-width: 768px) {
.hero h1 {
font-size: 2rem;
}
.hero p {
font-size: 1rem;
}
.cards-grid {
grid-template-columns: 1fr;
gap: 1.5rem;
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(30px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.card {
animation: fadeInUp 0.5s ease forwards;
}
.card:nth-child(1) {
animation-delay: 0.1s;
}
.card:nth-child(2) {
animation-delay: 0.2s;
}
.card:nth-child(3) {
animation-delay: 0.3s;
}
/* Chess Arena Styles */
.chess-container {
max-width: 1600px;
margin: 0 auto;
padding: 1rem 1.5rem 2rem;
zoom: 0.85;
}
.chess-header {
margin-bottom: 1.5rem;
}
.back-btn {
display: inline-flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 1rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--primary);
font-size: 0.9rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
margin-bottom: 1.5rem;
}
.back-btn:hover {
background: var(--primary);
color: white;
transform: translateX(-3px);
}
.chess-title {
font-size: 2rem;
font-weight: 700;
margin-bottom: 0.5rem;
background: linear-gradient(135deg, var(--primary), var(--secondary-foreground));
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.chess-subtitle {
font-size: 0.95rem;
color: var(--muted-foreground);
font-family: var(--font-serif);
}
.section-title {
font-size: 1.1rem;
font-weight: 700;
color: var(--foreground);
margin-bottom: 1rem;
display: flex;
align-items: center;
gap: 0.5rem;
}
.section-number {
display: inline-flex;
align-items: center;
justify-content: center;
width: 32px;
height: 32px;
background: var(--primary);
color: white;
border-radius: 50%;
font-size: 0.9rem;
font-weight: 700;
}
.puzzles-section {
margin-bottom: 2rem;
}
.puzzles-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 1rem;
margin-bottom: 1.5rem;
}
.puzzle-card {
background: var(--card);
border: 2px solid var(--border);
border-radius: calc(var(--radius) + 4px);
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
}
.puzzle-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 4px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
transform: scaleX(0);
transition: transform 0.3s ease;
}
.puzzle-card:hover {
transform: translateY(-4px);
box-shadow: var(--shadow-lg);
border-color: var(--primary);
}
.puzzle-card:hover::before {
transform: scaleX(1);
}
.puzzle-card.selected {
border-color: var(--primary);
box-shadow: var(--shadow-lg);
}
.puzzle-card.selected::before {
transform: scaleX(1);
}
.puzzle-image {
width: 100%;
aspect-ratio: 1 / 1;
object-fit: contain;
background: #f5f5f5;
display: block;
}
.puzzle-info {
padding: 0.75rem;
}
.puzzle-title {
font-size: 0.9rem;
font-weight: 700;
color: var(--foreground);
margin-bottom: 0.4rem;
}
.puzzle-difficulty {
display: inline-block;
padding: 0.2rem 0.6rem;
background: var(--accent);
color: var(--foreground);
border-radius: calc(var(--radius) - 2px);
font-size: 0.7rem;
font-weight: 600;
margin-bottom: 0.6rem;
}
.select-btn {
width: 100%;
padding: 0.5rem;
background: var(--muted);
border: none;
border-radius: var(--radius);
color: var(--foreground);
font-size: 0.85rem;
font-weight: 600;
cursor: pointer;
transition: all 0.2s ease;
}
.puzzle-card:hover .select-btn {
background: var(--primary);
color: white;
}
.puzzle-card.selected .select-btn {
background: var(--primary);
color: white;
}
.selection-info {
padding: 0.75rem 1rem;
background: var(--card);
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 1rem;
}
.selection-text {
font-size: 0.85rem;
color: var(--muted-foreground);
margin-bottom: 0.75rem;
}
.send-btn {
padding: 0.75rem 1.5rem;
background: var(--primary);
border: none;
border-radius: var(--radius);
color: white;
font-size: 0.9rem;
font-weight: 700;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: var(--shadow-md);
display: inline-flex;
align-items: center;
gap: 0.5rem;
}
.send-btn:disabled {
background: var(--muted);
color: var(--muted-foreground);
cursor: not-allowed;
box-shadow: none;
}
.send-btn:not(:disabled):hover {
transform: translateY(-2px);
box-shadow: var(--shadow-lg);
}
.results-section {
display: none;
}
.results-section.visible {
display: block;
}
.results-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 1rem;
}
.result-card {
background: var(--card);
border: 1px solid var(--border);
border-radius: calc(var(--radius) + 4px);
padding: 1rem;
box-shadow: var(--shadow-sm);
}
.result-header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 0.75rem;
padding-bottom: 0.75rem;
border-bottom: 1px solid var(--border);
}
.model-name {
font-size: 0.75rem;
font-weight: 700;
color: var(--foreground);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.model-badge {
padding: 0.2rem 0.5rem;
background: var(--accent);
border-radius: calc(var(--radius) - 2px);
font-size: 0.65rem;
font-weight: 600;
color: var(--foreground);
}
.answer-display {
font-family: 'Fira Code', monospace;
font-size: 1.5rem;
font-weight: 700;
text-align: center;
padding: 1.5rem 0.75rem;
background: var(--muted);
border-radius: var(--radius);
min-height: 80px;
display: flex;
align-items: center;
justify-content: center;
color: var(--foreground);
transition: all 0.3s ease;
}
.answer-display.blurred {
filter: blur(10px);
user-select: none;
}
.answer-display.correct {
color: #16a34a;
background: rgba(22, 163, 74, 0.1);
}
.answer-display.incorrect {
color: #dc2626;
background: rgba(220, 38, 38, 0.1);
}
.answer-display.loading {
color: var(--muted-foreground);
font-size: 1rem;
}
.result-footer {
margin-top: 0.75rem;
padding-top: 0.75rem;
border-top: 1px solid var(--border);
font-size: 0.7rem;
color: var(--muted-foreground);
text-align: center;
}
@media (max-width: 1024px) {
.results-grid {
grid-template-columns: 1fr;
}
.puzzles-grid {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 768px) {
.chess-container {
zoom: 0.75;
}
.puzzles-grid {
grid-template-columns: 1fr;
}
.chess-title {
font-size: 1.5rem;
}
}
/* Skateboard Arena Styles */
.skateboard-container {
max-width: 1600px;
margin: 0 auto;
padding: 1rem 1.5rem 2rem;
zoom: 0.85;
}
.skateboard-header {
margin-bottom: 1rem;
}
.skateboard-main-grid {
display: grid;
grid-template-columns: 1fr 1.2fr;
gap: 1.5rem;
align-items: start;
}
.questions-section {
position: sticky;
top: 1rem;
}
.questions-list {
display: grid;
grid-template-columns: 1fr;
gap: 0.5rem;
margin-bottom: 1rem;
max-height: 70vh;
overflow-y: auto;
padding-right: 0.5rem;
}
.questions-list::-webkit-scrollbar {
width: 6px;
}
.questions-list::-webkit-scrollbar-track {
background: var(--muted);
border-radius: 3px;
}
.questions-list::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 3px;
}
.question-card {
background: var(--card);
border: 2px solid var(--border);
border-radius: var(--radius);
padding: 0.75rem;
cursor: pointer;
transition: all 0.3s ease;
position: relative;
display: flex;
align-items: flex-start;
gap: 0.5rem;
}
.question-card::before {
content: '';
position: absolute;
top: 0;
left: 0;
right: 0;
height: 3px;
background: linear-gradient(90deg, var(--primary), var(--secondary));
transform: scaleX(0);
transition: transform 0.3s ease;
}
.question-card:hover {
transform: translateX(4px);
box-shadow: var(--shadow-md);
border-color: var(--primary);
}
.question-card:hover::before {
transform: scaleX(1);
}
.question-card.selected {
border-color: var(--primary);
box-shadow: var(--shadow-md);
background: var(--accent);
}
.question-card.selected::before {
transform: scaleX(1);
}
.question-number {
flex-shrink: 0;
width: 24px;
height: 24px;
background: var(--primary);
color: white;
border-radius: 50%;
text-align: center;
line-height: 24px;
font-size: 0.75rem;
font-weight: 700;
}
.question-text {
flex: 1;
font-size: 0.85rem;
color: var(--foreground);
line-height: 1.4;
}
.skateboard-container .results-grid {
grid-template-columns: 1fr;
gap: 1rem;
}
@media (max-width: 1024px) {
.skateboard-main-grid {
grid-template-columns: 1fr;
}
.questions-section {
position: static;
order: 2;
}
.results-section {
order: 1;
}
.questions-list {
max-height: none;
}
.skateboard-container .results-grid {
grid-template-columns: repeat(3, 1fr);
}
}
@media (max-width: 768px) {
.skateboard-container {
zoom: 0.75;
}
.question-text {
font-size: 0.8rem;
}
.skateboard-container .results-grid {
grid-template-columns: 1fr;
}
}
/* Ask Anything Chat Styles */
.chat-container {
max-width: 1600px;
margin: 0 auto;
padding: 1rem 1.5rem;
height: calc(100vh - 100px);
display: flex;
flex-direction: column;
}
.chat-header {
margin-bottom: 1rem;
flex-shrink: 0;
}
.chat-main {
flex: 1;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 1rem;
overflow: hidden;
min-height: 0;
}
.model-chat-column {
display: flex;
flex-direction: column;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--card);
overflow: hidden;
}
.model-chat-header {
padding: 0.75rem 1rem;
background: var(--muted);
border-bottom: 1px solid var(--border);
display: flex;
align-items: center;
justify-content: space-between;
flex-shrink: 0;
}
.model-chat-title {
font-size: 0.95rem;
font-weight: 700;
color: var(--foreground);
text-transform: uppercase;
letter-spacing: 0.05em;
}
.model-chat-badge {
padding: 0.2rem 0.6rem;
background: var(--accent);
border-radius: calc(var(--radius) - 2px);
font-size: 0.7rem;
font-weight: 600;
color: var(--foreground);
}
.chat-messages {
flex: 1;
overflow-y: auto;
padding: 1rem;
display: flex;
flex-direction: column;
gap: 1rem;
}
.chat-messages::-webkit-scrollbar {
width: 6px;
}
.chat-messages::-webkit-scrollbar-track {
background: var(--muted);
border-radius: 3px;
}
.chat-messages::-webkit-scrollbar-thumb {
background: var(--primary);
border-radius: 3px;
}
.message {
padding: 0.75rem 1rem;
border-radius: var(--radius);
max-width: 90%;
word-wrap: break-word;
line-height: 1.5;
}
.message.user {
background: var(--primary);
color: white;
align-self: flex-end;
margin-left: auto;
}
.message.assistant {
background: var(--muted);
color: var(--foreground);
align-self: flex-start;
}
.message.assistant.streaming {
opacity: 0.8;
}
.chat-input-container {
flex-shrink: 0;
padding: 1rem;
background: var(--card);
border-top: 1px solid var(--border);
}
.chat-input-wrapper {
display: flex;
gap: 0.75rem;
align-items: center;
}
.chat-input {
flex: 1;
padding: 0.875rem 1rem;
border: 2px solid var(--border);
border-radius: var(--radius);
background: var(--background);
color: var(--foreground);
font-size: 0.95rem;
font-family: var(--font-sans);
outline: none;
transition: all 0.2s ease;
resize: none;
min-height: 44px;
max-height: 120px;
}
.chat-input:focus {
border-color: var(--primary);
box-shadow: 0 0 0 2px rgba(180, 83, 9, 0.1);
}
.chat-send-btn {
padding: 0.875rem 1.5rem;
background: var(--primary);
border: none;
border-radius: var(--radius);
color: white;
font-size: 0.95rem;
font-weight: 700;
cursor: pointer;
transition: all 0.2s ease;
white-space: nowrap;
}
.chat-send-btn:hover:not(:disabled) {
background: var(--secondary-foreground);
transform: translateY(-2px);
}
.chat-send-btn:disabled {
background: var(--muted);
color: var(--muted-foreground);
cursor: not-allowed;
}
.empty-state {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100%;
color: var(--muted-foreground);
text-align: center;
padding: 2rem;
}
.empty-state-icon {
font-size: 3rem;
margin-bottom: 1rem;
}
.empty-state-text {
font-size: 1rem;
margin-bottom: 0.5rem;
}
.empty-state-subtext {
font-size: 0.85rem;
opacity: 0.7;
}
@media (max-width: 1024px) {
.chat-main {
grid-template-columns: 1fr;
gap: 0.75rem;
}
.chat-container {
height: auto;
min-height: calc(100vh - 100px);
}
.model-chat-column {
min-height: 400px;
}
}
@media (max-width: 768px) {
.chat-container {
zoom: 0.9;
}
.chat-input-wrapper {
flex-direction: column;
}
.chat-send-btn {
width: 100%;
}
}
`;
// Inject styles into page
const styleSheet = document.createElement('style');
styleSheet.textContent = styles;
document.head.appendChild(styleSheet);
// Add Google Fonts
const fontLink = document.createElement('link');
fontLink.href = 'https://fonts.googleapis.com/css2?family=Oxanium:wght@400;600;700&family=Merriweather:wght@400;700&family=Fira+Code&display=swap';
fontLink.rel = 'stylesheet';
document.head.appendChild(fontLink);
// Create main landing view
const createLandingPage = () => {
const html = `
<header>
<div class="container">
<div class="header-content">
<div class="logo">🤖 LLM Arena</div>
</div>
</div>
</header>
<main>
<section class="hero">
<div class="container">
<h1>Compare AI Models Side-by-Side</h1>
<p>Test and evaluate different language models across various challenges.</p>
<p class="subtitle">Choose your arena and see which model performs best.</p>
</div>
</section>
<section class="container">
<div class="cards-grid">
<div class="card" data-page="chess">
<div class="card-icon">♟️</div>
<h2>Chess Puzzle Solver</h2>
<p>Challenge AI models with chess puzzles and see which one finds the best move. Fully in-sandbox, no extra files.</p>
<div class="card-footer">
<span class="card-tag">Verified Answers</span>
<span class="card-arrow">→</span>
</div>
</div>
<div class="card" data-page="skateboard">
<div class="card-icon">🛹</div>
<h2>Skateboard Q&A</h2>
<p>Test models on skateboarding trick knowledge. AI models identify tricks from descriptions.</p>
<div class="card-footer">
<span class="card-tag">Verified Answers</span>
<span class="card-arrow">→</span>
</div>
</div>
<div class="card" data-page="general">
<div class="card-icon">💬</div>
<h2>Ask Anything</h2>
<p>Compare AI models on any topic. Chat with two powerful models side-by-side and judge the responses yourself.</p>
<div class="card-footer">
<span class="card-tag">User Judged</span>
<span class="card-arrow">→</span>
</div>
</div>
</div>
</section>
</main>
`;
document.body.innerHTML = html;
// Wire in-page navigation
document.querySelectorAll('.card').forEach((card) => {
card.addEventListener('click', () => {
const page = card.getAttribute('data-page');
if (page === 'chess') {
renderChessPuzzleArena();
} else if (page === 'skateboard') {
renderSkateboardArena();
} else if (page === 'general') {
renderAskAnythingArena();
}
});
});
};
// Skateboard trick questions data
const skateboardQuestions = [
{
id: 'question-1',
prompt: 'Board spins 360 degrees backside and flips in the kickflip direction. The skater does not spin.',
correctAnswers: ['tre flip', '360 flip'],
negativeAnswers: ['backside 360 kickflip', 'backside 360 flip', '360 heelflip']
},
{
id: 'question-2',
prompt: 'Board spins 360 degrees frontside and flips in the heelflip direction. The skater does not spin.',
correctAnswers: ['laser flip'],
negativeAnswers: []
},
{
id: 'question-3',
prompt: 'Board spins 180 degrees frontside and flips in the kickflip direction. The skater does not spin.',
correctAnswers: ['hardflip'],
negativeAnswers: ['frontside flip', 'hard luck flip']
},
{
id: 'question-4',
prompt: 'Board spins 180 degrees backside and flips in the kickflip direction. The skater does not spin.',
correctAnswers: ['varial'],
negativeAnswers: ['backside 180 kickflip', 'backside flip']
},
{
id: 'question-5',
prompt: 'Board spins 180 degrees frontside and flips in the heelflip direction. The skater does not spin.',
correctAnswers: ['varial heel'],
negativeAnswers: ['frontside 180 heelflip', 'laser flip']
},
{
id: 'question-6',
prompt: 'Board spins 180 degrees backside and flips in the heelflip direction. The skater does not spin.',
correctAnswers: ['inward heel'],
negativeAnswers: ['varial heel', 'backside heel']
},
{
id: 'question-7',
prompt: 'Board spins 360 degrees backside and flips in the kickflip direction. The skater spins 180 degrees',
correctAnswers: ['big flip', 'bigflip'],
negativeAnswers: []
}
];
// Chess puzzle data (image + correct move)
const chessPuzzles = [
{
id: 'puzzle-1',
title: 'Puzzle 1 (1000)',
imageUrl: '/uploads/adityavardhansharma/1.png',
apiImageUrl: 'https://ancientbrain.com/uploads/adityavardhansharma/1.png',
correctMove: 'Qe8+'
},
{
id: 'puzzle-2',
title: 'Puzzle 2 (1200)',
imageUrl: '/uploads/adityavardhansharma/2-1200.png',
apiImageUrl: 'https://ancientbrain.com/uploads/adityavardhansharma/2-1200.png',
correctMove: 'Rg3'
},
{
id: 'puzzle-3',
title: 'Puzzle 3 (1300)',
imageUrl: '/uploads/adityavardhansharma/3-1300.png',
apiImageUrl: 'https://ancientbrain.com/uploads/adityavardhansharma/3-1300.png',
correctMove: 'g5+'
},
{
id: 'puzzle-4',
title: 'Puzzle 4 (1400)',
imageUrl: '/uploads/adityavardhansharma/4-1400.png',
apiImageUrl: 'https://ancientbrain.com/uploads/adityavardhansharma/4-1400.png',
correctMove: 'Ng6+'
}
];
// Render chess puzzle arena
const renderChessPuzzleArena = () => {
const puzzlesHtml = chessPuzzles
.map(
(p) => `
<div class="puzzle-card" data-puzzle-id="${p.id}">
<img src="${p.imageUrl}" alt="${p.title}" class="puzzle-image" />
<div class="puzzle-info">
<div class="puzzle-title">${p.title}</div>
<div class="puzzle-difficulty">Rating: ${p.title.match(/\d+/)?.[0] || 'N/A'}</div>
<button class="select-btn">Select Puzzle</button>
</div>
</div>
`
)
.join('');
const html = `
<header>
<div class="container">
<div class="header-content">
<div class="logo">🤖 LLM Arena</div>
</div>
</div>
</header>
<main>
<div class="chess-container">
<div class="chess-header">
<button class="back-btn" id="back-to-home">
<span>←</span>
<span>Back to Arena</span>
</button>
<h1 class="chess-title">Chess Puzzle Solver</h1>
<p class="chess-subtitle">Challenge AI models with chess puzzles and compare their tactical abilities</p>
</div>
<div class="puzzles-section">
<h2 class="section-title">
<span class="section-number">1</span>
Choose Your Puzzle
</h2>
<div class="puzzles-grid">
${puzzlesHtml}
</div>
<div class="selection-info">
<div class="selection-text" id="selection-text">
👆 Select a puzzle above to begin the AI comparison
</div>
<button class="send-btn" id="send-btn" disabled>
<span>🚀</span>
<span>Send to AI Models</span>
</button>
</div>
</div>
<div class="results-section" id="results-section">
<h2 class="section-title">
<span class="section-number">2</span>
AI Model Comparison
</h2>
<div class="results-grid">
<div class="result-card">
<div class="result-header">
<span class="model-name">Gemini Flash</span>
<span class="model-badge">Google</span>
</div>
<div class="answer-display loading" id="gemini-answer">
Waiting...
</div>
<div class="result-footer">
Model: google/gemini-2.5-flash-lite
</div>
</div>
<div class="result-card">
<div class="result-header">
<span class="model-name">GPT-5</span>
<span class="model-badge">OpenAI</span>
</div>
<div class="answer-display loading" id="gpt5-answer">
Waiting...
</div>
<div class="result-footer">
Model: openai/gpt-5 (may take 2 minutes to reason)
</div>
</div>
<div class="result-card">
<div class="result-header">
<span class="model-name">Correct Answer</span>
<span class="model-badge">Verified</span>
</div>
<div class="answer-display blurred" id="correct-answer">
?
</div>
<div class="result-footer">
Ground truth solution
</div>
</div>
</div>
</div>
</div>
</main>
`;
document.body.innerHTML = html;
// Back navigation
const backBtn = document.getElementById('back-to-home');
if (backBtn) {
backBtn.addEventListener('click', () => {
createLandingPage();
});
}
let selectedPuzzle = null;
const selectionText = document.getElementById('selection-text');
const sendBtn = document.getElementById('send-btn');
const resultsSection = document.getElementById('results-section');
// Puzzle selection
document.querySelectorAll('.puzzle-card').forEach((card) => {
card.addEventListener('click', () => {
const id = card.getAttribute('data-puzzle-id');
selectedPuzzle = chessPuzzles.find((p) => p.id === id) || null;
document.querySelectorAll('.puzzle-card').forEach((c) => {
c.classList.remove('selected');
});
card.classList.add('selected');
if (selectedPuzzle && selectionText && sendBtn) {
selectionText.textContent = `✓ Selected: ${selectedPuzzle.title} - Ready to send to AI models`;
sendBtn.disabled = false;
}
});
});
// Send to AI
const sendToAi = async () => {
console.log('sendToAi called');
console.log('selectedPuzzle:', selectedPuzzle);
if (!selectedPuzzle) {
console.log('No puzzle selected, returning');
return;
}
const geminiAnswerEl = document.getElementById('gemini-answer');
const gpt5AnswerEl = document.getElementById('gpt5-answer');
const correctAnswerEl = document.getElementById('correct-answer');
console.log('Elements found:', { geminiAnswerEl, gpt5AnswerEl, correctAnswerEl });
if (resultsSection) resultsSection.classList.add('visible');
// Reset elements
if (geminiAnswerEl) {
geminiAnswerEl.textContent = 'Analyzing...';
geminiAnswerEl.classList.add('loading');
geminiAnswerEl.classList.remove('correct', 'incorrect');
}
if (gpt5AnswerEl) {
gpt5AnswerEl.textContent = 'Model may take 2 minutes to reason...';
gpt5AnswerEl.classList.add('loading');
gpt5AnswerEl.classList.remove('correct', 'incorrect');
}
if (correctAnswerEl) {
correctAnswerEl.textContent = selectedPuzzle.correctMove;
correctAnswerEl.classList.add('blurred');
correctAnswerEl.classList.remove('loading', 'correct', 'incorrect');
}
console.log('Sending API image URL:', selectedPuzzle.apiImageUrl);
try {
console.log('Starting API calls...');
// Store answers as they come in
let geminiMove = null;
let gpt5Move = null;
// Start both calls
const geminiPromise = fetchGeminiAnswer(selectedPuzzle.apiImageUrl).then(move => {
geminiMove = move;
console.log('Gemini answered:', geminiMove);
// Show Gemini answer immediately (no color yet)
if (geminiAnswerEl) {
geminiAnswerEl.textContent = geminiMove || 'No response';
geminiAnswerEl.classList.remove('loading');
}
return move;
});
const gpt5Promise = fetchGpt5Answer(selectedPuzzle.apiImageUrl).then(move => {
gpt5Move = move;
console.log('GPT-5 answered:', gpt5Move);
// Show GPT-5 answer immediately (no color yet)
if (gpt5AnswerEl) {
gpt5AnswerEl.textContent = gpt5Move || 'No response';
gpt5AnswerEl.classList.remove('loading');
}
return move;
});
// Wait for both to complete
await Promise.all([geminiPromise, gpt5Promise]);
console.log('Both models answered, revealing correct answer and colors');
// Now reveal correct answer and add colors
const norm = (s) => (s || '').toString().trim().replace(/\s+/g, '').toUpperCase();
const correct = norm(selectedPuzzle.correctMove);
// Unblur correct answer
if (correctAnswerEl) {
correctAnswerEl.classList.remove('blurred');
}
// Add colors to both answers
if (geminiAnswerEl) {
const isCorrect = norm(geminiMove) === correct;
geminiAnswerEl.classList.add(isCorrect ? 'correct' : 'incorrect');
}
if (gpt5AnswerEl) {
const isCorrect = norm(gpt5Move) === correct;
gpt5AnswerEl.classList.add(isCorrect ? 'correct' : 'incorrect');
}
} catch (err) {
console.error('ERROR in sendToAi:', err);
console.error('Error stack:', err.stack);
console.error('Error message:', err.message);
// Unblur correct answer even on error
if (correctAnswerEl) {
correctAnswerEl.classList.remove('blurred');
}
if (geminiAnswerEl && geminiAnswerEl.classList.contains('loading')) {
geminiAnswerEl.textContent = 'Error: ' + err.message;
geminiAnswerEl.classList.remove('loading');
geminiAnswerEl.classList.add('incorrect');
}
if (gpt5AnswerEl && gpt5AnswerEl.classList.contains('loading')) {
gpt5AnswerEl.textContent = 'Error: ' + err.message;
gpt5AnswerEl.classList.remove('loading');
gpt5AnswerEl.classList.add('incorrect');
}
}
console.log('sendToAi completed');
};
if (sendBtn) {
console.log('Send button found and event listener attached');
sendBtn.addEventListener('click', () => {
console.log('Send button clicked!');
sendToAi();
});
} else {
console.error('Send button not found!');
}
};
// Render skateboard Q&A arena
const renderSkateboardArena = () => {
const questionsHtml = skateboardQuestions
.map((q, index) => `
<div class="question-card" data-question-id="${q.id}">
<span class="question-number">${index + 1}</span>
<span class="question-text">${q.prompt}</span>
</div>
`)
.join('');
const html = `
<header>
<div class="container">
<div class="header-content">
<div class="logo">🤖 LLM Arena</div>
</div>
</div>
</header>
<main>
<div class="skateboard-container">
<div class="skateboard-header">
<button class="back-btn" id="back-to-home">
<span>←</span>
<span>Back to Arena</span>
</button>
<h1 class="chess-title">Skateboard Q&A</h1>
<p class="chess-subtitle">Test AI models on skateboarding trick knowledge</p>
</div>
<div class="skateboard-main-grid">
<div class="questions-section">
<h2 class="section-title">
<span class="section-number">1</span>
Select a Question
</h2>
<div class="questions-list">
${questionsHtml}
</div>
<div class="selection-info">
<div class="selection-text" id="skate-selection-text">
👈 Select a question and click send
</div>
<button class="send-btn" id="skate-send-btn" disabled>
<span>🚀</span>
<span>Send to AI Models</span>
</button>
</div>
</div>
<div class="results-section visible" id="results-section">
<h2 class="section-title">
<span class="section-number">2</span>
AI Model Responses
</h2>
<div class="results-grid">
<div class="result-card">
<div class="result-header">
<span class="model-name">GPT-5</span>
<span class="model-badge">OpenAI</span>
</div>
<div class="answer-display" id="gpt5-skate-answer">
Select a question
</div>
<div class="result-footer">
Model: openai/gpt-5 (low reasoning)
</div>
</div>
<div class="result-card">
<div class="result-header">
<span class="model-name">GPT-4.1</span>
<span class="model-badge">OpenAI</span>
</div>
<div class="answer-display" id="gpt4-skate-answer">
Select a question
</div>
<div class="result-footer">
Model: openai/gpt-4o
</div>
</div>
<div class="result-card">
<div class="result-header">
<span class="model-name">Correct Answer</span>
<span class="model-badge">Verified</span>
</div>
<div class="answer-display blurred" id="correct-skate-answer">
?
</div>
<div class="result-footer">
Ground truth solution
</div>
</div>
</div>
</div>
</div>
</div>
</main>
`;
document.body.innerHTML = html;
// Back navigation
const backBtn = document.getElementById('back-to-home');
if (backBtn) {
backBtn.addEventListener('click', () => {
createLandingPage();
});
}
let selectedQuestion = null;
const selectionText = document.getElementById('skate-selection-text');
const sendBtn = document.getElementById('skate-send-btn');
const resultsSection = document.getElementById('results-section');
// Question selection
document.querySelectorAll('.question-card').forEach((card) => {
card.addEventListener('click', () => {
const id = card.getAttribute('data-question-id');
selectedQuestion = skateboardQuestions.find((q) => q.id === id) || null;
document.querySelectorAll('.question-card').forEach((c) => {
c.classList.remove('selected');
});
card.classList.add('selected');
if (selectedQuestion && selectionText && sendBtn) {
selectionText.textContent = `✓ Selected: Question ${id.split('-')[1]} - Ready to send to AI models`;
sendBtn.disabled = false;
}
});
});
// Send to AI
const sendSkateToAi = async () => {
console.log('sendSkateToAi called');
console.log('selectedQuestion:', selectedQuestion);
if (!selectedQuestion) {
console.log('No question selected, returning');
return;
}
const gpt5AnswerEl = document.getElementById('gpt5-skate-answer');
const gpt4AnswerEl = document.getElementById('gpt4-skate-answer');
const correctAnswerEl = document.getElementById('correct-skate-answer');
console.log('Elements found:', { gpt5AnswerEl, gpt4AnswerEl, correctAnswerEl });
// Reset elements
if (gpt5AnswerEl) {
gpt5AnswerEl.textContent = 'Analyzing...';
gpt5AnswerEl.classList.add('loading');
gpt5AnswerEl.classList.remove('correct', 'incorrect');
}
if (gpt4AnswerEl) {
gpt4AnswerEl.textContent = 'Analyzing...';
gpt4AnswerEl.classList.add('loading');
gpt4AnswerEl.classList.remove('correct', 'incorrect');
}
if (correctAnswerEl) {
correctAnswerEl.textContent = selectedQuestion.correctAnswers[0];
correctAnswerEl.classList.add('blurred');
correctAnswerEl.classList.remove('loading', 'correct', 'incorrect');
}
console.log('Sending question prompt:', selectedQuestion.prompt);
try {
console.log('Starting API calls...');
// Store answers as they come in
let gpt5Answer = null;
let gpt4Answer = null;
// Start both calls
const gpt5Promise = fetchGpt5SkateAnswer(selectedQuestion.prompt).then(answer => {
gpt5Answer = answer;
console.log('GPT-5 answered:', gpt5Answer);
// Show GPT-5 answer immediately (no color yet)
if (gpt5AnswerEl) {
gpt5AnswerEl.textContent = gpt5Answer || 'No response';
gpt5AnswerEl.classList.remove('loading');
}
return answer;
});
const gpt4Promise = fetchGpt4SkateAnswer(selectedQuestion.prompt).then(answer => {
gpt4Answer = answer;
console.log('GPT-4.1 answered:', gpt4Answer);
// Show GPT-4 answer immediately (no color yet)
if (gpt4AnswerEl) {
gpt4AnswerEl.textContent = gpt4Answer || 'No response';
gpt4AnswerEl.classList.remove('loading');
}
return answer;
});
// Wait for both to complete
await Promise.all([gpt5Promise, gpt4Promise]);
console.log('Both models answered, revealing correct answer and colors');
// Now reveal correct answer and add colors
const norm = (s) => (s || '').toString().trim().toLowerCase();
const correctAnswers = selectedQuestion.correctAnswers.map(a => norm(a));
const negativeAnswers = selectedQuestion.negativeAnswers.map(a => norm(a));
// Unblur correct answer
if (correctAnswerEl) {
correctAnswerEl.classList.remove('blurred');
}
// Check if GPT-5 answer is correct
if (gpt5AnswerEl) {
const gpt5Normalized = norm(gpt5Answer);
const isCorrect = correctAnswers.some(correct => gpt5Normalized.includes(correct));
const isNegative = negativeAnswers.some(neg => gpt5Normalized.includes(neg));
if (isCorrect && !isNegative) {
gpt5AnswerEl.classList.add('correct');
} else {
gpt5AnswerEl.classList.add('incorrect');
}
}
// Check if GPT-4 answer is correct
if (gpt4AnswerEl) {
const gpt4Normalized = norm(gpt4Answer);
const isCorrect = correctAnswers.some(correct => gpt4Normalized.includes(correct));
const isNegative = negativeAnswers.some(neg => gpt4Normalized.includes(neg));
if (isCorrect && !isNegative) {
gpt4AnswerEl.classList.add('correct');
} else {
gpt4AnswerEl.classList.add('incorrect');
}
}
} catch (err) {
console.error('ERROR in sendSkateToAi:', err);
console.error('Error stack:', err.stack);
console.error('Error message:', err.message);
// Unblur correct answer even on error
if (correctAnswerEl) {
correctAnswerEl.classList.remove('blurred');
}
if (gpt5AnswerEl && gpt5AnswerEl.classList.contains('loading')) {
gpt5AnswerEl.textContent = 'Error: ' + err.message;
gpt5AnswerEl.classList.remove('loading');
gpt5AnswerEl.classList.add('incorrect');
}
if (gpt4AnswerEl && gpt4AnswerEl.classList.contains('loading')) {
gpt4AnswerEl.textContent = 'Error: ' + err.message;
gpt4AnswerEl.classList.remove('loading');
gpt4AnswerEl.classList.add('incorrect');
}
}
console.log('sendSkateToAi completed');
};
if (sendBtn) {
console.log('Send button found and event listener attached');
sendBtn.addEventListener('click', () => {
console.log('Send button clicked!');
sendSkateToAi();
});
} else {
console.error('Send button not found!');
}
};
// Render Ask Anything chat arena
const renderAskAnythingArena = () => {
const html = `
<header>
<div class="container">
<div class="header-content">
<div class="logo">🤖 LLM Arena</div>
</div>
</div>
</header>
<main>
<div class="chat-container">
<div class="chat-header">
<button class="back-btn" id="back-to-home">
<span>←</span>
<span>Back to Arena</span>
</button>
<h1 class="chess-title">Ask Anything</h1>
<p class="chess-subtitle">Chat with two AI models side-by-side and compare their responses</p>
</div>
<div class="chat-main">
<div class="model-chat-column">
<div class="model-chat-header">
<span class="model-chat-title">GPT-OSS-120B</span>
<span class="model-chat-badge">Cerebras</span>
</div>
<div class="chat-messages" id="chat-messages-1">
<div class="empty-state">
<div class="empty-state-icon">💬</div>
<div class="empty-state-text">Start a conversation</div>
<div class="empty-state-subtext">Type a message below to begin</div>
</div>
</div>
</div>
<div class="model-chat-column">
<div class="model-chat-header">
<span class="model-chat-title">Qwen-3-32B</span>
<span class="model-chat-badge">Cerebras</span>
</div>
<div class="chat-messages" id="chat-messages-2">
<div class="empty-state">
<div class="empty-state-icon">💬</div>
<div class="empty-state-text">Start a conversation</div>
<div class="empty-state-subtext">Type a message below to begin</div>
</div>
</div>
</div>
</div>
<div class="chat-input-container">
<div class="chat-input-wrapper">
<textarea
class="chat-input"
id="chat-input"
placeholder="Ask anything... Both models will respond"
rows="1"
></textarea>
<button class="chat-send-btn" id="chat-send-btn">
Send
</button>
</div>
</div>
</div>
</main>
`;
document.body.innerHTML = html;
// Back navigation
const backBtn = document.getElementById('back-to-home');
if (backBtn) {
backBtn.addEventListener('click', () => {
createLandingPage();
});
}
const chatInput = document.getElementById('chat-input');
const sendBtn = document.getElementById('chat-send-btn');
const messagesContainer1 = document.getElementById('chat-messages-1');
const messagesContainer2 = document.getElementById('chat-messages-2');
let conversationHistory = [];
let isProcessing = false;
// Auto-resize textarea
if (chatInput) {
chatInput.addEventListener('input', () => {
chatInput.style.height = 'auto';
chatInput.style.height = Math.min(chatInput.scrollHeight, 120) + 'px';
});
// Send on Enter (but allow Shift+Enter for new line)
chatInput.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
}
if (sendBtn) {
sendBtn.addEventListener('click', () => {
sendMessage();
});
}
const sendMessage = async () => {
if (!chatInput || !messagesContainer1 || !messagesContainer2) return;
const userMessage = chatInput.value.trim();
if (!userMessage || isProcessing) return;
isProcessing = true;
chatInput.disabled = true;
sendBtn.disabled = true;
// Clear empty states if first message
if (conversationHistory.length === 0) {
messagesContainer1.innerHTML = '';
messagesContainer2.innerHTML = '';
}
// Add user message to both columns
const userMsgEl1 = document.createElement('div');
userMsgEl1.className = 'message user';
userMsgEl1.textContent = userMessage;
messagesContainer1.appendChild(userMsgEl1);
const userMsgEl2 = document.createElement('div');
userMsgEl2.className = 'message user';
userMsgEl2.textContent = userMessage;
messagesContainer2.appendChild(userMsgEl2);
// Scroll to bottom
messagesContainer1.scrollTop = messagesContainer1.scrollHeight;
messagesContainer2.scrollTop = messagesContainer2.scrollHeight;
// Clear input
chatInput.value = '';
chatInput.style.height = 'auto';
// Add to conversation history
conversationHistory.push({ role: 'user', content: userMessage });
// Create assistant message placeholders
const assistantMsgEl1 = document.createElement('div');
assistantMsgEl1.className = 'message assistant streaming';
assistantMsgEl1.textContent = 'Thinking...';
messagesContainer1.appendChild(assistantMsgEl1);
const assistantMsgEl2 = document.createElement('div');
assistantMsgEl2.className = 'message assistant streaming';
assistantMsgEl2.textContent = 'Thinking...';
messagesContainer2.appendChild(assistantMsgEl2);
try {
// Call both models in parallel
const [response1, response2] = await Promise.all([
fetchCerebrasGptOss(conversationHistory),
fetchCerebrasQwen(conversationHistory)
]);
// Update UI with responses
assistantMsgEl1.textContent = response1 || 'No response';
assistantMsgEl1.classList.remove('streaming');
assistantMsgEl2.textContent = response2 || 'No response';
assistantMsgEl2.classList.remove('streaming');
// Scroll to bottom
messagesContainer1.scrollTop = messagesContainer1.scrollHeight;
messagesContainer2.scrollTop = messagesContainer2.scrollHeight;
} catch (err) {
console.error('Error fetching responses:', err);
assistantMsgEl1.textContent = 'Error: ' + err.message;
assistantMsgEl1.classList.remove('streaming');
assistantMsgEl2.textContent = 'Error: ' + err.message;
assistantMsgEl2.classList.remove('streaming');
}
isProcessing = false;
chatInput.disabled = false;
sendBtn.disabled = false;
chatInput.focus();
};
};
// OpenRouter API helpers (fetch-based, no external libs).
// Assumes OPENROUTER_API_KEY is available via environment or injected string.
const OPENROUTER_API_KEY = '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';
const fetchGeminiAnswer = async (imageUrl) => {
console.log('fetchGeminiAnswer called with imageUrl:', imageUrl);
if (!OPENROUTER_API_KEY) {
console.warn('Missing OPENROUTER_API_KEY for Gemini request.');
return '';
}
const body = {
model: 'google/gemini-2.5-flash-lite',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Given this chess puzzle image, output ONLY the best move in algebraic notation (e.g., "Qe8+" or "Rg3"). No explanation, no extra words, just the move.'
},
{
type: 'image_url',
image_url: { url: imageUrl }
}
]
}
]
};
console.log('Gemini request body:', JSON.stringify(body, null, 2));
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + OPENROUTER_API_KEY
},
body: JSON.stringify(body)
});
console.log('Gemini response status:', res.status, res.statusText);
if (!res.ok) {
const errorText = await res.text();
console.error('Gemini API error status', res.status, errorText);
return '';
}
const data = await res.json();
console.log('Gemini response data:', data);
const raw = (data.choices?.[0]?.message?.content || '').toString();
console.log('Gemini raw content:', raw);
const filtered = removeThinkTags(raw);
const move = extractMoveOnly(filtered);
console.log('Gemini extracted move:', move);
return move;
};
const fetchGpt5Answer = async (imageUrl) => {
console.log('fetchGpt5Answer called with imageUrl:', imageUrl);
if (!OPENROUTER_API_KEY) {
console.warn('Missing OPENROUTER_API_KEY for GPT-5 request.');
return '';
}
const body = {
model: 'openai/gpt-5',
messages: [
{
role: 'user',
content: [
{
type: 'text',
text: 'Given this chess puzzle image, output ONLY the best move in algebraic notation (e.g., "Qe8+" or "Rg3"). No explanation, no extra words, just the move.'
},
{
type: 'image_url',
image_url: { url: imageUrl }
}
]
}
],
reasoning: {
effort: 'low'
}
};
console.log('GPT-5 request body:', JSON.stringify(body, null, 2));
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + OPENROUTER_API_KEY
},
body: JSON.stringify(body)
});
console.log('GPT-5 response status:', res.status, res.statusText);
if (!res.ok) {
const errorText = await res.text();
console.error('GPT-5 API error status', res.status, errorText);
return '';
}
const data = await res.json();
console.log('GPT-5 response data:', data);
const raw = (data.choices?.[0]?.message?.content || '').toString();
console.log('GPT-5 raw content:', raw);
const filtered = removeThinkTags(raw);
const move = extractMoveOnly(filtered);
console.log('GPT-5 extracted move:', move);
return move;
};
// Skateboard Q&A API functions
const fetchGpt5SkateAnswer = async (prompt) => {
console.log('fetchGpt5SkateAnswer called with prompt:', prompt);
if (!OPENROUTER_API_KEY) {
console.warn('Missing OPENROUTER_API_KEY for GPT-5 skateboard request.');
return '';
}
const body = {
model: 'openai/gpt-5',
messages: [
{
role: 'system',
content: 'You are a skateboard trick naming assistant. You are given a description of a trick and you need to give the name of the trick. If the trick has multiple names, you should give the common name that most skateboarders would use. Keep answers concise and to the point - don\'t include names of other tricks. Only respond with the trick name, nothing else.'
},
{
role: 'user',
content: prompt
}
],
reasoning: {
effort: 'low'
}
};
console.log('GPT-5 skateboard request body:', JSON.stringify(body, null, 2));
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + OPENROUTER_API_KEY
},
body: JSON.stringify(body)
});
console.log('GPT-5 skateboard response status:', res.status, res.statusText);
if (!res.ok) {
const errorText = await res.text();
console.error('GPT-5 skateboard API error status', res.status, errorText);
return '';
}
const data = await res.json();
console.log('GPT-5 skateboard response data:', data);
const answer = (data.choices?.[0]?.message?.content || '').toString().trim();
console.log('GPT-5 skateboard answer:', answer);
return removeThinkTags(answer);
};
const fetchGpt4SkateAnswer = async (prompt) => {
console.log('fetchGpt4SkateAnswer called with prompt:', prompt);
if (!OPENROUTER_API_KEY) {
console.warn('Missing OPENROUTER_API_KEY for GPT-4o skateboard request.');
return '';
}
const body = {
model: 'openai/gpt-4.1',
messages: [
{
role: 'system',
content: 'You are a skateboard trick naming assistant. You are given a description of a trick and you need to give the name of the trick. If the trick has multiple names, you should give the common name that most skateboarders would use. Keep answers concise and to the point - don\'t include names of other tricks. Only respond with the trick name, nothing else.'
},
{
role: 'user',
content: prompt
}
]
};
console.log('GPT-4o skateboard request body:', JSON.stringify(body, null, 2));
const res = await fetch('https://openrouter.ai/api/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + OPENROUTER_API_KEY
},
body: JSON.stringify(body)
});
console.log('GPT-4o skateboard response status:', res.status, res.statusText);
if (!res.ok) {
const errorText = await res.text();
console.error('GPT-4o skateboard API error status', res.status, errorText);
return '';
}
const data = await res.json();
console.log('GPT-4o skateboard response data:', data);
const answer = (data.choices?.[0]?.message?.content || '').toString().trim();
console.log('GPT-4o skateboard answer:', answer);
return removeThinkTags(answer);
};
// Helper function to remove <think></think> tags and content
const removeThinkTags = (text) => {
if (!text) return text;
// Remove all <think>...</think> blocks (case insensitive, handles multiline)
return text.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
};
// Cerebras API functions
const CEREBRAS_API_KEY = 'csk-xp5e2xey3mv8ptt3wc2cmwptfx8mywx9em5k2pdenvx2k96r';
const fetchCerebrasGptOss = async (conversationHistory) => {
console.log('fetchCerebrasGptOss called');
if (!CEREBRAS_API_KEY) {
console.warn('Missing CEREBRAS_API_KEY');
return '';
}
const body = {
model: 'gpt-oss-120b',
stream: false,
max_tokens: 4096,
temperature: 1,
top_p: 1,
reasoning_effort: 'low',
messages: conversationHistory
};
console.log('Cerebras GPT-OSS request:', body);
const res = await fetch('https://api.cerebras.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + CEREBRAS_API_KEY
},
body: JSON.stringify(body)
});
console.log('Cerebras GPT-OSS response status:', res.status);
if (!res.ok) {
const errorText = await res.text();
console.error('Cerebras GPT-OSS API error', res.status, errorText);
return '';
}
const data = await res.json();
console.log('Cerebras GPT-OSS response data:', data);
const answer = (data.choices?.[0]?.message?.content || '').toString().trim();
return removeThinkTags(answer);
};
const fetchCerebrasQwen = async (conversationHistory) => {
console.log('fetchCerebrasQwen called');
if (!CEREBRAS_API_KEY) {
console.warn('Missing CEREBRAS_API_KEY');
return '';
}
const body = {
model: 'qwen-3-32b',
stream: false,
max_tokens: 4096,
temperature: 0.6,
top_p: 0.95,
messages: conversationHistory
};
console.log('Cerebras Qwen request:', body);
const res = await fetch('https://api.cerebras.ai/v1/chat/completions', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + CEREBRAS_API_KEY
},
body: JSON.stringify(body)
});
console.log('Cerebras Qwen response status:', res.status);
if (!res.ok) {
const errorText = await res.text();
console.error('Cerebras Qwen API error', res.status, errorText);
return '';
}
const data = await res.json();
console.log('Cerebras Qwen response data:', data);
const answer = (data.choices?.[0]?.message?.content || '').toString().trim();
return removeThinkTags(answer);
};
// Ensure only the move remains (strip reasoning/extra tokens if any).
const extractMoveOnly = (raw) => {
console.log('extractMoveOnly called with:', raw);
if (!raw) return '';
// Take first token-like chunk that looks like a move.
const text = raw.trim().replace(/\s+/g, ' ');
// Simple heuristic: split on spaces / punctuation, pick first chunk containing a digit or '#'
const candidates = text.split(/[\s,;:.!?\n\r]+/).filter(Boolean);
console.log('extractMoveOnly candidates:', candidates);
if (!candidates.length) return text;
const moveLike = candidates.find((c) => /[0-9]|#|\+/.test(c)) || candidates[0];
console.log('extractMoveOnly result:', moveLike);
return moveLike.trim();
};
// Initialize app
createLandingPage();