|
|
<!DOCTYPE html> |
|
|
<html lang="en"> |
|
|
<head> |
|
|
<meta charset="UTF-8"> |
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"> |
|
|
<title>Function Arena // BUILD_YOUR_TOOLS</title> |
|
|
|
|
|
<link href="https://api.fontshare.com/v2/css?f[]=clash-display@700,600,500&f[]=space-mono@400,700&display=swap" rel="stylesheet"> |
|
|
|
|
|
|
|
|
<script async src="https://www.googletagmanager.com/gtag/js?id=G-2Q4M55VKPR"></script> |
|
|
<script> |
|
|
window.dataLayer = window.dataLayer || []; |
|
|
function gtag(){dataLayer.push(arguments);} |
|
|
gtag('js', new Date()); |
|
|
gtag('config', 'G-2Q4M55VKPR', { |
|
|
page_path: window.location.pathname, |
|
|
anonymize_ip: true |
|
|
}); |
|
|
</script> |
|
|
|
|
|
<style> |
|
|
:root { |
|
|
--void: #050505; |
|
|
--panel: #111111; |
|
|
--acid: #ccff00; |
|
|
--acid-dim: #4d6000; |
|
|
--hyper-purple: #7000ff; |
|
|
--alert: #ff3300; |
|
|
--success: #00ff88; |
|
|
--text-main: #f0f0f0; |
|
|
--text-muted: #666; |
|
|
--border-thick: 3px; |
|
|
--hard-shadow: 6px 6px 0px var(--hyper-purple); |
|
|
--ease-out-expo: cubic-bezier(0.19, 1, 0.22, 1); |
|
|
} |
|
|
|
|
|
* { margin: 0; padding: 0; box-sizing: border-box; } |
|
|
|
|
|
body { |
|
|
background-color: var(--void); |
|
|
color: var(--text-main); |
|
|
font-family: 'Space Mono', monospace; |
|
|
min-height: 100vh; |
|
|
overflow-x: hidden; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
} |
|
|
|
|
|
.noise-overlay { |
|
|
position: fixed; |
|
|
top: 0; left: 0; width: 100%; height: 100%; |
|
|
pointer-events: none; |
|
|
z-index: 9999; |
|
|
opacity: 0.05; |
|
|
background-image: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E"); |
|
|
} |
|
|
|
|
|
.scanlines { |
|
|
position: fixed; |
|
|
top: 0; left: 0; width: 100%; height: 100%; |
|
|
background: linear-gradient(to bottom, rgba(255,255,255,0), rgba(255,255,255,0) 50%, rgba(0,0,0,0.1) 50%, rgba(0,0,0,0.1)); |
|
|
background-size: 100% 4px; |
|
|
z-index: 9998; |
|
|
pointer-events: none; |
|
|
} |
|
|
|
|
|
h1, h2, h3, .display-font { |
|
|
font-family: 'Clash Display', sans-serif; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: -0.02em; |
|
|
} |
|
|
|
|
|
#boot-sequence { |
|
|
position: fixed; |
|
|
inset: 0; |
|
|
background: var(--void); |
|
|
z-index: 10000; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
justify-content: flex-end; |
|
|
padding: 40px; |
|
|
font-size: 14px; |
|
|
} |
|
|
|
|
|
.boot-line { |
|
|
opacity: 0; |
|
|
transform: translateY(10px); |
|
|
color: var(--acid); |
|
|
margin-bottom: 5px; |
|
|
} |
|
|
|
|
|
.app-container { |
|
|
max-width: 1800px; |
|
|
margin: 0 auto; |
|
|
width: 100%; |
|
|
padding: 20px; |
|
|
display: grid; |
|
|
grid-template-columns: 350px 1fr; |
|
|
gap: 20px; |
|
|
flex: 1; |
|
|
opacity: 0; |
|
|
transition: opacity 0.5s ease; |
|
|
} |
|
|
|
|
|
@media (max-width: 1200px) { |
|
|
.app-container { grid-template-columns: 1fr; } |
|
|
} |
|
|
|
|
|
.sidebar { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
gap: 20px; |
|
|
} |
|
|
|
|
|
.panel { |
|
|
background: var(--panel); |
|
|
border: var(--border-thick) solid var(--text-main); |
|
|
padding: 20px; |
|
|
position: relative; |
|
|
} |
|
|
|
|
|
.panel-title { |
|
|
background: var(--text-main); |
|
|
color: var(--void); |
|
|
padding: 5px 10px; |
|
|
font-weight: 700; |
|
|
display: inline-block; |
|
|
margin-bottom: 15px; |
|
|
font-size: 12px; |
|
|
} |
|
|
|
|
|
.stat-row { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: baseline; |
|
|
margin-bottom: 10px; |
|
|
border-bottom: 1px solid #333; |
|
|
padding-bottom: 5px; |
|
|
} |
|
|
|
|
|
.stat-value { |
|
|
font-family: 'Clash Display'; |
|
|
font-size: 24px; |
|
|
color: var(--acid); |
|
|
} |
|
|
|
|
|
.game-area { |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
gap: 20px; |
|
|
} |
|
|
|
|
|
.level-header { |
|
|
border: var(--border-thick) solid var(--acid); |
|
|
background: var(--void); |
|
|
padding: 30px; |
|
|
position: relative; |
|
|
box-shadow: var(--hard-shadow); |
|
|
} |
|
|
|
|
|
.level-title { |
|
|
font-size: 2.5rem; |
|
|
line-height: 0.9; |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
.objective-badge { |
|
|
display: inline-block; |
|
|
background: var(--hyper-purple); |
|
|
color: var(--acid); |
|
|
font-weight: 900; |
|
|
padding: 8px 16px; |
|
|
font-size: 1rem; |
|
|
margin-bottom: 15px; |
|
|
} |
|
|
|
|
|
.scenario-grid { |
|
|
display: grid; |
|
|
gap: 10px; |
|
|
margin-bottom: 20px; |
|
|
} |
|
|
|
|
|
.scenario-card { |
|
|
background: rgba(255,255,255,0.02); |
|
|
border: 1px solid #333; |
|
|
padding: 15px; |
|
|
display: grid; |
|
|
grid-template-columns: 40px 1fr 100px; |
|
|
align-items: center; |
|
|
gap: 15px; |
|
|
transition: all 0.2s; |
|
|
} |
|
|
|
|
|
.scenario-card:hover { |
|
|
border-color: var(--acid); |
|
|
background: rgba(204, 255, 0, 0.03); |
|
|
} |
|
|
|
|
|
.scenario-icon { |
|
|
font-size: 24px; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.scenario-text { |
|
|
font-size: 0.9rem; |
|
|
line-height: 1.4; |
|
|
} |
|
|
|
|
|
.scenario-expected { |
|
|
text-align: center; |
|
|
font-weight: bold; |
|
|
font-size: 0.8rem; |
|
|
} |
|
|
|
|
|
.scenario-expected.should-trigger { |
|
|
color: var(--success); |
|
|
} |
|
|
|
|
|
.scenario-expected.should-not { |
|
|
color: var(--alert); |
|
|
} |
|
|
|
|
|
.scenario-result { |
|
|
position: absolute; |
|
|
right: 10px; |
|
|
top: 50%; |
|
|
transform: translateY(-50%); |
|
|
font-size: 20px; |
|
|
} |
|
|
|
|
|
.editor-section { |
|
|
display: grid; |
|
|
grid-template-columns: 1fr 1fr; |
|
|
gap: 20px; |
|
|
margin-top: 20px; |
|
|
} |
|
|
|
|
|
@media (max-width: 900px) { |
|
|
.editor-section { grid-template-columns: 1fr; } |
|
|
} |
|
|
|
|
|
.editor-panel { |
|
|
border: 2px solid #333; |
|
|
background: #000; |
|
|
} |
|
|
|
|
|
.editor-header { |
|
|
background: #1a1a1a; |
|
|
padding: 10px 15px; |
|
|
border-bottom: 1px solid #333; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.editor-title { |
|
|
color: var(--acid); |
|
|
font-size: 0.9rem; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
textarea, .json-output { |
|
|
width: 100%; |
|
|
background: #000; |
|
|
border: none; |
|
|
color: var(--text-main); |
|
|
padding: 20px; |
|
|
font-family: 'Space Mono', monospace; |
|
|
font-size: 0.9rem; |
|
|
min-height: 300px; |
|
|
resize: vertical; |
|
|
overflow-y: auto; |
|
|
} |
|
|
|
|
|
textarea { |
|
|
height: auto; |
|
|
} |
|
|
|
|
|
textarea:focus { |
|
|
outline: none; |
|
|
} |
|
|
|
|
|
.json-output { |
|
|
color: var(--success); |
|
|
overflow-x: auto; |
|
|
white-space: pre-wrap; |
|
|
} |
|
|
|
|
|
.action-bar { |
|
|
display: flex; |
|
|
gap: 10px; |
|
|
margin-top: 20px; |
|
|
} |
|
|
|
|
|
button { |
|
|
background: var(--text-main); |
|
|
color: var(--void); |
|
|
border: none; |
|
|
font-family: 'Clash Display', sans-serif; |
|
|
font-weight: 700; |
|
|
text-transform: uppercase; |
|
|
font-size: 1rem; |
|
|
padding: 15px 30px; |
|
|
cursor: pointer; |
|
|
transition: all 0.2s; |
|
|
flex: 1; |
|
|
} |
|
|
|
|
|
button:hover:not(:disabled) { |
|
|
background: var(--acid); |
|
|
transform: translate(-4px, -4px); |
|
|
box-shadow: 6px 6px 0px var(--text-main); |
|
|
} |
|
|
|
|
|
button:active:not(:disabled) { |
|
|
transform: translate(0, 0); |
|
|
box-shadow: none; |
|
|
} |
|
|
|
|
|
button:disabled { |
|
|
background: #333; |
|
|
color: #666; |
|
|
cursor: not-allowed; |
|
|
} |
|
|
|
|
|
button.secondary { |
|
|
background: transparent; |
|
|
border: 2px solid var(--text-main); |
|
|
color: var(--text-main); |
|
|
} |
|
|
|
|
|
button.secondary:hover:not(:disabled) { |
|
|
background: var(--text-main); |
|
|
color: var(--void); |
|
|
} |
|
|
|
|
|
.hint-panel { |
|
|
background: rgba(112, 0, 255, 0.1); |
|
|
border: 1px solid var(--hyper-purple); |
|
|
padding: 15px; |
|
|
margin-top: 20px; |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.hint-panel.visible { |
|
|
display: block; |
|
|
} |
|
|
|
|
|
.hint-title { |
|
|
color: var(--hyper-purple); |
|
|
font-weight: bold; |
|
|
margin-bottom: 10px; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
.hint-content { |
|
|
font-size: 0.85rem; |
|
|
line-height: 1.5; |
|
|
color: var(--text-muted); |
|
|
} |
|
|
|
|
|
.competitor-functions { |
|
|
display: grid; |
|
|
gap: 10px; |
|
|
margin-top: 15px; |
|
|
} |
|
|
|
|
|
.competitor-fn { |
|
|
background: rgba(255,0,0,0.05); |
|
|
border: 1px solid #4a0000; |
|
|
padding: 12px; |
|
|
font-size: 0.85rem; |
|
|
} |
|
|
|
|
|
.fn-name-display { |
|
|
color: var(--alert); |
|
|
font-weight: bold; |
|
|
margin-bottom: 5px; |
|
|
} |
|
|
|
|
|
.result-panel { |
|
|
margin-top: 20px; |
|
|
padding: 20px; |
|
|
background: #080808; |
|
|
border: 2px solid #333; |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.result-panel.visible { |
|
|
display: block; |
|
|
animation: flash 0.2s; |
|
|
} |
|
|
|
|
|
.result-header { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
margin-bottom: 20px; |
|
|
padding-bottom: 15px; |
|
|
border-bottom: 2px solid #333; |
|
|
} |
|
|
|
|
|
.result-title { |
|
|
font-size: 1.5rem; |
|
|
font-family: 'Clash Display'; |
|
|
} |
|
|
|
|
|
.result-score { |
|
|
font-size: 2rem; |
|
|
font-family: 'Clash Display'; |
|
|
} |
|
|
|
|
|
.result-title.success, .result-score.success { |
|
|
color: var(--success); |
|
|
} |
|
|
|
|
|
.result-title.fail, .result-score.fail { |
|
|
color: var(--alert); |
|
|
} |
|
|
|
|
|
.result-breakdown { |
|
|
display: grid; |
|
|
gap: 8px; |
|
|
} |
|
|
|
|
|
.result-item { |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
padding: 8px; |
|
|
background: rgba(255,255,255,0.02); |
|
|
border-left: 3px solid #333; |
|
|
} |
|
|
|
|
|
.result-item.correct { |
|
|
border-left-color: var(--success); |
|
|
} |
|
|
|
|
|
.result-item.wrong { |
|
|
border-left-color: var(--alert); |
|
|
} |
|
|
|
|
|
.execution-logs { |
|
|
border: 2px solid #333; |
|
|
background: #000; |
|
|
margin-top: 20px; |
|
|
} |
|
|
|
|
|
.logs-header { |
|
|
background: #1a1a1a; |
|
|
padding: 12px 15px; |
|
|
border-bottom: 1px solid #333; |
|
|
display: flex; |
|
|
justify-content: space-between; |
|
|
align-items: center; |
|
|
} |
|
|
|
|
|
.logs-title { |
|
|
color: var(--acid); |
|
|
font-size: 0.9rem; |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.logs-clear { |
|
|
background: transparent; |
|
|
border: 1px solid #333; |
|
|
color: #666; |
|
|
padding: 4px 12px; |
|
|
font-size: 0.8rem; |
|
|
cursor: pointer; |
|
|
} |
|
|
|
|
|
.logs-clear:hover { |
|
|
border-color: #666; |
|
|
color: #999; |
|
|
} |
|
|
|
|
|
.logs-content { |
|
|
padding: 15px; |
|
|
font-family: 'Space Mono', monospace; |
|
|
font-size: 0.85rem; |
|
|
color: var(--text-main); |
|
|
max-height: 400px; |
|
|
overflow-y: auto; |
|
|
background: #0a0a0a; |
|
|
} |
|
|
|
|
|
.log-entry { |
|
|
margin-bottom: 10px; |
|
|
padding-bottom: 10px; |
|
|
border-bottom: 1px solid #222; |
|
|
opacity: 0; |
|
|
animation: fadeIn 0.3s forwards; |
|
|
} |
|
|
|
|
|
.log-entry:last-child { |
|
|
border-bottom: none; |
|
|
} |
|
|
|
|
|
@keyframes fadeIn { |
|
|
from { opacity: 0; transform: translateY(-5px); } |
|
|
to { opacity: 1; transform: translateY(0); } |
|
|
} |
|
|
|
|
|
.log-time { |
|
|
color: #666; |
|
|
font-size: 0.75rem; |
|
|
} |
|
|
|
|
|
.log-label { |
|
|
color: var(--acid); |
|
|
font-weight: bold; |
|
|
display: inline-block; |
|
|
min-width: 120px; |
|
|
} |
|
|
|
|
|
.log-prompt { |
|
|
color: #90ee90; |
|
|
padding: 8px; |
|
|
background: rgba(144, 238, 144, 0.1); |
|
|
border-left: 3px solid #90ee90; |
|
|
margin: 8px 0; |
|
|
word-break: break-word; |
|
|
} |
|
|
|
|
|
.log-tools { |
|
|
color: #87ceeb; |
|
|
padding: 8px; |
|
|
background: rgba(135, 206, 235, 0.1); |
|
|
border-left: 3px solid #87ceeb; |
|
|
margin: 8px 0; |
|
|
max-height: 200px; |
|
|
overflow-y: auto; |
|
|
} |
|
|
|
|
|
.log-function { |
|
|
padding: 4px 8px; |
|
|
margin: 4px 0; |
|
|
background: #1a1a1a; |
|
|
border-radius: 3px; |
|
|
} |
|
|
|
|
|
.log-function-name { |
|
|
color: var(--acid); |
|
|
font-weight: bold; |
|
|
} |
|
|
|
|
|
.log-success { |
|
|
color: #00ff88; |
|
|
} |
|
|
|
|
|
.log-error { |
|
|
color: #ff3300; |
|
|
} |
|
|
|
|
|
.log-info { |
|
|
color: #999; |
|
|
} |
|
|
|
|
|
@keyframes flash { |
|
|
0% { background-color: var(--text-main); } |
|
|
100% { background-color: #080808; } |
|
|
} |
|
|
|
|
|
.rules-modal { |
|
|
position: fixed; |
|
|
inset: 0; |
|
|
background: rgba(0,0,0,0.9); |
|
|
z-index: 5000; |
|
|
display: none; |
|
|
align-items: center; |
|
|
justify-content: center; |
|
|
padding: 20px; |
|
|
} |
|
|
|
|
|
.rules-modal.visible { |
|
|
display: flex; |
|
|
} |
|
|
|
|
|
.rules-content { |
|
|
background: var(--panel); |
|
|
border: var(--border-thick) solid var(--acid); |
|
|
max-width: 800px; |
|
|
max-height: 90vh; |
|
|
overflow-y: auto; |
|
|
padding: 30px; |
|
|
} |
|
|
|
|
|
.rules-section { |
|
|
margin-bottom: 25px; |
|
|
} |
|
|
|
|
|
.rules-section h3 { |
|
|
color: var(--acid); |
|
|
margin-bottom: 10px; |
|
|
font-size: 1.2rem; |
|
|
} |
|
|
|
|
|
.rules-section p, .rules-section ul { |
|
|
line-height: 1.6; |
|
|
color: var(--text-muted); |
|
|
margin-bottom: 10px; |
|
|
} |
|
|
|
|
|
.rules-section ul { |
|
|
margin-left: 20px; |
|
|
} |
|
|
|
|
|
.rules-section code { |
|
|
background: #000; |
|
|
padding: 2px 6px; |
|
|
border: 1px solid #333; |
|
|
color: var(--acid); |
|
|
font-size: 0.85rem; |
|
|
} |
|
|
|
|
|
.close-rules { |
|
|
width: 100%; |
|
|
margin-top: 20px; |
|
|
} |
|
|
|
|
|
.loader-overlay { |
|
|
position: fixed; |
|
|
bottom: 20px; |
|
|
right: 20px; |
|
|
background: var(--panel); |
|
|
border: 2px solid var(--acid); |
|
|
padding: 15px 20px; |
|
|
z-index: 10001; |
|
|
display: flex; |
|
|
flex-direction: column; |
|
|
align-items: center; |
|
|
gap: 10px; |
|
|
min-width: 250px; |
|
|
box-shadow: var(--hard-shadow); |
|
|
opacity: 0.9; |
|
|
} |
|
|
|
|
|
.loader-overlay.hidden { |
|
|
display: none; |
|
|
} |
|
|
|
|
|
.loader-spinner { |
|
|
width: 30px; |
|
|
height: 30px; |
|
|
border: 3px solid var(--panel); |
|
|
border-top: 3px solid var(--acid); |
|
|
border-radius: 50%; |
|
|
animation: spin 1s linear infinite; |
|
|
} |
|
|
|
|
|
@keyframes spin { |
|
|
0% { transform: rotate(0deg); } |
|
|
100% { transform: rotate(360deg); } |
|
|
} |
|
|
|
|
|
@keyframes slideInRight { |
|
|
from { |
|
|
transform: translateX(400px); |
|
|
opacity: 0; |
|
|
} |
|
|
to { |
|
|
transform: translateX(0); |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes slideOutRight { |
|
|
from { |
|
|
transform: translateX(0); |
|
|
opacity: 1; |
|
|
} |
|
|
to { |
|
|
transform: translateX(400px); |
|
|
opacity: 0; |
|
|
} |
|
|
} |
|
|
|
|
|
@keyframes pulse { |
|
|
0%, 100% { transform: scale(1); } |
|
|
50% { transform: scale(1.05); } |
|
|
} |
|
|
|
|
|
.scenario-card.correct { |
|
|
border-color: var(--success); |
|
|
background: rgba(0, 255, 136, 0.1); |
|
|
animation: pulse 0.3s; |
|
|
} |
|
|
|
|
|
.scenario-card.wrong { |
|
|
border-color: var(--alert); |
|
|
background: rgba(255, 51, 0, 0.1); |
|
|
animation: pulse 0.3s; |
|
|
} |
|
|
|
|
|
.loader-text { |
|
|
color: var(--acid); |
|
|
font-family: 'Clash Display', sans-serif; |
|
|
font-size: 0.9rem; |
|
|
text-transform: uppercase; |
|
|
letter-spacing: 0.05em; |
|
|
text-align: center; |
|
|
} |
|
|
|
|
|
.loader-progress { |
|
|
width: 200px; |
|
|
height: 3px; |
|
|
background: var(--panel); |
|
|
border: 1px solid var(--text-main); |
|
|
position: relative; |
|
|
overflow: hidden; |
|
|
} |
|
|
|
|
|
.loader-progress-bar { |
|
|
height: 100%; |
|
|
background: var(--acid); |
|
|
width: 0%; |
|
|
transition: width 0.3s ease; |
|
|
box-shadow: 0 0 5px var(--acid); |
|
|
} |
|
|
|
|
|
.test-loader { |
|
|
position: fixed; |
|
|
bottom: 30px; |
|
|
right: 30px; |
|
|
background: var(--panel); |
|
|
border: var(--border-thick) solid var(--acid); |
|
|
padding: 20px 30px; |
|
|
z-index: 5000; |
|
|
display: none; |
|
|
align-items: center; |
|
|
gap: 15px; |
|
|
box-shadow: var(--hard-shadow); |
|
|
} |
|
|
|
|
|
.test-loader.visible { |
|
|
display: flex; |
|
|
animation: slideInUp 0.3s var(--ease-out-expo); |
|
|
} |
|
|
|
|
|
@keyframes slideInUp { |
|
|
from { |
|
|
transform: translateY(100px); |
|
|
opacity: 0; |
|
|
} |
|
|
to { |
|
|
transform: translateY(0); |
|
|
opacity: 1; |
|
|
} |
|
|
} |
|
|
|
|
|
.test-loader-spinner { |
|
|
width: 24px; |
|
|
height: 24px; |
|
|
border: 3px solid var(--panel); |
|
|
border-top: 3px solid var(--acid); |
|
|
border-radius: 50%; |
|
|
animation: spin 0.8s linear infinite; |
|
|
} |
|
|
|
|
|
.test-loader-text { |
|
|
color: var(--acid); |
|
|
font-family: 'Space Mono', monospace; |
|
|
font-size: 0.9rem; |
|
|
} |
|
|
|
|
|
.test-loader-progress { |
|
|
color: var(--text-muted); |
|
|
font-size: 0.8rem; |
|
|
} |
|
|
|
|
|
.model-info { |
|
|
position: fixed; |
|
|
bottom: 10px; |
|
|
left: 10px; |
|
|
background: var(--panel); |
|
|
border: 1px solid var(--text-main); |
|
|
padding: 8px 12px; |
|
|
font-size: 0.75rem; |
|
|
color: var(--text-muted); |
|
|
z-index: 100; |
|
|
opacity: 0.7; |
|
|
transition: opacity 0.2s; |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 15px; |
|
|
flex-wrap: wrap; |
|
|
} |
|
|
|
|
|
.model-info:hover { |
|
|
opacity: 1; |
|
|
} |
|
|
|
|
|
.model-info strong { |
|
|
color: var(--acid); |
|
|
} |
|
|
|
|
|
.model-info .divider { |
|
|
color: #333; |
|
|
margin: 0 5px; |
|
|
} |
|
|
|
|
|
.model-info .creator-info { |
|
|
display: flex; |
|
|
align-items: center; |
|
|
gap: 8px; |
|
|
} |
|
|
|
|
|
.model-info .creator-info a { |
|
|
color: var(--acid); |
|
|
text-decoration: none; |
|
|
transition: color 0.2s; |
|
|
} |
|
|
|
|
|
.model-info .creator-info a:hover { |
|
|
color: var(--success); |
|
|
} |
|
|
|
|
|
.model-info .creator-name { |
|
|
color: var(--text-main); |
|
|
font-weight: bold; |
|
|
} |
|
|
</style> |
|
|
</head> |
|
|
<body> |
|
|
|
|
|
<div class="noise-overlay"></div> |
|
|
<div class="scanlines"></div> |
|
|
|
|
|
<div id="boot-sequence"></div> |
|
|
|
|
|
<div class="loader-overlay" id="modelLoader"> |
|
|
<div class="loader-spinner"></div> |
|
|
<div class="loader-text" id="loaderText">Loading AI Model</div> |
|
|
<div class="loader-progress"> |
|
|
<div class="loader-progress-bar" id="modelProgressBar"></div> |
|
|
</div> |
|
|
<div id="loaderSubtext" style="color: var(--text-muted); font-size: 0.75rem; text-align: center; max-width: 200px;"> |
|
|
<span id="deviceInfo"></span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="test-loader" id="testLoader"> |
|
|
<div class="test-loader-spinner"></div> |
|
|
<div> |
|
|
<div class="test-loader-text">TESTING FUNCTION</div> |
|
|
<div class="test-loader-progress" id="testProgress">Initializing...</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="rules-modal" id="rulesModal"> |
|
|
<div class="rules-content"> |
|
|
<h2 style="color: var(--acid); margin-bottom: 20px; font-size: 2rem;">FUNCTION ARENA // RULES</h2> |
|
|
|
|
|
<div class="rules-section"> |
|
|
<h3>π― OBJECTIVE</h3> |
|
|
<p>Create precise function definitions that trigger in the RIGHT scenarios and stay silent in the WRONG ones. You're competing against other functions for attention.</p> |
|
|
</div> |
|
|
|
|
|
<div class="rules-section"> |
|
|
<h3>π HOW TO PLAY</h3> |
|
|
<p>1. Review the test scenarios - some should trigger your function (β SHOULD), others shouldn't (β SHOULD NOT)</p> |
|
|
<p>2. Write your function definition with a clear name and description</p> |
|
|
<p>3. Add parameters with proper types and descriptions (optional but helps)</p> |
|
|
<p>4. Use enums to constrain values when appropriate</p> |
|
|
<p>5. Test your function against all scenarios</p> |
|
|
</div> |
|
|
|
|
|
<div class="rules-section"> |
|
|
<h3>π§ FUNCTION SCHEMA FORMAT</h3> |
|
|
<p>Your function follows the JSON Schema / OpenAPI format:</p> |
|
|
<ul> |
|
|
<li><code>name</code>: Function identifier (e.g., "get_weather")</li> |
|
|
<li><code>description</code>: What your function does - BE SPECIFIC</li> |
|
|
<li><code>parameters</code>: Object with type definitions</li> |
|
|
<li><code>properties</code>: Define each parameter with type, description</li> |
|
|
<li><code>required</code>: Array of required parameter names</li> |
|
|
<li><code>enum</code>: Use to limit values (e.g., ["celsius", "fahrenheit"])</li> |
|
|
</ul> |
|
|
</div> |
|
|
|
|
|
<div class="rules-section"> |
|
|
<h3>π‘ PRO TIPS</h3> |
|
|
<p><strong>Names matter:</strong> Specific names beat generic ones. "fetch_user_profile" > "get_data"</p> |
|
|
<p><strong>Descriptions are key:</strong> AI reads your description to decide. Be clear about WHAT and WHEN.</p> |
|
|
<p><strong>Use enums wisely:</strong> They constrain inputs and make intent clearer.</p> |
|
|
<p><strong>Think like AI:</strong> What keywords would make YOUR function the obvious choice?</p> |
|
|
</div> |
|
|
|
|
|
<div class="rules-section"> |
|
|
<h3>π SCORING</h3> |
|
|
<p>+100 points for each correct trigger (when you SHOULD)</p> |
|
|
<p>-50 points for each incorrect trigger (when you SHOULDN'T)</p> |
|
|
<p>+50 bonus points for each FALSE scenario where you correctly didn't trigger</p> |
|
|
<p>Compete against 0-5 competitor functions trying to steal your scenarios!</p> |
|
|
</div> |
|
|
|
|
|
<div class="rules-section"> |
|
|
<h3>π LEARNING PATH</h3> |
|
|
<p>Levels progress from basic scenarios to complex edge cases:</p> |
|
|
<p>β’ <strong>Novice:</strong> Simple, clear scenarios</p> |
|
|
<p>β’ <strong>Intermediate:</strong> Ambiguous queries, need precision</p> |
|
|
<p>β’ <strong>Advanced:</strong> Multiple competitors, overlapping domains</p> |
|
|
<p>β’ <strong>Expert:</strong> Complex parameters, enums required</p> |
|
|
<p>β’ <strong>Master:</strong> Multi-domain confusion, extreme precision needed</p> |
|
|
</div> |
|
|
|
|
|
<button class="close-rules" onclick="toggleRules()">CLOSE // BEGIN TRAINING</button> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="app-container" id="app"> |
|
|
|
|
|
<aside class="sidebar"> |
|
|
<div class="panel"> |
|
|
<div class="panel-title">OPERATOR_STATS</div> |
|
|
<div class="stat-row"> |
|
|
<span>LEVEL</span> |
|
|
<span class="stat-value" id="levelDisp">01</span> |
|
|
</div> |
|
|
<div class="stat-row"> |
|
|
<span>SCENARIOS</span> |
|
|
<span class="stat-value" id="scenarioCount">0</span> |
|
|
</div> |
|
|
<div class="stat-row"> |
|
|
<span>SCORE</span> |
|
|
<span class="stat-value" id="scoreDisp">0000</span> |
|
|
</div> |
|
|
<div class="stat-row"> |
|
|
<span>ACCURACY</span> |
|
|
<span class="stat-value" id="accuracyDisp" style="color: var(--success)">--</span> |
|
|
</div> |
|
|
<div class="stat-row" id="streakRow" style="display: none;"> |
|
|
<span>STREAK</span> |
|
|
<span class="stat-value" id="streakDisp" style="color: var(--hyper-purple)">0</span> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
|
|
|
<div class="panel"> |
|
|
<div class="panel-title">COMPETITOR_FUNCTIONS</div> |
|
|
<div id="competitorList" style="font-size: 0.8rem; color: #666;"> |
|
|
None loaded... |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="panel"> |
|
|
<div class="panel-title">ACHIEVEMENTS</div> |
|
|
<div id="achievementsList" style="font-size: 0.8rem; color: #666; min-height: 40px;"> |
|
|
No achievements yet... |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<button onclick="toggleRules()" style="border: 1px solid var(--text-main); background: transparent;"> |
|
|
VIEW RULES [?] |
|
|
</button> |
|
|
|
|
|
<button onclick="toggleHints()" style="border: 1px solid var(--hyper-purple); background: transparent; color: var(--hyper-purple);"> |
|
|
SHOW HINTS [π‘] |
|
|
</button> |
|
|
|
|
|
<button onclick="resetProgress()" style="border: 1px solid var(--alert); background: transparent; color: var(--alert); margin-top: 10px; font-size: 0.85rem;"> |
|
|
RESET PROGRESS [β οΈ] |
|
|
</button> |
|
|
</aside> |
|
|
|
|
|
<main class="game-area"> |
|
|
<header class="level-header"> |
|
|
<div class="objective-badge" id="objectiveBadge">BUILD YOUR FUNCTION</div> |
|
|
<h1 class="level-title" id="lvlName">INITIALIZING...</h1> |
|
|
<p style="max-width: 800px; color: var(--text-muted); margin-top: 10px;" id="lvlDesc">Loading challenge parameters...</p> |
|
|
</header> |
|
|
|
|
|
<div class="panel" style="border-color: var(--acid);"> |
|
|
<div class="panel-title" style="background: var(--acid); color: black;">TEST_SCENARIOS</div> |
|
|
<div class="scenario-grid" id="scenarioGrid"></div> |
|
|
</div> |
|
|
|
|
|
<div class="editor-section"> |
|
|
<div class="editor-panel"> |
|
|
<div class="editor-header"> |
|
|
<span class="editor-title">YOUR FUNCTION DEFINITION</span> |
|
|
<span style="color: #666; font-size: 0.8rem;">β Pre-filled template β’ Edit to refine</span> |
|
|
</div> |
|
|
<textarea id="functionEditor" placeholder='{\n "name": "my_function",\n "description": "What my function does...",\n "parameters": {\n "type": "object",\n "properties": {\n "param1": {\n "type": "string",\n "description": "First parameter"\n }\n },\n "required": ["param1"]\n }\n}'></textarea> |
|
|
</div> |
|
|
|
|
|
<div class="editor-panel"> |
|
|
<div class="editor-header"> |
|
|
<span class="editor-title">PARSED OUTPUT</span> |
|
|
<span style="color: #666; font-size: 0.8rem;">Validation</span> |
|
|
</div> |
|
|
<div class="json-output" id="jsonOutput">// Your JSON will be validated here...</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
<div class="hint-panel" id="hintPanel"> |
|
|
<div class="hint-title">π‘ STRATEGIC HINTS</div> |
|
|
<div class="hint-content" id="hintContent"></div> |
|
|
</div> |
|
|
|
|
|
<div class="action-bar"> |
|
|
<button id="testBtn" onclick="testFunction()">TEST FUNCTION</button> |
|
|
<button class="secondary" onclick="resetEditor()">RESET</button> |
|
|
<button class="secondary" onclick="loadTemplate()">π STANDARD TEMPLATE</button> |
|
|
<button class="secondary" id="nextBtn" onclick="nextLevel()" style="display: none;">NEXT LEVEL β</button> |
|
|
</div> |
|
|
|
|
|
<div class="result-panel" id="resultPanel"> |
|
|
<div class="result-header"> |
|
|
<span class="result-title" id="resultTitle">RESULTS</span> |
|
|
<span class="result-score" id="resultScore">+0</span> |
|
|
</div> |
|
|
<div class="result-breakdown" id="resultBreakdown"></div> |
|
|
</div> |
|
|
|
|
|
<div class="execution-logs"> |
|
|
<div class="logs-header"> |
|
|
<span class="logs-title">βοΈ EXECUTION LOGS // Real-time Model Behavior</span> |
|
|
<button class="logs-clear" onclick="clearLogs()">CLEAR LOGS</button> |
|
|
</div> |
|
|
<div class="logs-content" id="logsContent"> |
|
|
<div style="color: #666; text-align: center; padding: 40px 20px;"> |
|
|
Logs will appear here when you test a function... |
|
|
</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
</main> |
|
|
</div> |
|
|
|
|
|
<div class="model-info" id="modelInfo" style="display: none;"> |
|
|
<div> |
|
|
<strong>Model:</strong> <span id="modelName">-</span> | |
|
|
<strong>Version:</strong> <span id="modelVersion">-</span> | |
|
|
<strong>Quantization:</strong> <span id="modelQuantization">-</span> | |
|
|
<strong>Device:</strong> <span id="modelDevice">-</span> | |
|
|
|
|
|
<strong>Made by Gaurav Chauhan</strong> | |
|
|
<strong>2796gaurav@gmail.com</strong> | |
|
|
<strong><a href="https://www.linkedin.com/in/gauravc2708/" target="_blank" rel="noopener noreferrer">LinkedIn</a></strong> | |
|
|
|
|
|
</div> |
|
|
|
|
|
</div> |
|
|
|
|
|
<script type="module"> |
|
|
|
|
|
import { openDB } from 'https://cdn.jsdelivr.net/npm/idb@7/+esm'; |
|
|
|
|
|
|
|
|
const dbPromise = openDB('function-arena-cache', 1, { |
|
|
upgrade(db) { |
|
|
db.createObjectStore('models'); |
|
|
db.createObjectStore('tokenizers'); |
|
|
db.createObjectStore('level-data'); |
|
|
}, |
|
|
}); |
|
|
|
|
|
|
|
|
const telemetry = { |
|
|
modelLoadTime: 0, |
|
|
averageGenerationTime: 0, |
|
|
cacheHitRate: 0, |
|
|
deviceType: null, |
|
|
generationTimes: [], |
|
|
cacheHits: 0, |
|
|
cacheMisses: 0, |
|
|
init() { |
|
|
|
|
|
const hasWebGPU = !!navigator.gpu; |
|
|
const isMobile = /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent); |
|
|
this.deviceType = hasWebGPU ? 'webgpu' : (isMobile ? 'mobile-cpu' : 'desktop-cpu'); |
|
|
|
|
|
|
|
|
if (typeof gtag !== 'undefined') { |
|
|
gtag('event', 'page_load', { |
|
|
device_type: this.deviceType, |
|
|
timestamp: new Date().toISOString() |
|
|
}); |
|
|
} |
|
|
}, |
|
|
trackModelLoad(time) { |
|
|
this.modelLoadTime = time; |
|
|
if (typeof gtag !== 'undefined') { |
|
|
gtag('event', 'model_load', { |
|
|
load_time: time, |
|
|
device_type: this.deviceType |
|
|
}); |
|
|
} |
|
|
}, |
|
|
trackGeneration(time) { |
|
|
this.generationTimes.push(time); |
|
|
|
|
|
if (this.generationTimes.length > 100) { |
|
|
this.generationTimes.shift(); |
|
|
} |
|
|
this.averageGenerationTime = this.generationTimes.reduce((a, b) => a + b, 0) / this.generationTimes.length; |
|
|
|
|
|
if (typeof gtag !== 'undefined') { |
|
|
gtag('event', 'function_generation', { |
|
|
generation_time: time, |
|
|
average_time: this.averageGenerationTime |
|
|
}); |
|
|
} |
|
|
}, |
|
|
trackCacheHit(hit) { |
|
|
if (hit) { |
|
|
this.cacheHits++; |
|
|
} else { |
|
|
this.cacheMisses++; |
|
|
} |
|
|
const total = this.cacheHits + this.cacheMisses; |
|
|
this.cacheHitRate = total > 0 ? (this.cacheHits / total) * 100 : 0; |
|
|
|
|
|
if (typeof gtag !== 'undefined') { |
|
|
gtag('event', 'cache_access', { |
|
|
cache_hit: hit, |
|
|
hit_rate: this.cacheHitRate |
|
|
}); |
|
|
} |
|
|
}, |
|
|
trackLevelComplete(levelId, score, passed) { |
|
|
if (typeof gtag !== 'undefined') { |
|
|
gtag('event', 'level_complete', { |
|
|
level_id: levelId, |
|
|
score: score, |
|
|
passed: passed, |
|
|
average_generation_time: this.averageGenerationTime, |
|
|
cache_hit_rate: this.cacheHitRate |
|
|
}); |
|
|
} |
|
|
}, |
|
|
getMetrics() { |
|
|
return { |
|
|
modelLoadTime: this.modelLoadTime, |
|
|
averageGenerationTime: this.averageGenerationTime, |
|
|
cacheHitRate: this.cacheHitRate, |
|
|
deviceType: this.deviceType, |
|
|
totalGenerations: this.generationTimes.length |
|
|
}; |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
telemetry.init(); |
|
|
|
|
|
|
|
|
function setupCacheWarmup() { |
|
|
if ('requestIdleCallback' in window) { |
|
|
requestIdleCallback(() => { |
|
|
|
|
|
const nextLevel = levels[state.levelIdx + 1]; |
|
|
if (nextLevel) { |
|
|
|
|
|
dbPromise.then(db => { |
|
|
return db.put('level-data', nextLevel, `level-${nextLevel.id}`); |
|
|
}).then(() => { |
|
|
console.log('β Next level data cached'); |
|
|
telemetry.trackCacheHit(true); |
|
|
}).catch(err => { |
|
|
console.log('Cache warmup failed:', err); |
|
|
}); |
|
|
} |
|
|
}, { timeout: 5000 }); |
|
|
} else { |
|
|
|
|
|
setTimeout(() => { |
|
|
const nextLevel = levels[state.levelIdx + 1]; |
|
|
if (nextLevel) { |
|
|
dbPromise.then(db => { |
|
|
return db.put('level-data', nextLevel, `level-${nextLevel.id}`); |
|
|
}).catch(err => console.log('Cache warmup failed:', err)); |
|
|
} |
|
|
}, 2000); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
const levels = [ |
|
|
{ |
|
|
id: 1, |
|
|
title: "BASIC_TRIGGER", |
|
|
description: "Simple weather function. Make it trigger for weather queries only.", |
|
|
difficulty: "NOVICE", |
|
|
scenarios: [ |
|
|
{ query: "What's the weather in Tokyo?", shouldTrigger: true, icon: "βοΈ" }, |
|
|
{ query: "Get weather for London", shouldTrigger: true, icon: "π§οΈ" }, |
|
|
{ query: "Tell me about Paris history", shouldTrigger: false, icon: "π" }, |
|
|
{ query: "What time is it?", shouldTrigger: false, icon: "π" }, |
|
|
], |
|
|
competitors: [], |
|
|
hints: [ |
|
|
"Focus on weather-related keywords in your description", |
|
|
"Use a clear name like 'get_weather' or 'fetch_weather'", |
|
|
"Add a location parameter to make it more specific" |
|
|
], |
|
|
starterCode: { |
|
|
name: "get_weather", |
|
|
description: "Get current weather information for a specified location", |
|
|
parameters: { |
|
|
type: "object", |
|
|
properties: { |
|
|
location: { |
|
|
type: "string", |
|
|
description: "The city or location name" |
|
|
} |
|
|
}, |
|
|
required: ["location"] |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
id: 2, |
|
|
title: "PRECISION_TARGETING", |
|
|
description: "Build a user profile function. Avoid triggering on similar but different queries.", |
|
|
difficulty: "INTERMEDIATE", |
|
|
scenarios: [ |
|
|
{ query: "Get user profile for john_doe", shouldTrigger: true, icon: "π€" }, |
|
|
{ query: "Fetch user info for ID 12345", shouldTrigger: true, icon: "π" }, |
|
|
{ query: "Show user analytics", shouldTrigger: false, icon: "π" }, |
|
|
{ query: "Delete user account", shouldTrigger: false, icon: "ποΈ" }, |
|
|
{ query: "Update user settings", shouldTrigger: false, icon: "βοΈ" }, |
|
|
{ query: "Get profile data", shouldTrigger: true, icon: "πΎ" }, |
|
|
], |
|
|
competitors: [ |
|
|
{ |
|
|
name: "get_user_analytics", |
|
|
description: "Retrieve user behavior analytics and statistics" |
|
|
}, |
|
|
{ |
|
|
name: "update_user", |
|
|
description: "Update user information and settings" |
|
|
} |
|
|
], |
|
|
hints: [ |
|
|
"Emphasize 'retrieve' or 'get' operations, not modify/delete", |
|
|
"Specify that it's for profile/information retrieval", |
|
|
"Distinguish from analytics (which is aggregate data)", |
|
|
"Use parameters to show what data you return" |
|
|
], |
|
|
starterCode: { |
|
|
name: "get_user_profile", |
|
|
description: "Retrieve user profile information including name, email, and account details", |
|
|
parameters: { |
|
|
type: "object", |
|
|
properties: { |
|
|
user_id: { |
|
|
type: "string", |
|
|
description: "The unique identifier or username of the user" |
|
|
} |
|
|
}, |
|
|
required: ["user_id"] |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
id: 3, |
|
|
title: "ENUM_MASTERY", |
|
|
description: "Create a task management function. Use enums to constrain priority and status.", |
|
|
difficulty: "ADVANCED", |
|
|
scenarios: [ |
|
|
{ query: "Create high priority task", shouldTrigger: true, icon: "π" }, |
|
|
{ query: "Add new task", shouldTrigger: true, icon: "β" }, |
|
|
{ query: "List completed tasks", shouldTrigger: false, icon: "π" }, |
|
|
{ query: "Mark task as done", shouldTrigger: false, icon: "β
" }, |
|
|
{ query: "Create task with low priority", shouldTrigger: true, icon: "π" }, |
|
|
{ query: "Delete tasks", shouldTrigger: false, icon: "ποΈ" }, |
|
|
{ query: "Update task deadline", shouldTrigger: false, icon: "π
" }, |
|
|
], |
|
|
competitors: [ |
|
|
{ |
|
|
name: "list_tasks", |
|
|
description: "Retrieve and list existing tasks with filters" |
|
|
}, |
|
|
{ |
|
|
name: "update_task_status", |
|
|
description: "Change the status of an existing task" |
|
|
}, |
|
|
{ |
|
|
name: "delete_task", |
|
|
description: "Remove a task from the system" |
|
|
} |
|
|
], |
|
|
hints: [ |
|
|
"Focus on CREATE/ADD operations only", |
|
|
"Use enum for priority: ['low', 'medium', 'high']", |
|
|
"Use enum for initial status: ['pending', 'in_progress']", |
|
|
"Make your description explicitly mention 'create' or 'add'", |
|
|
"Distinguish from update, delete, and list operations" |
|
|
], |
|
|
starterCode: { |
|
|
name: "create_task", |
|
|
description: "Create a new task with title, description, priority level, and initial status", |
|
|
parameters: { |
|
|
type: "object", |
|
|
properties: { |
|
|
title: { |
|
|
type: "string", |
|
|
description: "The task title or name" |
|
|
}, |
|
|
priority: { |
|
|
type: "string", |
|
|
enum: ["low", "medium", "high"], |
|
|
description: "Priority level of the task" |
|
|
}, |
|
|
status: { |
|
|
type: "string", |
|
|
enum: ["pending", "in_progress"], |
|
|
description: "Initial status of the task" |
|
|
} |
|
|
}, |
|
|
required: ["title", "priority"] |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
id: 4, |
|
|
title: "MULTI_DOMAIN_CHAOS", |
|
|
description: "Calendar event creation vs. reminders vs. notifications. Extreme precision required.", |
|
|
difficulty: "EXPERT", |
|
|
scenarios: [ |
|
|
{ query: "Schedule meeting for tomorrow", shouldTrigger: true, icon: "π
" }, |
|
|
{ query: "Create calendar event", shouldTrigger: true, icon: "π₯" }, |
|
|
{ query: "Set reminder to call mom", shouldTrigger: false, icon: "π" }, |
|
|
{ query: "Send notification", shouldTrigger: false, icon: "π¬" }, |
|
|
{ query: "Add event to calendar", shouldTrigger: true, icon: "π" }, |
|
|
{ query: "Remind me about meeting", shouldTrigger: false, icon: "β°" }, |
|
|
{ query: "List calendar events", shouldTrigger: false, icon: "π" }, |
|
|
{ query: "Block time on calendar", shouldTrigger: true, icon: "π―" }, |
|
|
], |
|
|
competitors: [ |
|
|
{ |
|
|
name: "create_reminder", |
|
|
description: "Set a reminder notification for a specific time or event" |
|
|
}, |
|
|
{ |
|
|
name: "send_notification", |
|
|
description: "Send an immediate or scheduled notification to the user" |
|
|
}, |
|
|
{ |
|
|
name: "list_events", |
|
|
description: "Retrieve calendar events within a date range" |
|
|
}, |
|
|
{ |
|
|
name: "update_event", |
|
|
description: "Modify an existing calendar event" |
|
|
} |
|
|
], |
|
|
hints: [ |
|
|
"Calendar events are time-blocked entries with start/end times", |
|
|
"Reminders are notifications, not calendar entries", |
|
|
"Use parameters: title, start_time, end_time, location", |
|
|
"Emphasize 'calendar', 'schedule', 'meeting', 'event' in description", |
|
|
"Explicitly exclude reminders and notifications in your description" |
|
|
], |
|
|
starterCode: { |
|
|
name: "create_calendar_event", |
|
|
description: "Create a calendar event with time-blocked scheduling for meetings and appointments", |
|
|
parameters: { |
|
|
type: "object", |
|
|
properties: { |
|
|
title: { |
|
|
type: "string", |
|
|
description: "Event title or meeting name" |
|
|
}, |
|
|
start_time: { |
|
|
type: "string", |
|
|
description: "Event start time (ISO 8601 format)" |
|
|
}, |
|
|
end_time: { |
|
|
type: "string", |
|
|
description: "Event end time (ISO 8601 format)" |
|
|
}, |
|
|
location: { |
|
|
type: "string", |
|
|
description: "Physical or virtual meeting location" |
|
|
} |
|
|
}, |
|
|
required: ["title", "start_time", "end_time"] |
|
|
} |
|
|
} |
|
|
}, |
|
|
{ |
|
|
id: 5, |
|
|
title: "ULTIMATE_PRECISION", |
|
|
description: "Payment processing with strict parameters. Security-critical function calling.", |
|
|
difficulty: "MASTER", |
|
|
scenarios: [ |
|
|
{ query: "Process payment $99.99", shouldTrigger: true, icon: "π³" }, |
|
|
{ query: "Charge customer $250", shouldTrigger: true, icon: "π°" }, |
|
|
{ query: "Show payment history", shouldTrigger: false, icon: "π" }, |
|
|
{ query: "Refund transaction", shouldTrigger: false, icon: "β©οΈ" }, |
|
|
{ query: "Process payment $1500", shouldTrigger: true, icon: "π¦" }, |
|
|
{ query: "Verify payment status", shouldTrigger: false, icon: "π" }, |
|
|
{ query: "Cancel payment", shouldTrigger: false, icon: "β" }, |
|
|
{ query: "Charge $45.50", shouldTrigger: true, icon: "π΅" }, |
|
|
{ query: "Update payment method", shouldTrigger: false, icon: "βοΈ" }, |
|
|
], |
|
|
competitors: [ |
|
|
{ |
|
|
name: "refund_payment", |
|
|
description: "Issue a refund for a completed transaction" |
|
|
}, |
|
|
{ |
|
|
name: "verify_payment", |
|
|
description: "Check the status and validity of a payment transaction" |
|
|
}, |
|
|
{ |
|
|
name: "get_payment_history", |
|
|
description: "Retrieve past payment transactions and history" |
|
|
}, |
|
|
{ |
|
|
name: "cancel_payment", |
|
|
description: "Cancel a pending or scheduled payment" |
|
|
}, |
|
|
{ |
|
|
name: "update_payment_method", |
|
|
description: "Modify or change stored payment method information" |
|
|
} |
|
|
], |
|
|
hints: [ |
|
|
"This is about PROCESSING/CHARGING, not viewing or refunding", |
|
|
"Require amount parameter with number type", |
|
|
"Use enum for payment_method: ['credit_card', 'debit_card', 'bank_transfer', 'saved_method']", |
|
|
"Add currency parameter with enum: ['USD', 'EUR', 'GBP']", |
|
|
"Make description security-conscious and specific to charging", |
|
|
"Distinguish clearly from refund, verify, and cancel operations" |
|
|
], |
|
|
starterCode: { |
|
|
name: "process_payment", |
|
|
description: "Process a payment transaction with secure handling of payment method and currency", |
|
|
parameters: { |
|
|
type: "object", |
|
|
properties: { |
|
|
amount: { |
|
|
type: "number", |
|
|
description: "Payment amount in the specified currency" |
|
|
}, |
|
|
currency: { |
|
|
type: "string", |
|
|
enum: ["USD", "EUR", "GBP"], |
|
|
description: "Currency code for the transaction" |
|
|
}, |
|
|
payment_method: { |
|
|
type: "string", |
|
|
enum: ["credit_card", "debit_card", "bank_transfer", "saved_method"], |
|
|
description: "The payment method to use" |
|
|
}, |
|
|
customer_id: { |
|
|
type: "string", |
|
|
description: "Customer identifier for the transaction" |
|
|
} |
|
|
}, |
|
|
required: ["amount", "currency", "payment_method", "customer_id"] |
|
|
} |
|
|
} |
|
|
} |
|
|
]; |
|
|
|
|
|
let state = { |
|
|
levelIdx: 0, |
|
|
totalScore: 0, |
|
|
model: null, |
|
|
tokenizer: null, |
|
|
booted: false, |
|
|
currentFunction: null, |
|
|
achievements: [], |
|
|
streak: 0, |
|
|
bestStreak: 0, |
|
|
levelAttempts: {}, |
|
|
hintsUsed: 0, |
|
|
modelInfo: { |
|
|
name: null, |
|
|
version: null, |
|
|
quantization: null, |
|
|
device: null |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
function loadState() { |
|
|
try { |
|
|
const saved = localStorage.getItem('functionArenaState'); |
|
|
if (saved) { |
|
|
const parsed = JSON.parse(saved); |
|
|
state.totalScore = parsed.totalScore || 0; |
|
|
state.achievements = parsed.achievements || []; |
|
|
state.bestStreak = parsed.bestStreak || 0; |
|
|
state.levelAttempts = parsed.levelAttempts || {}; |
|
|
state.hintsUsed = parsed.hintsUsed || 0; |
|
|
} |
|
|
} catch(e) { |
|
|
console.log('No saved state found'); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function saveState() { |
|
|
try { |
|
|
localStorage.setItem('functionArenaState', JSON.stringify({ |
|
|
totalScore: state.totalScore, |
|
|
achievements: state.achievements, |
|
|
bestStreak: state.bestStreak, |
|
|
levelAttempts: state.levelAttempts, |
|
|
hintsUsed: state.hintsUsed |
|
|
})); |
|
|
} catch(e) { |
|
|
console.error('Failed to save state:', e); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
loadState(); |
|
|
|
|
|
|
|
|
|
|
|
async function bootSequence() { |
|
|
const instructions = [ |
|
|
"π― WELCOME TO FUNCTION ARENA", |
|
|
"", |
|
|
"Your mission: Create precise function definitions that trigger", |
|
|
"in the RIGHT scenarios and stay silent in the WRONG ones.", |
|
|
"", |
|
|
"π HOW TO PLAY:", |
|
|
"1. Review test scenarios - some should trigger (β), others shouldn't (β)", |
|
|
"2. Write your function with a clear name and description", |
|
|
"3. Add parameters with proper types and descriptions", |
|
|
"4. Use enums to constrain values when appropriate", |
|
|
"5. Test your function against all scenarios", |
|
|
"", |
|
|
"π‘ KEY TIPS:", |
|
|
"β’ Function names matter: 'get_weather' beats 'do_stuff'", |
|
|
"β’ Descriptions are KEY - AI reads this to decide when to call", |
|
|
"β’ Be specific about WHAT your function does AND what it doesn't", |
|
|
"β’ Use enums wisely to constrain inputs and clarify intent", |
|
|
"", |
|
|
"π SCORING:", |
|
|
"+100 points for correct triggers (when you SHOULD)", |
|
|
"-50 points for incorrect triggers (when you SHOULDN'T)", |
|
|
"+50 bonus for correctly NOT triggering (when you SHOULDN'T)", |
|
|
"", |
|
|
"β³ Please read the instructions above carefully...", |
|
|
" Model is loading in the background (may take a moment on first load)" |
|
|
]; |
|
|
|
|
|
const container = document.getElementById('boot-sequence'); |
|
|
container.style.justifyContent = 'center'; |
|
|
container.style.padding = '60px 40px'; |
|
|
|
|
|
|
|
|
const instructionBox = document.createElement('div'); |
|
|
instructionBox.style.cssText = ` |
|
|
max-width: 800px; |
|
|
line-height: 1.8; |
|
|
font-size: 16px; |
|
|
`; |
|
|
|
|
|
instructions.forEach((line, i) => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'boot-line'; |
|
|
div.textContent = line; |
|
|
div.style.marginBottom = line === '' ? '10px' : '5px'; |
|
|
div.style.fontSize = line.includes('π―') ? '24px' : |
|
|
line.includes('π') || line.includes('π‘') || line.includes('π') || line.includes('β³') ? '18px' : '16px'; |
|
|
div.style.fontWeight = line.includes('π―') || line.includes('π') || line.includes('π‘') || line.includes('π') || line.includes('β³') ? 'bold' : 'normal'; |
|
|
div.style.color = line.includes('β³') ? 'var(--text-muted)' : 'var(--acid)'; |
|
|
instructionBox.appendChild(div); |
|
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
div.style.opacity = 1; |
|
|
div.style.transform = "translateY(0)"; |
|
|
}, i * 80); |
|
|
}); |
|
|
|
|
|
container.innerHTML = ''; |
|
|
container.appendChild(instructionBox); |
|
|
|
|
|
|
|
|
const modelLoadPromise = loadModel(); |
|
|
|
|
|
|
|
|
|
|
|
await new Promise(r => setTimeout(r, 8000)); |
|
|
|
|
|
|
|
|
const lastInstruction = instructionBox.lastChild; |
|
|
lastInstruction.textContent = "β³ Waiting for model to finish loading..."; |
|
|
lastInstruction.style.color = 'var(--acid)'; |
|
|
|
|
|
await modelLoadPromise; |
|
|
|
|
|
container.style.transition = "opacity 0.5s"; |
|
|
container.style.opacity = 0; |
|
|
setTimeout(() => { |
|
|
container.style.display = 'none'; |
|
|
document.getElementById('app').style.opacity = 1; |
|
|
state.booted = true; |
|
|
renderLevel(); |
|
|
toggleRules(); |
|
|
}, 500); |
|
|
} |
|
|
|
|
|
async function loadModel() { |
|
|
const loadStartTime = performance.now(); |
|
|
const loader = document.getElementById('modelLoader'); |
|
|
const progressBar = document.getElementById('modelProgressBar'); |
|
|
const loaderText = document.getElementById('loaderText'); |
|
|
const loaderSubtext = document.getElementById('loaderSubtext'); |
|
|
const deviceInfo = document.getElementById('deviceInfo'); |
|
|
|
|
|
loader.classList.remove('hidden'); |
|
|
|
|
|
|
|
|
const hasWebGPU = !!navigator.gpu; |
|
|
const modelId = "onnx-community/functiongemma-270m-it-ONNX"; |
|
|
|
|
|
|
|
|
if (hasWebGPU) { |
|
|
deviceInfo.textContent = 'Using GPU acceleration'; |
|
|
deviceInfo.style.color = 'var(--success)'; |
|
|
} else { |
|
|
deviceInfo.textContent = 'Using CPU (may be slower)'; |
|
|
deviceInfo.style.color = 'var(--text-muted)'; |
|
|
} |
|
|
|
|
|
try { |
|
|
|
|
|
loaderText.textContent = 'Loading AI Engine'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--text-muted); font-size: 0.75rem;">Preparing...</span>'; |
|
|
progressBar.style.width = '15%'; |
|
|
|
|
|
const { env, AutoTokenizer, AutoModelForCausalLM } = await import( |
|
|
"https://cdn.jsdelivr.net/npm/@huggingface/transformers@latest" |
|
|
); |
|
|
|
|
|
env.allowRemoteModels = true; |
|
|
env.allowLocalModels = false; |
|
|
env.useBrowserCache = true; |
|
|
|
|
|
|
|
|
loaderText.textContent = 'Loading AI Engine'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--text-muted); font-size: 0.75rem;">Preparing language processor...</span>'; |
|
|
progressBar.style.width = '30%'; |
|
|
|
|
|
let tokenizerCached = false; |
|
|
state.tokenizer = await AutoTokenizer.from_pretrained(modelId, { |
|
|
progress_callback: (progress) => { |
|
|
if (progress && (progress.status === 'done' || progress.file)) { |
|
|
tokenizerCached = progress.cached || false; |
|
|
if (tokenizerCached) { |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--success); font-size: 0.75rem;">Using cached data</span>'; |
|
|
} else { |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--text-muted); font-size: 0.75rem;">Downloading...</span>'; |
|
|
} |
|
|
console.log(`β Tokenizer ${tokenizerCached ? 'cached' : 'downloaded'}`); |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
progressBar.style.width = '40%'; |
|
|
|
|
|
|
|
|
let modelConfig = {}; |
|
|
let modelCached = false; |
|
|
|
|
|
if (hasWebGPU) { |
|
|
|
|
|
modelConfig = { |
|
|
dtype: "q4", |
|
|
device: "webgpu" |
|
|
}; |
|
|
loaderText.textContent = 'Loading AI Model'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--text-muted); font-size: 0.75rem;">Downloading model...</span>'; |
|
|
console.log("β Using WebGPU with q4 quantization"); |
|
|
} else { |
|
|
|
|
|
modelConfig = { |
|
|
dtype: "q8", |
|
|
device: "wasm" |
|
|
}; |
|
|
loaderText.textContent = 'Loading AI Model'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--text-muted); font-size: 0.75rem;">Downloading model...</span>'; |
|
|
console.log("β WebGPU unavailable, using WASM with q8 quantization"); |
|
|
} |
|
|
|
|
|
state.model = await AutoModelForCausalLM.from_pretrained(modelId, { |
|
|
...modelConfig, |
|
|
progress_callback: (progress) => { |
|
|
if (!progress) return; |
|
|
|
|
|
|
|
|
if (progress.status === 'progress' || (progress.loaded && progress.total)) { |
|
|
const loaded = progress.loaded || 0; |
|
|
const total = progress.total || 1; |
|
|
const percent = 40 + Math.round((loaded / total) * 55); |
|
|
progressBar.style.width = `${percent}%`; |
|
|
|
|
|
if (progress.cached) { |
|
|
modelCached = true; |
|
|
loaderText.textContent = 'Loading AI Model'; |
|
|
loaderSubtext.innerHTML = `<span style="color: var(--success); font-size: 0.75rem;">${percent}% (cached)</span>`; |
|
|
} else { |
|
|
loaderText.textContent = 'Loading AI Model'; |
|
|
loaderSubtext.innerHTML = `<span style="color: var(--text-muted); font-size: 0.75rem;">${percent}%</span>`; |
|
|
} |
|
|
} else if (progress.status === 'done' || progress.file) { |
|
|
progressBar.style.width = '98%'; |
|
|
if (progress.cached || modelCached) { |
|
|
loaderText.textContent = 'AI Model Ready'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--success); font-size: 0.75rem;">Loaded from cache</span>'; |
|
|
console.log("β Model loaded from cache"); |
|
|
} else { |
|
|
loaderText.textContent = 'AI Model Ready'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--success); font-size: 0.75rem;">Downloaded & cached</span>'; |
|
|
console.log("β Model downloaded and cached"); |
|
|
} |
|
|
} |
|
|
} |
|
|
}); |
|
|
|
|
|
|
|
|
progressBar.style.width = '100%'; |
|
|
const loadTime = ((performance.now() - loadStartTime) / 1000).toFixed(1); |
|
|
loaderText.textContent = 'Ready!'; |
|
|
loaderSubtext.innerHTML = `<span style="color: var(--success); font-size: 0.75rem;">Loaded in ${loadTime}s</span>`; |
|
|
await new Promise(r => setTimeout(r, 500)); |
|
|
|
|
|
|
|
|
state.modelInfo = { |
|
|
name: "functiongemma-270m", |
|
|
version: "it-ONNX", |
|
|
quantization: modelConfig.dtype || 'fp32', |
|
|
device: modelConfig.device || 'default' |
|
|
}; |
|
|
|
|
|
|
|
|
telemetry.trackModelLoad(parseFloat(loadTime)); |
|
|
telemetry.trackCacheHit(modelCached || tokenizerCached); |
|
|
|
|
|
|
|
|
try { |
|
|
const db = await dbPromise; |
|
|
await db.put('models', { |
|
|
modelId: modelId, |
|
|
config: modelConfig, |
|
|
loadTime: loadTime, |
|
|
cached: modelCached || tokenizerCached, |
|
|
timestamp: Date.now() |
|
|
}, 'current-model'); |
|
|
} catch (e) { |
|
|
console.log('IndexedDB cache write failed:', e); |
|
|
} |
|
|
|
|
|
console.log("β Model loaded successfully"); |
|
|
console.log(` Device: ${modelConfig.device || 'default'}`); |
|
|
console.log(` Quantization: ${modelConfig.dtype || 'fp32'}`); |
|
|
console.log(` Cached: ${modelCached || tokenizerCached ? 'Yes' : 'No (first load)'}`); |
|
|
console.log(` Load time: ${loadTime}s`); |
|
|
|
|
|
|
|
|
updateModelInfoDisplay(); |
|
|
|
|
|
|
|
|
setupCacheWarmup(); |
|
|
|
|
|
} catch(e) { |
|
|
console.error("Model load failed:", e); |
|
|
loaderText.textContent = 'Loading Failed'; |
|
|
loaderSubtext.innerHTML = `<span style="color: var(--alert); font-size: 0.75rem;">Please refresh</span>`; |
|
|
progressBar.style.background = 'var(--alert)'; |
|
|
|
|
|
|
|
|
if (e.message && e.message.includes('webgpu') && hasWebGPU) { |
|
|
console.log("β WebGPU failed, retrying with WASM fallback..."); |
|
|
loaderText.textContent = 'Retrying...'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--text-muted); font-size: 0.75rem;">Using CPU mode</span>'; |
|
|
progressBar.style.background = 'var(--acid)'; |
|
|
|
|
|
try { |
|
|
state.model = await AutoModelForCausalLM.from_pretrained(modelId, { |
|
|
dtype: "q8", |
|
|
device: "wasm" |
|
|
}); |
|
|
progressBar.style.width = '100%'; |
|
|
progressBar.style.background = 'var(--acid)'; |
|
|
loaderText.textContent = 'Ready!'; |
|
|
loaderSubtext.innerHTML = '<span style="color: var(--success); font-size: 0.75rem;">Loaded (CPU mode)</span>'; |
|
|
console.log("β Model loaded with CPU fallback"); |
|
|
|
|
|
|
|
|
state.modelInfo = { |
|
|
name: "functiongemma-270m", |
|
|
version: "it-ONNX", |
|
|
quantization: "q8", |
|
|
device: "wasm" |
|
|
}; |
|
|
updateModelInfoDisplay(); |
|
|
} catch(fallbackError) { |
|
|
console.error("Fallback also failed:", fallbackError); |
|
|
throw fallbackError; |
|
|
} |
|
|
} else { |
|
|
throw e; |
|
|
} |
|
|
} finally { |
|
|
|
|
|
setTimeout(() => { |
|
|
loader.classList.add('hidden'); |
|
|
}, 500); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
function renderLevel() { |
|
|
const lvl = levels[state.levelIdx]; |
|
|
|
|
|
document.getElementById('lvlName').innerText = lvl.title; |
|
|
document.getElementById('lvlDesc').innerText = lvl.description; |
|
|
document.getElementById('objectiveBadge').innerText = `LEVEL ${lvl.id} // ${lvl.difficulty}`; |
|
|
document.getElementById('levelDisp').innerText = `0${lvl.id}`; |
|
|
document.getElementById('scenarioCount').innerText = lvl.scenarios.length; |
|
|
document.getElementById('scoreDisp').innerText = state.totalScore.toString().padStart(4, '0'); |
|
|
|
|
|
|
|
|
const grid = document.getElementById('scenarioGrid'); |
|
|
grid.innerHTML = ''; |
|
|
lvl.scenarios.forEach((s, i) => { |
|
|
const div = document.createElement('div'); |
|
|
div.className = 'scenario-card'; |
|
|
div.innerHTML = ` |
|
|
<div class="scenario-icon">${s.icon}</div> |
|
|
<div class="scenario-text">${s.query}</div> |
|
|
<div class="scenario-expected ${s.shouldTrigger ? 'should-trigger' : 'should-not'}"> |
|
|
${s.shouldTrigger ? 'β SHOULD' : 'β SHOULD NOT'} |
|
|
</div> |
|
|
`; |
|
|
div.style.opacity = '0'; |
|
|
div.style.transform = 'translateY(10px)'; |
|
|
grid.appendChild(div); |
|
|
|
|
|
setTimeout(() => { |
|
|
div.style.transition = 'all 0.3s'; |
|
|
div.style.opacity = '1'; |
|
|
div.style.transform = 'translateY(0)'; |
|
|
}, i * 50); |
|
|
}); |
|
|
|
|
|
|
|
|
const compList = document.getElementById('competitorList'); |
|
|
if (lvl.competitors.length === 0) { |
|
|
compList.innerHTML = '<div style="color: var(--success);">No competitors this level!</div>'; |
|
|
} else { |
|
|
compList.innerHTML = lvl.competitors.map(c => ` |
|
|
<div class="competitor-fn"> |
|
|
<div class="fn-name-display">${c.name}()</div> |
|
|
<div style="font-size: 0.75rem; color: #888;">${c.description}</div> |
|
|
</div> |
|
|
`).join(''); |
|
|
} |
|
|
|
|
|
|
|
|
const achievementsList = document.getElementById('achievementsList'); |
|
|
if (achievementsList) { |
|
|
if (state.achievements.length === 0) { |
|
|
achievementsList.innerHTML = '<div style="color: #666;">No achievements yet...</div>'; |
|
|
} else { |
|
|
const names = { |
|
|
'first_perfect': 'π Perfect Score', |
|
|
'streak_3': 'π₯ On Fire!', |
|
|
'streak_5': 'β‘ Unstoppable!', |
|
|
'score_1k': 'π― Score Master', |
|
|
'all_levels': 'π Master Engineer' |
|
|
}; |
|
|
achievementsList.innerHTML = state.achievements.map(a => |
|
|
`<div style="color: var(--acid); margin-bottom: 5px;">${names[a] || a}</div>` |
|
|
).join(''); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
updateStreakDisplay(); |
|
|
|
|
|
|
|
|
document.getElementById('functionEditor').value = JSON.stringify(lvl.starterCode, null, 2); |
|
|
validateJSON(); |
|
|
|
|
|
setTimeout(autoResizeTextarea, 100); |
|
|
|
|
|
|
|
|
document.getElementById('resultPanel').classList.remove('visible'); |
|
|
document.getElementById('nextBtn').style.display = 'none'; |
|
|
} |
|
|
|
|
|
|
|
|
const functionEditor = document.getElementById('functionEditor'); |
|
|
functionEditor.addEventListener('input', () => { |
|
|
validateJSON(); |
|
|
autoResizeTextarea(); |
|
|
}); |
|
|
|
|
|
function getStandardTemplate() { |
|
|
|
|
|
return { |
|
|
name: "function_name", |
|
|
description: "Clear, descriptive description of what this function does. Mention key operations and purpose.", |
|
|
parameters: { |
|
|
type: "object", |
|
|
properties: { |
|
|
param_1: { |
|
|
type: "string", |
|
|
description: "Description of the first parameter" |
|
|
}, |
|
|
param_2: { |
|
|
type: "string", |
|
|
enum: ["option_a", "option_b", "option_c"], |
|
|
description: "Parameter with fixed options (remove enum if not needed)" |
|
|
}, |
|
|
param_3: { |
|
|
type: "number", |
|
|
description: "Numeric parameter (use number for amounts, counts, etc)" |
|
|
} |
|
|
}, |
|
|
required: ["param_1", "param_2"] |
|
|
}, |
|
|
_hints: [ |
|
|
"π‘ TIP: Function name should be specific and descriptive", |
|
|
"π‘ TIP: Description is KEY - AI reads this to decide when to call your function", |
|
|
"π‘ TIP: Use enums to constrain values and make intent clearer", |
|
|
"π‘ TIP: Required fields help the model understand what's essential", |
|
|
"π‘ TIP: Be explicit about what your function does AND what it doesn't do" |
|
|
] |
|
|
}; |
|
|
} |
|
|
|
|
|
function getTemplateWithComments() { |
|
|
return `{ |
|
|
// π‘ FUNCTION DEFINITION TEMPLATE WITH HINTS |
|
|
// =========================================== |
|
|
// |
|
|
// π‘ TIP: Function name should be specific and descriptive |
|
|
// Good: "get_weather", "create_task", "process_payment" |
|
|
// Bad: "do_stuff", "function1", "data" |
|
|
// |
|
|
"name": "function_name", |
|
|
|
|
|
// π‘ TIP: Description is THE MOST IMPORTANT part! |
|
|
// - AI reads this to decide when to call your function |
|
|
// - Be specific about what it does |
|
|
// - Mention key operations (get, create, process, etc.) |
|
|
// - Explicitly exclude what it doesn't do (if needed) |
|
|
// |
|
|
"description": "Clear, descriptive description of what this function does. Mention key operations and purpose.", |
|
|
|
|
|
"parameters": { |
|
|
"type": "object", |
|
|
"properties": { |
|
|
// π‘ TIP: Parameter names should be clear and descriptive |
|
|
"param_1": { |
|
|
"type": "string", |
|
|
// π‘ TIP: Parameter description helps AI understand what to extract |
|
|
"description": "Description of the first parameter" |
|
|
}, |
|
|
|
|
|
// π‘ TIP: Use enums to constrain values - makes intent clearer! |
|
|
// Remove the enum array if you don't need fixed options |
|
|
"param_2": { |
|
|
"type": "string", |
|
|
"enum": ["option_a", "option_b", "option_c"], |
|
|
"description": "Parameter with fixed options (remove enum if not needed)" |
|
|
}, |
|
|
|
|
|
// π‘ TIP: Use "number" type for amounts, counts, quantities |
|
|
// Use "string" for text, IDs, names |
|
|
// Use "boolean" for true/false values |
|
|
"param_3": { |
|
|
"type": "number", |
|
|
"description": "Numeric parameter (use number for amounts, counts, etc)" |
|
|
} |
|
|
}, |
|
|
// π‘ TIP: Required fields help the model understand what's essential |
|
|
// Only include parameters that are truly required |
|
|
"required": ["param_1", "param_2"] |
|
|
} |
|
|
}`; |
|
|
} |
|
|
|
|
|
window.loadTemplate = function() { |
|
|
const editor = document.getElementById('functionEditor'); |
|
|
|
|
|
editor.value = getTemplateWithComments(); |
|
|
|
|
|
try { |
|
|
|
|
|
const cleaned = editor.value.replace(/\/\/.*$/gm, '').replace(/\/\*[\s\S]*?\*\//g, ''); |
|
|
const parsed = JSON.parse(cleaned); |
|
|
state.currentFunction = parsed; |
|
|
document.getElementById('jsonOutput').style.color = 'var(--success)'; |
|
|
document.getElementById('jsonOutput').textContent = 'β Template loaded (comments are for reference only)'; |
|
|
} catch(e) { |
|
|
document.getElementById('jsonOutput').style.color = 'var(--text-muted)'; |
|
|
document.getElementById('jsonOutput').textContent = 'π‘ Template with hints loaded. Remove comments (// lines) before testing.'; |
|
|
} |
|
|
|
|
|
|
|
|
setTimeout(autoResizeTextarea, 100); |
|
|
}; |
|
|
|
|
|
|
|
|
function addLog(label, content, type = 'info') { |
|
|
const logsContent = document.getElementById('logsContent'); |
|
|
|
|
|
|
|
|
if (logsContent.innerHTML.includes('Logs will appear here')) { |
|
|
logsContent.innerHTML = ''; |
|
|
} |
|
|
|
|
|
const entry = document.createElement('div'); |
|
|
entry.className = 'log-entry'; |
|
|
|
|
|
const time = new Date().toLocaleTimeString(); |
|
|
let html = `<div class="log-time">[${time}]</div>`; |
|
|
html += `<span class="log-label">${label}</span>`; |
|
|
|
|
|
if (type === 'prompt') { |
|
|
html += `<div class="log-prompt"><strong>π PROMPT:</strong><br>"${content}"</div>`; |
|
|
} else if (type === 'tools') { |
|
|
html += `<div class="log-tools"><strong>π§ AVAILABLE FUNCTIONS:</strong><br>${content}</div>`; |
|
|
} else if (type === 'triggered') { |
|
|
html += `<span class="log-success">β
${content}</span>`; |
|
|
} else if (type === 'error') { |
|
|
html += `<span class="log-error">β ${content}</span>`; |
|
|
} else { |
|
|
html += `<span class="log-${type}">${content}</span>`; |
|
|
} |
|
|
|
|
|
entry.innerHTML = html; |
|
|
logsContent.insertBefore(entry, logsContent.firstChild); |
|
|
logsContent.scrollTop = 0; |
|
|
} |
|
|
|
|
|
window.clearLogs = function() { |
|
|
const logsContent = document.getElementById('logsContent'); |
|
|
logsContent.innerHTML = '<div style="color: #666; text-align: center; padding: 40px 20px;">Logs cleared. Run a test to see new logs...</div>'; |
|
|
}; |
|
|
|
|
|
|
|
|
function autoResizeTextarea() { |
|
|
const editor = document.getElementById('functionEditor'); |
|
|
if (editor) { |
|
|
|
|
|
editor.style.height = 'auto'; |
|
|
|
|
|
const newHeight = Math.max(300, editor.scrollHeight); |
|
|
editor.style.height = newHeight + 'px'; |
|
|
} |
|
|
} |
|
|
|
|
|
function validateJSON() { |
|
|
const editor = document.getElementById('functionEditor'); |
|
|
const output = document.getElementById('jsonOutput'); |
|
|
|
|
|
try { |
|
|
|
|
|
let cleaned = editor.value |
|
|
.replace(/\/\/.*$/gm, '') |
|
|
.replace(/\/\*[\s\S]*?\*\//g, ''); |
|
|
|
|
|
const parsed = JSON.parse(cleaned); |
|
|
state.currentFunction = parsed; |
|
|
|
|
|
|
|
|
if (!parsed.name || !parsed.description) { |
|
|
throw new Error("Missing 'name' or 'description'"); |
|
|
} |
|
|
|
|
|
|
|
|
const warnings = []; |
|
|
if (parsed.name === 'function_name' || parsed.name.includes('_name')) { |
|
|
warnings.push('β οΈ Consider changing the function name to something specific'); |
|
|
} |
|
|
if (parsed.description.length < 20) { |
|
|
warnings.push('β οΈ Description seems short - be more descriptive!'); |
|
|
} |
|
|
if (parsed.description === 'Clear, descriptive description of what this function does. Mention key operations and purpose.') { |
|
|
warnings.push('β οΈ Update the description to match your function!'); |
|
|
} |
|
|
|
|
|
output.style.color = 'var(--success)'; |
|
|
let outputText = 'β Valid JSON Schema\n\n' + JSON.stringify(parsed, null, 2); |
|
|
if (warnings.length > 0) { |
|
|
outputText = warnings.join('\n') + '\n\n' + outputText; |
|
|
output.style.color = 'var(--acid)'; |
|
|
} |
|
|
output.textContent = outputText; |
|
|
} catch(e) { |
|
|
output.style.color = 'var(--alert)'; |
|
|
output.textContent = `β JSON Error: ${e.message}\n\nπ‘ Tip: Remove any comments (// lines) before testing.\nπ‘ Tip: Make sure all strings are in double quotes.`; |
|
|
state.currentFunction = null; |
|
|
} |
|
|
|
|
|
|
|
|
autoResizeTextarea(); |
|
|
} |
|
|
|
|
|
|
|
|
window.testFunction = async function() { |
|
|
if (!state.currentFunction) { |
|
|
alert('Please fix your JSON first!'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const btn = document.getElementById('testBtn'); |
|
|
const testLoader = document.getElementById('testLoader'); |
|
|
const testProgress = document.getElementById('testProgress'); |
|
|
|
|
|
btn.disabled = true; |
|
|
btn.innerText = 'TESTING...'; |
|
|
|
|
|
|
|
|
let loaderTimeout = setTimeout(() => { |
|
|
testLoader.classList.add('visible'); |
|
|
testProgress.textContent = 'Preparing test environment...'; |
|
|
}, 100); |
|
|
|
|
|
try { |
|
|
const lvl = levels[state.levelIdx]; |
|
|
const results = []; |
|
|
let score = 0; |
|
|
const totalScenarios = lvl.scenarios.length; |
|
|
|
|
|
|
|
|
const tools = [ |
|
|
{ |
|
|
type: "function", |
|
|
function: state.currentFunction |
|
|
}, |
|
|
...lvl.competitors.map(c => ({ |
|
|
type: "function", |
|
|
function: c |
|
|
})) |
|
|
]; |
|
|
|
|
|
|
|
|
addLog('FUNCTION_SCHEMA', 'Loaded function definitions for testing', 'info'); |
|
|
const toolsHtml = tools.map(t => |
|
|
`<div class="log-function"><span class="log-function-name">${t.function.name}</span>() - ${t.function.description}</div>` |
|
|
).join(''); |
|
|
addLog('AVAILABLE_TOOLS', toolsHtml, 'tools'); |
|
|
|
|
|
|
|
|
for (let i = 0; i < lvl.scenarios.length; i++) { |
|
|
const scenario = lvl.scenarios[i]; |
|
|
const scenarioNum = i + 1; |
|
|
testProgress.textContent = `Testing scenario ${scenarioNum}/${totalScenarios}: "${scenario.query.substring(0, 40)}${scenario.query.length > 40 ? '...' : ''}"`; |
|
|
addLog('TEST_START', `Testing scenario: "${scenario.query}"`, 'info'); |
|
|
|
|
|
const messages = [ |
|
|
{ role: "developer", content: "You are a function calling model. Select the most appropriate function for the user's request." }, |
|
|
{ role: "user", content: scenario.query } |
|
|
]; |
|
|
|
|
|
addLog('USER_PROMPT', scenario.query, 'prompt'); |
|
|
|
|
|
try { |
|
|
const inputs = await state.tokenizer.apply_chat_template(messages, { |
|
|
tools: tools, |
|
|
tokenize: true, |
|
|
add_generation_prompt: true, |
|
|
return_dict: true |
|
|
}); |
|
|
|
|
|
const genStart = performance.now(); |
|
|
const output = await state.model.generate({ |
|
|
...inputs, |
|
|
max_new_tokens: 128, |
|
|
do_sample: false |
|
|
}); |
|
|
const genTime = performance.now() - genStart; |
|
|
|
|
|
|
|
|
telemetry.trackGeneration(genTime); |
|
|
|
|
|
const decoded = await state.tokenizer.decode(output.slice(0, [inputs.input_ids.dims[1], null]), { skip_special_tokens: false }); |
|
|
|
|
|
addLog('MODEL_OUTPUT', `Generated in ${genTime.toFixed(0)}ms`, 'info'); |
|
|
addLog('RAW_OUTPUT', decoded.substring(0, 200) + (decoded.length > 200 ? '...' : ''), 'info'); |
|
|
|
|
|
|
|
|
let triggered = null; |
|
|
|
|
|
|
|
|
const match1 = decoded.match(/call:\s*"?(\w+)"?/i); |
|
|
if (match1) triggered = match1[1]; |
|
|
|
|
|
|
|
|
if (!triggered) { |
|
|
const match2 = decoded.match(/function:\s*"?(\w+)"?/i); |
|
|
if (match2) triggered = match2[1]; |
|
|
} |
|
|
|
|
|
|
|
|
if (!triggered) { |
|
|
const match3 = decoded.match(/"name"\s*:\s*"(\w+)"/i); |
|
|
if (match3) triggered = match3[1]; |
|
|
} |
|
|
|
|
|
|
|
|
if (!triggered) { |
|
|
const match4 = decoded.match(/<tool_call[^>]*>[\s\S]*?name["\s:=]+(\w+)/i); |
|
|
if (match4) triggered = match4[1]; |
|
|
} |
|
|
|
|
|
|
|
|
if (!triggered) { |
|
|
const allFunctionNames = tools.map(t => t.function.name); |
|
|
for (const fnName of allFunctionNames) { |
|
|
|
|
|
const regex = new RegExp(`\\b${fnName}\\b`, 'i'); |
|
|
if (regex.test(decoded)) { |
|
|
|
|
|
const contextMatch = decoded.match(new RegExp(`(call|function|name|invoke|use|select)[\\s:="]*${fnName}`, 'i')); |
|
|
if (contextMatch) { |
|
|
triggered = fnName; |
|
|
break; |
|
|
} |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
addLog('FUNCTION_DETECTED', triggered ? triggered : 'NONE', triggered ? 'triggered' : 'error'); |
|
|
|
|
|
const triggeredYourFunction = triggered === state.currentFunction.name; |
|
|
const shouldHaveTriggered = scenario.shouldTrigger; |
|
|
|
|
|
|
|
|
let result = { |
|
|
query: scenario.query, |
|
|
expected: shouldHaveTriggered, |
|
|
triggered: triggeredYourFunction, |
|
|
correctFunction: triggered, |
|
|
icon: scenario.icon |
|
|
}; |
|
|
|
|
|
if (shouldHaveTriggered && triggeredYourFunction) { |
|
|
|
|
|
result.status = 'correct'; |
|
|
result.points = 100; |
|
|
score += 100; |
|
|
addLog('RESULT_EVAL', `β
CORRECT! Expected: ${state.currentFunction.name}, Got: ${triggered}`, 'success'); |
|
|
} else if (!shouldHaveTriggered && !triggeredYourFunction) { |
|
|
|
|
|
result.status = 'correct'; |
|
|
result.points = 50; |
|
|
score += 50; |
|
|
addLog('RESULT_EVAL', `β
CORRECT! Expected: NO TRIGGER, Got: NONE`, 'success'); |
|
|
} else if (shouldHaveTriggered && !triggeredYourFunction) { |
|
|
|
|
|
result.status = 'wrong'; |
|
|
result.points = 0; |
|
|
addLog('RESULT_EVAL', `β WRONG! Expected: ${state.currentFunction.name}, Got: ${triggered || 'NONE'}`, 'error'); |
|
|
} else { |
|
|
|
|
|
result.status = 'wrong'; |
|
|
result.points = -50; |
|
|
score -= 50; |
|
|
addLog('RESULT_EVAL', `β WRONG! Expected: NO TRIGGER, Got: ${triggered || 'NONE'}`, 'error'); |
|
|
} |
|
|
|
|
|
results.push(result); |
|
|
|
|
|
|
|
|
const scenarioCards = document.querySelectorAll('.scenario-card'); |
|
|
if (scenarioCards[i]) { |
|
|
scenarioCards[i].classList.add(result.status); |
|
|
setTimeout(() => { |
|
|
scenarioCards[i].classList.remove(result.status); |
|
|
}, 2000); |
|
|
} |
|
|
|
|
|
} catch(e) { |
|
|
console.error('Test error:', e); |
|
|
addLog('ERROR', `${e.message}`, 'error'); |
|
|
results.push({ |
|
|
query: scenario.query, |
|
|
status: 'error', |
|
|
points: 0, |
|
|
icon: scenario.icon |
|
|
}); |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
displayResults(results, score); |
|
|
} catch(error) { |
|
|
console.error('Test function error:', error); |
|
|
addLog('FATAL_ERROR', `Test failed: ${error.message}`, 'error'); |
|
|
} finally { |
|
|
|
|
|
clearTimeout(loaderTimeout); |
|
|
testLoader.classList.remove('visible'); |
|
|
btn.disabled = false; |
|
|
btn.innerText = 'TEST FUNCTION'; |
|
|
} |
|
|
}; |
|
|
|
|
|
function displayResults(results, score) { |
|
|
const panel = document.getElementById('resultPanel'); |
|
|
const title = document.getElementById('resultTitle'); |
|
|
const scoreEl = document.getElementById('resultScore'); |
|
|
const breakdown = document.getElementById('resultBreakdown'); |
|
|
|
|
|
const passed = results.every(r => r.status === 'correct'); |
|
|
|
|
|
title.innerText = passed ? 'β ALL TESTS PASSED' : 'β SOME TESTS FAILED'; |
|
|
title.className = `result-title ${passed ? 'success' : 'fail'}`; |
|
|
|
|
|
scoreEl.innerText = (score >= 0 ? '+' : '') + score; |
|
|
scoreEl.className = `result-score ${score >= 0 ? 'success' : 'fail'}`; |
|
|
|
|
|
breakdown.innerHTML = results.map(r => ` |
|
|
<div class="result-item ${r.status}"> |
|
|
<span>${r.icon} ${r.query}</span> |
|
|
<span style="font-weight: bold;"> |
|
|
${r.points >= 0 ? '+' : ''}${r.points} pts |
|
|
${r.correctFunction && r.correctFunction !== 'null' ? ` (${r.correctFunction})` : ''} |
|
|
</span> |
|
|
</div> |
|
|
`).join(''); |
|
|
|
|
|
panel.classList.add('visible'); |
|
|
|
|
|
|
|
|
document.getElementById('nextBtn').style.display = 'block'; |
|
|
|
|
|
if (passed) { |
|
|
|
|
|
state.totalScore += score; |
|
|
document.getElementById('scoreDisp').innerText = state.totalScore.toString().padStart(4, '0'); |
|
|
|
|
|
|
|
|
telemetry.trackLevelComplete(state.levelIdx + 1, score, true); |
|
|
|
|
|
|
|
|
checkAchievements(results, score); |
|
|
|
|
|
|
|
|
updateStreakDisplay(); |
|
|
} else { |
|
|
|
|
|
state.streak = 0; |
|
|
updateStreakDisplay(); |
|
|
|
|
|
|
|
|
telemetry.trackLevelComplete(state.levelIdx + 1, score, false); |
|
|
} |
|
|
|
|
|
|
|
|
setupCacheWarmup(); |
|
|
|
|
|
|
|
|
const totalScenarios = (state.levelIdx + 1) * levels[state.levelIdx].scenarios.length; |
|
|
const accuracy = totalScenarios > 0 ? Math.round((state.totalScore / (totalScenarios * 100)) * 100) : 0; |
|
|
document.getElementById('accuracyDisp').innerText = `${accuracy}%`; |
|
|
|
|
|
|
|
|
const levelKey = `level_${state.levelIdx + 1}`; |
|
|
state.levelAttempts[levelKey] = (state.levelAttempts[levelKey] || 0) + 1; |
|
|
saveState(); |
|
|
} |
|
|
|
|
|
|
|
|
window.nextLevel = function() { |
|
|
state.levelIdx++; |
|
|
if (state.levelIdx >= levels.length) { |
|
|
victory(); |
|
|
} else { |
|
|
clearLogs(); |
|
|
renderLevel(); |
|
|
|
|
|
|
|
|
if (typeof gtag !== 'undefined') { |
|
|
gtag('event', 'level_navigation', { |
|
|
level_id: state.levelIdx + 1, |
|
|
action: 'next' |
|
|
}); |
|
|
} |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
window.resetEditor = function() { |
|
|
const lvl = levels[state.levelIdx]; |
|
|
document.getElementById('functionEditor').value = JSON.stringify(lvl.starterCode, null, 2); |
|
|
validateJSON(); |
|
|
setTimeout(autoResizeTextarea, 100); |
|
|
}; |
|
|
|
|
|
|
|
|
function checkAchievements(results, score) { |
|
|
const newAchievements = []; |
|
|
|
|
|
|
|
|
if (results.every(r => r.status === 'correct') && !state.achievements.includes('first_perfect')) { |
|
|
newAchievements.push({ id: 'first_perfect', name: 'Perfect Score', desc: 'Got all scenarios correct!' }); |
|
|
state.achievements.push('first_perfect'); |
|
|
} |
|
|
|
|
|
|
|
|
if (results.every(r => r.status === 'correct')) { |
|
|
state.streak++; |
|
|
if (state.streak > state.bestStreak) { |
|
|
state.bestStreak = state.streak; |
|
|
} |
|
|
if (state.streak === 3 && !state.achievements.includes('streak_3')) { |
|
|
newAchievements.push({ id: 'streak_3', name: 'On Fire!', desc: '3 perfect levels in a row!' }); |
|
|
state.achievements.push('streak_3'); |
|
|
} |
|
|
if (state.streak === 5 && !state.achievements.includes('streak_5')) { |
|
|
newAchievements.push({ id: 'streak_5', name: 'Unstoppable!', desc: '5 perfect levels in a row!' }); |
|
|
state.achievements.push('streak_5'); |
|
|
} |
|
|
} else { |
|
|
state.streak = 0; |
|
|
} |
|
|
|
|
|
|
|
|
if (state.totalScore >= 1000 && !state.achievements.includes('score_1k')) { |
|
|
newAchievements.push({ id: 'score_1k', name: 'Score Master', desc: 'Reached 1000 points!' }); |
|
|
state.achievements.push('score_1k'); |
|
|
} |
|
|
|
|
|
|
|
|
if (state.levelIdx === 4 && !state.achievements.includes('all_levels')) { |
|
|
newAchievements.push({ id: 'all_levels', name: 'Master Engineer', desc: 'Completed all levels!' }); |
|
|
state.achievements.push('all_levels'); |
|
|
} |
|
|
|
|
|
|
|
|
if (newAchievements.length > 0) { |
|
|
showAchievementNotification(newAchievements); |
|
|
} |
|
|
|
|
|
saveState(); |
|
|
} |
|
|
|
|
|
function showAchievementNotification(achievements) { |
|
|
achievements.forEach((ach, idx) => { |
|
|
setTimeout(() => { |
|
|
const notif = document.createElement('div'); |
|
|
notif.style.cssText = ` |
|
|
position: fixed; |
|
|
top: 20px; |
|
|
right: 20px; |
|
|
background: var(--panel); |
|
|
border: 3px solid var(--acid); |
|
|
padding: 20px; |
|
|
z-index: 10002; |
|
|
animation: slideInRight 0.5s var(--ease-out-expo); |
|
|
box-shadow: var(--hard-shadow); |
|
|
max-width: 300px; |
|
|
`; |
|
|
notif.innerHTML = ` |
|
|
<div style="color: var(--acid); font-weight: bold; margin-bottom: 5px;">π ACHIEVEMENT UNLOCKED</div> |
|
|
<div style="font-size: 1.2rem; margin-bottom: 5px;">${ach.name}</div> |
|
|
<div style="color: var(--text-muted); font-size: 0.9rem;">${ach.desc}</div> |
|
|
`; |
|
|
document.body.appendChild(notif); |
|
|
|
|
|
setTimeout(() => { |
|
|
notif.style.animation = 'slideOutRight 0.5s var(--ease-out-expo)'; |
|
|
setTimeout(() => notif.remove(), 500); |
|
|
}, 3000); |
|
|
}, idx * 400); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
function updateStreakDisplay() { |
|
|
|
|
|
const streakEl = document.getElementById('streakDisp'); |
|
|
if (streakEl) { |
|
|
streakEl.innerText = state.streak; |
|
|
streakEl.parentElement.style.display = state.streak > 0 ? 'flex' : 'none'; |
|
|
} |
|
|
} |
|
|
|
|
|
function updateModelInfoDisplay() { |
|
|
const modelInfoEl = document.getElementById('modelInfo'); |
|
|
if (modelInfoEl && state.modelInfo.name) { |
|
|
document.getElementById('modelName').textContent = state.modelInfo.name; |
|
|
document.getElementById('modelVersion').textContent = state.modelInfo.version || '-'; |
|
|
document.getElementById('modelQuantization').textContent = state.modelInfo.quantization.toUpperCase(); |
|
|
document.getElementById('modelDevice').textContent = state.modelInfo.device.toUpperCase(); |
|
|
modelInfoEl.style.display = 'block'; |
|
|
} |
|
|
} |
|
|
|
|
|
|
|
|
window.toggleHints = function() { |
|
|
const panel = document.getElementById('hintPanel'); |
|
|
const content = document.getElementById('hintContent'); |
|
|
const lvl = levels[state.levelIdx]; |
|
|
|
|
|
if (panel.classList.contains('visible')) { |
|
|
panel.classList.remove('visible'); |
|
|
} else { |
|
|
state.hintsUsed++; |
|
|
saveState(); |
|
|
content.innerHTML = lvl.hints.map((h, i) => |
|
|
`<p style="margin-bottom: 8px;"><strong>${i+1}.</strong> ${h}</p>` |
|
|
).join(''); |
|
|
panel.classList.add('visible'); |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
window.toggleRules = function() { |
|
|
const modal = document.getElementById('rulesModal'); |
|
|
modal.classList.toggle('visible'); |
|
|
}; |
|
|
|
|
|
|
|
|
window.resetProgress = function() { |
|
|
if (!confirm('β οΈ WARNING: This will reset ALL progress, achievements, and scores!\n\nAre you sure?')) { |
|
|
return; |
|
|
} |
|
|
|
|
|
state.totalScore = 0; |
|
|
state.achievements = []; |
|
|
state.streak = 0; |
|
|
state.bestStreak = 0; |
|
|
state.levelAttempts = {}; |
|
|
state.hintsUsed = 0; |
|
|
state.levelIdx = 0; |
|
|
|
|
|
localStorage.removeItem('functionArenaState'); |
|
|
|
|
|
|
|
|
const scoreDisp = document.getElementById('scoreDisp'); |
|
|
const accuracyDisp = document.getElementById('accuracyDisp'); |
|
|
if (scoreDisp) scoreDisp.innerText = '0000'; |
|
|
if (accuracyDisp) accuracyDisp.innerText = '--'; |
|
|
updateStreakDisplay(); |
|
|
|
|
|
const achievementsList = document.getElementById('achievementsList'); |
|
|
if (achievementsList) { |
|
|
achievementsList.innerHTML = '<div style="color: #666;">No achievements yet...</div>'; |
|
|
} |
|
|
|
|
|
|
|
|
clearLogs(); |
|
|
renderLevel(); |
|
|
|
|
|
alert('Progress reset! Starting fresh...'); |
|
|
}; |
|
|
|
|
|
|
|
|
function victory() { |
|
|
const achievementsCount = state.achievements.length; |
|
|
const totalAttempts = Object.values(state.levelAttempts).reduce((a, b) => a + b, 0); |
|
|
const avgAttempts = totalAttempts / 5; |
|
|
|
|
|
document.body.innerHTML = ` |
|
|
<div style="height:100vh; display:flex; flex-direction:column; justify-content:center; align-items:center; text-align:center; background: var(--void); padding: 20px;"> |
|
|
<div class="noise-overlay"></div> |
|
|
<div class="scanlines"></div> |
|
|
|
|
|
<h1 style="font-size: 4rem; color: var(--acid); margin-bottom: 20px; font-family: 'Clash Display'; text-transform: uppercase; animation: pulse 2s infinite;"> |
|
|
π TRAINING_COMPLETE π |
|
|
</h1> |
|
|
|
|
|
<p style="font-family: 'Space Mono'; margin-bottom: 40px; color: var(--text-muted); font-size: 1.2rem;"> |
|
|
You've mastered function definition engineering! |
|
|
</p> |
|
|
|
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); gap: 20px; max-width: 800px; width: 100%; margin-bottom: 40px;"> |
|
|
<div style="border: 3px solid var(--success); padding: 20px; background: var(--panel);"> |
|
|
<div style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 10px;">FINAL SCORE</div> |
|
|
<div style="font-size: 2.5rem; font-family: 'Clash Display'; color: var(--success);">${state.totalScore}</div> |
|
|
</div> |
|
|
|
|
|
<div style="border: 3px solid var(--hyper-purple); padding: 20px; background: var(--panel);"> |
|
|
<div style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 10px;">ACHIEVEMENTS</div> |
|
|
<div style="font-size: 2.5rem; font-family: 'Clash Display'; color: var(--hyper-purple);">${achievementsCount}/5</div> |
|
|
</div> |
|
|
|
|
|
<div style="border: 3px solid var(--acid); padding: 20px; background: var(--panel);"> |
|
|
<div style="font-size: 0.9rem; color: var(--text-muted); margin-bottom: 10px;">BEST STREAK</div> |
|
|
<div style="font-size: 2.5rem; font-family: 'Clash Display'; color: var(--acid);">${state.bestStreak}</div> |
|
|
</div> |
|
|
</div> |
|
|
|
|
|
${achievementsCount > 0 ? ` |
|
|
<div style="max-width: 600px; margin-bottom: 40px; padding: 20px; background: var(--panel); border: 2px solid var(--acid);"> |
|
|
<div style="color: var(--acid); font-weight: bold; margin-bottom: 15px; font-size: 1.1rem;">YOUR ACHIEVEMENTS</div> |
|
|
<div style="display: grid; grid-template-columns: repeat(auto-fit, minmax(150px, 1fr)); gap: 10px;"> |
|
|
${state.achievements.map(a => { |
|
|
const names = { |
|
|
'first_perfect': 'π Perfect Score', |
|
|
'streak_3': 'π₯ On Fire!', |
|
|
'streak_5': 'β‘ Unstoppable!', |
|
|
'score_1k': 'π― Score Master', |
|
|
'all_levels': 'π Master Engineer' |
|
|
}; |
|
|
return `<div style="color: var(--acid); padding: 10px; background: rgba(204,255,0,0.1);">${names[a] || a}</div>`; |
|
|
}).join('')} |
|
|
</div> |
|
|
</div> |
|
|
` : ''} |
|
|
|
|
|
<p style="max-width: 600px; line-height: 1.6; color: var(--text-muted); margin-bottom: 40px;"> |
|
|
You now understand how to craft precise function definitions, use enums effectively, |
|
|
and distinguish your functions from competitors. Apply this knowledge to build better AI integrations! |
|
|
</p> |
|
|
|
|
|
<div style="display: flex; gap: 20px; flex-wrap: wrap; justify-content: center;"> |
|
|
<button onclick="location.reload()" style="background: var(--text-main); color: var(--void); padding: 20px 40px; font-family: 'Clash Display'; font-size: 1.2rem; border: none; cursor: pointer; font-weight: 700; transition: all 0.2s;"> |
|
|
TRAIN AGAIN |
|
|
</button> |
|
|
<button onclick="resetProgress(); location.reload();" style="background: transparent; color: var(--alert); padding: 20px 40px; font-family: 'Clash Display'; font-size: 1.2rem; border: 2px solid var(--alert); cursor: pointer; font-weight: 700; transition: all 0.2s;"> |
|
|
RESET & RESTART |
|
|
</button> |
|
|
</div> |
|
|
</div> |
|
|
`; |
|
|
} |
|
|
|
|
|
|
|
|
window.addEventListener('load', () => { |
|
|
loadState(); |
|
|
bootSequence(); |
|
|
}); |
|
|
|
|
|
|
|
|
setInterval(() => { |
|
|
if (state.booted) { |
|
|
saveState(); |
|
|
} |
|
|
}, 30000); |
|
|
</script> |
|
|
</body> |
|
|
</html> |