Skip to content

Instantly share code, notes, and snippets.

@23maverick23
Created March 23, 2026 20:24
Show Gist options
  • Select an option

  • Save 23maverick23/6cc64f27d1f64c6118fbe222790fcbfd to your computer and use it in GitHub Desktop.

Select an option

Save 23maverick23/6cc64f27d1f64c6118fbe222790fcbfd to your computer and use it in GitHub Desktop.
SCLS: Jeopardy Game
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>NetSuite AI Jeopardy!</title>
<link href="https://fonts.googleapis.com/css2?family=Bebas+Neue&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--navy: #0a1628;
--board: #0d2347;
--cell: #1a3a6b;
--cell-h: #1e4d8c;
--gold: #f0b429;
--gold-d: #c8941a;
--teal: #2a9d8f;
--teal-d: #1f7a6e;
--red: #e63946;
--red-d: #b02a31;
--green: #2d9a5f;
--green-d: #1f7044;
--text: #e8edf5;
--muted: #7a9abf;
--done: #0f1e36;
--done-t: #1e3a5a;
}
body {
font-family: 'Inter', sans-serif;
background: linear-gradient(160deg, #060f1e 0%, #0a1628 60%, #071020 100%);
color: var(--text);
min-height: 100vh;
padding: 1.5rem 1rem 3rem;
}
/* ── Setup screen ── */
#setup-screen { max-width: 560px; margin: 0 auto; padding: 2rem 0; }
.setup-title { font-family: 'Bebas Neue', sans-serif; font-size: 52px; letter-spacing: .04em; color: var(--gold); text-align: center; line-height: 1; margin-bottom: .25rem; text-shadow: 0 0 40px rgba(240,180,41,.3); }
.setup-sub { text-align: center; color: var(--muted); font-size: 14px; margin-bottom: 2.5rem; }
.setup-label { font-size: 11px; font-weight: 700; text-transform: uppercase; letter-spacing: .1em; color: var(--muted); margin-bottom: 8px; }
.team-inputs { display: flex; flex-direction: column; gap: 12px; margin-bottom: 2rem; }
.team-row { display: flex; align-items: center; gap: 10px; }
.team-dot { width: 10px; height: 10px; border-radius: 50%; flex-shrink: 0; }
.team-row input {
flex: 1; padding: 11px 14px;
background: rgba(255,255,255,.05);
border: 1px solid rgba(255,255,255,.1);
border-radius: 8px; color: var(--text);
font-size: 15px; font-family: 'Inter', sans-serif;
outline: none; transition: border-color .15s;
}
.team-row input:focus { border-color: var(--gold); }
.setup-btn {
width: 100%; padding: 14px;
background: var(--gold); border: none;
border-radius: 10px; color: var(--navy);
font-size: 16px; font-weight: 700;
cursor: pointer; letter-spacing: .03em;
transition: background .15s, transform .1s;
font-family: 'Inter', sans-serif;
}
.setup-btn:hover { background: var(--gold-d); }
.setup-btn:active { transform: scale(.98); }
/* ── Game wrapper ── */
#game-screen { display: none; max-width: 1240px; margin: 0 auto; }
/* ── Header ── */
.game-title { font-family: 'Bebas Neue', sans-serif; font-size: 48px; letter-spacing: .05em; color: var(--gold); text-align: center; text-shadow: 0 0 40px rgba(240,180,41,.25); margin-bottom: .75rem; }
/* ── Round tabs ── */
.round-tabs { display: flex; justify-content: center; gap: 8px; margin-bottom: 1.5rem; }
.rtab {
padding: 8px 22px; border-radius: 8px;
border: 2px solid rgba(255,255,255,.15);
background: transparent; color: var(--muted);
font-size: 13px; font-weight: 700;
cursor: pointer; transition: all .15s;
font-family: 'Inter', sans-serif;
letter-spacing: .03em;
}
.rtab:hover { border-color: rgba(255,255,255,.3); color: var(--text); }
.rtab.active { background: var(--text); border-color: var(--text); color: var(--navy); }
.rtab.reset { background: var(--red); border-color: var(--red); color: #fff; }
.rtab.reset:hover { background: var(--red-d); border-color: var(--red-d); }
/* ── Scoreboards ── */
.scoreboards { display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px; margin-bottom: 1.5rem; }
.scoreboard {
background: var(--board);
border: 1px solid rgba(255,255,255,.08);
border-radius: 12px; padding: 14px 16px;
text-align: center;
}
.sb-name { font-size: 13px; font-weight: 600; color: var(--muted); margin-bottom: 6px; }
.sb-score { font-family: 'Bebas Neue', sans-serif; font-size: 36px; letter-spacing: .04em; color: var(--gold); line-height: 1; margin-bottom: 10px; }
.sb-btns { display: flex; gap: 6px; justify-content: center; }
.sb-btn {
padding: 5px 12px; border-radius: 6px; border: none;
font-size: 12px; font-weight: 700; cursor: pointer;
font-family: 'Inter', sans-serif; transition: opacity .12s;
}
.sb-btn:hover { opacity: .85; }
.sb-minus { background: var(--red); color: #fff; }
.sb-plus { background: var(--green); color: #fff; }
/* ── Board ── */
.board { display: grid; gap: 6px; }
.board-4 { grid-template-columns: repeat(4, 1fr); }
.board-6 { grid-template-columns: repeat(6, 1fr); }
.cat-header {
background: var(--cell);
border-radius: 8px; padding: 14px 8px;
text-align: center;
font-size: 11px; font-weight: 700;
text-transform: uppercase; letter-spacing: .08em;
color: var(--text); line-height: 1.3;
min-height: 64px; display: flex;
align-items: center; justify-content: center;
}
.cell {
background: var(--cell);
border-radius: 8px; padding: 18px 8px;
text-align: center; cursor: pointer;
font-family: 'Bebas Neue', sans-serif;
font-size: 26px; letter-spacing: .04em;
color: var(--gold); min-height: 72px;
display: flex; align-items: center; justify-content: center;
transition: background .12s, transform .1s;
position: relative; user-select: none;
}
.cell:hover:not(.used) { background: var(--cell-h); transform: scale(1.02); }
.cell.used { background: var(--done); color: var(--done-t); cursor: default; }
.cell.used:hover { transform: none; }
.cell.daily-double::after {
content: '★';
position: absolute; top: 4px; right: 6px;
font-size: 10px; color: var(--gold); opacity: .6;
}
/* ── Modal overlay ── */
.overlay {
position: fixed; inset: 0;
background: rgba(0,0,0,.72);
display: flex; align-items: center; justify-content: center;
z-index: 100; padding: 1rem;
backdrop-filter: blur(3px);
}
.modal {
background: var(--board);
border: 1px solid rgba(255,255,255,.1);
border-radius: 16px; padding: 2rem 2.5rem;
max-width: 680px; width: 100%;
text-align: center;
}
.modal-cat {
display: inline-block;
background: var(--gold); color: var(--navy);
font-size: 11px; font-weight: 800;
text-transform: uppercase; letter-spacing: .12em;
padding: 4px 14px; border-radius: 20px;
margin-bottom: 1rem;
}
.modal-val {
font-family: 'Bebas Neue', sans-serif;
font-size: 52px; letter-spacing: .04em;
color: var(--gold); line-height: 1;
margin-bottom: 1rem;
}
.modal-clue {
font-size: 18px; font-weight: 500;
color: var(--text); line-height: 1.6;
margin-bottom: 1.5rem;
}
.modal-answer {
font-size: 17px; font-weight: 700;
color: var(--teal); margin-bottom: 1.5rem;
padding: 12px 20px;
background: rgba(42,157,143,.12);
border: 1px solid rgba(42,157,143,.3);
border-radius: 10px;
}
.modal-timer {
font-family: 'Bebas Neue', sans-serif;
font-size: 48px; letter-spacing: .06em;
margin-bottom: 1rem;
transition: color .3s;
}
.timer-green { color: #4caf50; }
.timer-amber { color: var(--gold); }
.timer-red { color: var(--red); }
.modal-btns { display: flex; flex-wrap: wrap; gap: 10px; justify-content: center; }
.mbtn {
padding: 11px 24px; border-radius: 10px; border: none;
font-size: 14px; font-weight: 700; cursor: pointer;
font-family: 'Inter', sans-serif; transition: opacity .12s, transform .1s;
letter-spacing: .02em;
}
.mbtn:hover { opacity: .85; }
.mbtn:active { transform: scale(.97); }
.mbtn-show { background: var(--teal); color: #fff; }
.mbtn-close { background: var(--red); color: #fff; }
.mbtn-gold { background: var(--gold); color: var(--navy); }
/* ── DD modal ── */
.dd-banner {
font-family: 'Bebas Neue', sans-serif;
font-size: 36px; letter-spacing: .1em;
color: var(--gold); margin-bottom: .5rem;
}
.dd-sub { font-size: 14px; color: var(--muted); margin-bottom: 1.5rem; }
.dd-team-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 10px; margin-bottom: 1.5rem; }
.dd-team-box { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1); border-radius: 10px; padding: 12px; }
.dd-team-name { font-size: 12px; font-weight: 600; color: var(--muted); margin-bottom: 4px; }
.dd-team-bal { font-size: 13px; color: var(--text); margin-bottom: 8px; }
.dd-team-box input {
width: 100%; padding: 8px 10px;
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.12);
border-radius: 6px; color: var(--text);
font-size: 14px; font-weight: 700; text-align: center;
font-family: 'Inter', sans-serif; outline: none;
}
/* ── Final Jeopardy ── */
.fj-wrap { max-width: 900px; margin: 0 auto; }
.fj-header {
font-family: 'Bebas Neue', sans-serif;
font-size: 42px; letter-spacing: .08em;
color: var(--gold); text-align: center;
margin-bottom: .5rem;
}
.fj-cat { font-size: 18px; color: var(--text); text-align: center; margin-bottom: .5rem; }
.fj-instr { font-size: 13px; color: var(--muted); text-align: center; margin-bottom: 1.75rem; }
.fj-panel {
background: var(--board);
border: 1px solid rgba(255,255,255,.08);
border-radius: 16px; padding: 2rem;
}
.fj-team-grid { display: grid; grid-template-columns: repeat(3,1fr); gap: 12px; margin-bottom: 1.75rem; }
.fj-team-box { background: rgba(255,255,255,.05); border: 1px solid rgba(255,255,255,.1); border-radius: 12px; padding: 16px; text-align: center; }
.fj-team-name { font-size: 13px; font-weight: 700; color: var(--text); margin-bottom: 4px; }
.fj-team-bal { font-size: 12px; color: var(--muted); margin-bottom: 10px; }
.fj-team-box input {
width: 100%; padding: 9px 12px; text-align: center;
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.12);
border-radius: 8px; color: var(--text);
font-size: 16px; font-weight: 700;
font-family: 'Inter', sans-serif; outline: none;
transition: border-color .15s; margin-bottom: 8px;
}
.fj-team-box input:focus { border-color: var(--gold); }
.fj-team-box textarea {
width: 100%; padding: 9px 12px;
background: rgba(255,255,255,.08);
border: 1px solid rgba(255,255,255,.12);
border-radius: 8px; color: var(--text);
font-size: 13px; font-family: 'Inter', sans-serif;
outline: none; resize: none; height: 70px;
transition: border-color .15s;
}
.fj-team-box textarea:focus { border-color: var(--teal); }
.fj-team-box textarea::placeholder { color: rgba(255,255,255,.25); }
.fj-center { text-align: center; }
.fj-timer {
font-family: 'Bebas Neue', sans-serif;
font-size: 56px; letter-spacing: .06em;
margin-bottom: 1rem; transition: color .3s;
}
.fj-clue { font-size: 17px; color: var(--text); line-height: 1.6; margin-bottom: 1.25rem; max-width: 700px; margin-left: auto; margin-right: auto; }
.fj-answer {
font-size: 16px; font-weight: 700; color: var(--teal);
padding: 12px 20px; border-radius: 10px;
background: rgba(42,157,143,.12);
border: 1px solid rgba(42,157,143,.3);
margin-bottom: 1.5rem; display: inline-block;
}
.fj-bid-label { font-size: 11px; color: var(--muted); text-transform: uppercase; letter-spacing: .1em; margin-bottom: 4px; }
</style>
</head>
<body>
<!-- ═══════════ SETUP ═══════════ -->
<div id="setup-screen">
<div class="setup-title">NetSuite AI<br>Jeopardy!</div>
<div class="setup-sub">Enter team names to begin</div>
<div class="setup-label">Team names</div>
<div class="team-inputs">
<div class="team-row">
<div class="team-dot" style="background:#f0b429"></div>
<input id="t1" type="text" placeholder="Team Alpha" value="Team Alpha"/>
</div>
<div class="team-row">
<div class="team-dot" style="background:#2a9d8f"></div>
<input id="t2" type="text" placeholder="Team Beta" value="Team Beta"/>
</div>
<div class="team-row">
<div class="team-dot" style="background:#e63946"></div>
<input id="t3" type="text" placeholder="Team Gamma" value="Team Gamma"/>
</div>
</div>
<button class="setup-btn" onclick="startGame()">Start Game</button>
</div>
<!-- ═══════════ GAME ═══════════ -->
<div id="game-screen">
<div class="game-title">NetSuite AI Jeopardy!</div>
<div class="round-tabs">
<button class="rtab active" id="tab-r1" onclick="showRound(1)">Round 1</button>
<button class="rtab" id="tab-r2" onclick="showRound(2)">Round 2</button>
<button class="rtab" id="tab-fj" onclick="showRound('fj')">Final Jeopardy</button>
<button class="rtab reset" onclick="resetGame()">Reset Game</button>
</div>
<div class="scoreboards" id="scoreboards"></div>
<div id="board-area"></div>
</div>
<!-- ═══════════ QUESTION MODAL ═══════════ -->
<div class="overlay" id="q-overlay" style="display:none">
<div class="modal" id="q-modal">
<div class="modal-cat" id="m-cat"></div>
<div class="modal-val" id="m-val"></div>
<div class="modal-clue" id="m-clue"></div>
<div class="modal-timer" id="m-timer"></div>
<div id="m-answer" style="display:none" class="modal-answer"></div>
<div class="modal-btns" id="m-btns"></div>
</div>
</div>
<!-- ═══════════ DAILY DOUBLE MODAL ═══════════ -->
<div class="overlay" id="dd-overlay" style="display:none">
<div class="modal" id="dd-modal">
<div class="dd-banner">⭐ Daily Double!</div>
<div class="dd-sub">Enter each team's wager before revealing the question</div>
<div class="dd-team-grid" id="dd-wager-grid"></div>
<div class="modal-btns">
<button class="mbtn mbtn-gold" onclick="revealDDQuestion()">Reveal Question</button>
</div>
</div>
</div>
<script>
/* ═══════════════════════════════════════════
DATA
═══════════════════════════════════════════ */
const ROUNDS = {
1: {
categories: [
"AI Fundamentals",
"The AI Stack",
"MCP & Protocols",
"Human-in-the-Loop"
],
questions: [
// AI Fundamentals — $200,$400,$600,$800,$1000
[
{ clue: "The property of an AI system where the same input always produces the same output — like a SuiteFlow approval rule.", answer: "What is deterministic?" },
{ clue: "LLM-based systems are described as this — context-aware and flexible, but outputs may vary for the same input.", answer: "What is non-deterministic (or probabilistic)?" },
{ clue: "The unit of text — roughly one word — that determines the cost and speed of every LLM input and output.", answer: "What is a token?" },
{ clue: "This technique reduces AI hallucination by fetching relevant source data from a knowledge base before the model generates its response.", answer: "What is RAG — Retrieval-Augmented Generation?" },
{ clue: "The model's 'working memory' — the maximum amount of text it can read and reason over in a single response.", answer: "What is a context window?" }
],
// The AI Stack — $200,$400,$600,$800,$1000
[
{ clue: "The bottom layer of the AI stack — ERP, CRM, and HCM systems — described as the source of truth for all AI reasoning.", answer: "What is the Data & Systems layer?" },
{ clue: "The AI stack layer containing SuiteScript, connectors, and MCP services — what the model can call to act on real data.", answer: "What is the Tools & APIs layer?" },
{ clue: "The layer where LLMs, rules engines, and policies live — responsible for generating output.", answer: "What is the Reasoning & Models layer?" },
{ clue: "The top layer of the AI stack — home to task execution, orchestration, and guardrails — and where agents live.", answer: "What is the AI Automation layer?" },
{ clue: "This is why a company with fragmented data across five disconnected systems has a harder AI problem than one on a unified ERP.", answer: "What is: agents are only as reliable as the data layer beneath them (no single source of truth)?" }
],
// MCP & Protocols — $200,$400,$600,$800,$1000
[
{ clue: "MCP stands for this — an open, industry-standard interface that lets AI models connect to external tools consistently.", answer: "What is Model Context Protocol?" },
{ clue: "Why MCP is described as 'the plug, not the thinking.'", answer: "What is: MCP provides the connection interface — reasoning and decision-making happen in the model layer, not in MCP itself?" },
{ clue: "The correct answer when a prospect asks whether adopting NetSuite's MCP-based AI Connector will lock them into Oracle's infrastructure.", answer: "What is no — MCP is an open standard, not proprietary to Oracle or NetSuite?" },
{ clue: "MCP serves as the foundation of this NetSuite capability that enables custom agentic-style workflows today.", answer: "What is the NetSuite AI Connector?" },
{ clue: "This is the honest answer when a customer asks 'Is MCP required to use AI in NetSuite?'", answer: "What is no — MCP is not required; it enables the AI Connector ecosystem but AI features work without it?" }
],
// Human-in-the-Loop — $200,$400,$600,$800,$1000
[
{ clue: "The HITL category where AI acts fully autonomously — appropriate for low-risk, high-volume tasks like categorizing invoices.", answer: "What is Automate?" },
{ clue: "The HITL pattern where AI generates a recommendation or draft, but no action executes until a human explicitly approves it.", answer: "What is Suggest and Confirm?" },
{ clue: "Three examples of actions that must always remain in the 'Always Human' category — AI must never act alone on these.", answer: "What are employee data changes, GL overrides, and bank detail updates (accept any two)?" },
{ clue: "In a SuiteAgents workflow, this is why the agent asks for confirmation before sending a collections email — even though it retrieved the data autonomously.", answer: "What is: sending an email is an action tool that changes state, requiring human approval under HITL?" },
{ clue: "The most important reason a finance leader accepts AI in their ERP workflow — it comes down to this design principle.", answer: "What is deterministic controls around probabilistic reasoning (humans stay in the loop for high-risk decisions)?" }
]
],
dailyDoubles: [[0, 4], [3, 2]]
},
2: {
categories: [
"Agentic Levels",
"Workflow Spectrum",
"NetSuite AI Features",
"Positioning & Objections",
"Structured vs Unstructured",
"SuiteAgents"
],
questions: [
// Agentic Levels — $400,$800,$1200,$1600,$2000
[
{ clue: "The AI maturity level covering Q&A and knowledge retrieval — where SuiteAnswers and Ask Oracle operate.", answer: "What is Answering (Level 1)?" },
{ clue: "The maturity level covering drafting, summarizing, and suggesting — where the GenAI API and Exception Management sit.", answer: "What is Assisting (Level 2)?" },
{ clue: "The maturity level for tool use, writebacks, and transactions — where Ask Oracle and AI Connector operate today.", answer: "What is Acting (Level 3)?" },
{ clue: "The maturity level SuiteAgents targets — orchestrating multi-step workflows across multiple systems.", answer: "What is Coordinating (Level 4)?" },
{ clue: "The highest rung on the AI maturity ladder — where the system learns from outcomes and refines its own behavior — currently described as emerging.", answer: "What is Optimizing (Level 5)?" }
],
// Workflow Spectrum — $400,$800,$1200,$1600,$2000
[
{ clue: "In the transportation analogy, a train on fixed tracks maps to this type of workflow.", answer: "What is a traditional deterministic workflow?" },
{ clue: "A GPS app that reroutes around traffic but always heads to the same destination — the analogy for this workflow type.", answer: "What is an AI-enhanced workflow?" },
{ clue: "A self-driving car where you only set the goal maps to this — the highest level of the workflow spectrum.", answer: "What is a fully agentic workflow?" },
{ clue: "In an AI-enhanced workflow, this tool still owns the overall sequence even when AI steps are inserted.", answer: "What is SuiteFlow?" },
{ clue: "The single word that best describes what an agent does at runtime that a pre-scripted AI workflow cannot — it makes this.", answer: "What is a decision (or: dynamically determines the next step)?" }
],
// NetSuite AI Features — $400,$800,$1200,$1600,$2000
[
{ clue: "This NetSuite feature lets users ask business questions in plain English and get answers from their own ERP data.", answer: "What is Ask Oracle?" },
{ clue: "The NetSuite AI capability that automatically surfaces invoice mismatches, duplicate records, and payment anomalies for finance teams.", answer: "What is AI Exception Management?" },
{ clue: "The NetSuite tool that lets developers embed custom generative AI capabilities directly into scripts and workflows.", answer: "What is the GenAI API?" },
{ clue: "This upcoming NetSuite tool will let customers build and configure their own AI agents — no deep coding required.", answer: "What is Agent Studio?" },
{ clue: "The NetSuite capability coming in NetSuite Next that orchestrates multi-step agentic workflows — the subject of this entire training.", answer: "What is SuiteAgents?" }
],
// Positioning & Objections — $400,$800,$1200,$1600,$2000
[
{ clue: "The first question to ask any prospect who opens with 'We want agentic AI' — before demoing anything.", answer: "What is: 'What specific problem are you trying to solve?'" },
{ clue: "Why a prospect's concern about vendor lock-in is directly answered by confirming MCP's status as this.", answer: "What is an open, industry-standard protocol (not proprietary to Oracle)?" },
{ clue: "The positioning phrase that reassures a CFO or auditor that NetSuite AI doesn't sacrifice control for flexibility.", answer: "What is: deterministic controls around probabilistic reasoning?" },
{ clue: "When a skeptical buyer asks 'How do you stop the AI from making things up in my financial data?', this is the technical answer.", answer: "What is RAG — the AI retrieves structured data from NetSuite before generating, grounding the output in real records?" },
{ clue: "The career-limiting risk of overselling AI autonomy before an implementation — what happens post-go-live.", answer: "What is: the customer loses trust when the system requires more human involvement than was promised?" }
],
// Structured vs Unstructured — $400,$800,$1200,$1600,$2000
[
{ clue: "Why ERP data — rows, schemas, defined fields — is particularly well-suited for AI compared to emails and PDFs.", answer: "What is: structured data is deterministic, grounded, and auditable — outputs can be traced to source records?" },
{ clue: "What AI must do with unstructured inputs like emails or PDFs that it does not need to do with structured ERP data.", answer: "What is interpret (or: infer meaning using probabilistic reasoning)?" },
{ clue: "The type of data where 'the same document can yield different outputs' — a key governance risk to explain to customers.", answer: "What is unstructured data?" },
{ clue: "This is why an AI agent working from a NetSuite invoice record produces a more auditable output than one working from a scanned PDF of the same invoice.", answer: "What is: the ERP record is structured and traceable; the PDF requires inference to interpret?" },
{ clue: "The term for grounding AI responses in real, retrieved data — the technique that makes structured ERP data NetSuite's biggest AI advantage.", answer: "What is RAG — Retrieval-Augmented Generation?" }
],
// SuiteAgents — $400,$800,$1200,$1600,$2000
[
{ clue: "In a SuiteAgents cashflow workflow, these tools retrieve AP balances, AR data, and forecasts — without requiring human confirmation.", answer: "What are read tools?" },
{ clue: "The type of SuiteAgents tool that sends emails, updates records, or triggers transactions — and requires human approval before executing.", answer: "What are action tools?" },
{ clue: "What SuiteAgents does after autonomously gathering data and synthesizing a recommendation — before taking any consequential action.", answer: "What is: requests human confirmation (human-in-the-loop gate)?" },
{ clue: "The honest answer when a customer asks whether SuiteAgents is available to deploy today — accurate and commercially positioned.", answer: "What is: SuiteAgents is coming in NetSuite Next; AI Connector enables custom agentic flows today; HITL controls are part of the design?" },
{ clue: "SuiteAgents sits at Level 4 of the maturity ladder, but this is what separates it from a Level 3 'Acting' capability.", answer: "What is: SuiteAgents coordinates multi-step workflows across systems — not just a single tool call or transaction?" }
]
],
dailyDoubles: [[5, 3], [3, 4]]
}
};
const FINAL_JEOPARDY = {
category: "NetSuite AI Positioning",
clue: "A prospect's CFO says: 'I'm open to AI in our ERP, but I need to know that my team stays in control of financial decisions and that every AI action is auditable.' Give the complete positioning response that addresses both concerns simultaneously — in one sentence.",
answer: "What is: NetSuite uses deterministic controls around probabilistic reasoning — AI provides the contextual intelligence while rules-based guardrails ensure every action is auditable and high-risk decisions always require human approval?"
};
const ROUND_VALUES = { 1: [200,400,600,800,1000], 2: [400,800,1200,1600,2000] };
const TIMER_SECS = 15;
const FJ_TIMER_SECS = 30;
/* ═══════════════════════════════════════════
STATE
═══════════════════════════════════════════ */
let teams = [], scores = [0,0,0], usedCells = {}, currentRound = 1;
let timerInterval = null, timerVal = 0;
let pendingCell = null, ddWagers = [0,0,0];
let fjPhase = 'wager'; // wager | question | result
/* ═══════════════════════════════════════════
SETUP
═══════════════════════════════════════════ */
function startGame() {
teams = [
document.getElementById('t1').value.trim() || 'Team Alpha',
document.getElementById('t2').value.trim() || 'Team Beta',
document.getElementById('t3').value.trim() || 'Team Gamma'
];
document.getElementById('setup-screen').style.display = 'none';
document.getElementById('game-screen').style.display = 'block';
renderScoreboards();
showRound(1);
}
function resetGame() {
if (!confirm('Reset everything and start a new game?')) return;
scores = [0,0,0];
usedCells = {};
fjPhase = 'wager';
document.getElementById('game-screen').style.display = 'none';
document.getElementById('setup-screen').style.display = 'block';
}
/* ═══════════════════════════════════════════
SCOREBOARDS
═══════════════════════════════════════════ */
function renderScoreboards() {
const el = document.getElementById('scoreboards');
const colors = ['#f0b429','#2a9d8f','#e63946'];
el.innerHTML = teams.map((t,i) => `
<div class="scoreboard">
<div class="sb-name" style="color:${colors[i]}">${t}</div>
<div class="sb-score" id="score-${i}">$${scores[i].toLocaleString()}</div>
<div class="sb-btns">
<button class="sb-btn sb-minus" onclick="adjustScore(${i},-100)">−100</button>
<button class="sb-btn sb-plus" onclick="adjustScore(${i},+100)">+100</button>
</div>
</div>`).join('');
}
function adjustScore(i, delta) {
scores[i] = Math.max(0, scores[i] + delta);
document.getElementById('score-'+i).textContent = '$' + scores[i].toLocaleString();
}
function awardScore(i, amount) {
scores[i] = Math.max(0, scores[i] + amount);
document.getElementById('score-'+i).textContent = '$' + scores[i].toLocaleString();
}
/* ═══════════════════════════════════════════
ROUNDS
═══════════════════════════════════════════ */
function showRound(r) {
currentRound = r;
['r1','r2','fj'].forEach(id => {
document.getElementById('tab-'+id).classList.toggle('active', id === (r === 'fj' ? 'fj' : 'r'+r));
});
if (r === 'fj') { renderFinalJeopardy(); return; }
renderBoard(r);
}
function renderBoard(r) {
const data = ROUNDS[r];
const vals = ROUND_VALUES[r];
const cols = data.categories.length;
const gridClass = cols === 4 ? 'board-4' : 'board-6';
// Build daily double set
const ddSet = new Set(data.dailyDoubles.map(([c,q]) => `${c}-${q}`));
let html = `<div class="board ${gridClass}">`;
// Category headers
data.categories.forEach(cat => {
html += `<div class="cat-header">${cat}</div>`;
});
// Question rows
vals.forEach((val, qi) => {
data.categories.forEach((cat, ci) => {
const key = `${r}-${ci}-${qi}`;
const used = !!usedCells[key];
const isDD = ddSet.has(`${ci}-${qi}`);
html += `<div class="cell${used?' used':''}${isDD?' daily-double':''}"
onclick="${used ? '' : `openQuestion(${r},${ci},${qi})`}"
data-key="${key}">
${used ? '' : '$'+val.toLocaleString()}
</div>`;
});
});
html += '</div>';
document.getElementById('board-area').innerHTML = html;
}
/* ═══════════════════════════════════════════
QUESTION MODAL
═══════════════════════════════════════════ */
function openQuestion(r, ci, qi) {
const key = `${r}-${ci}-${qi}`;
if (usedCells[key]) return;
const data = ROUNDS[r];
const ddSet = new Set(data.dailyDoubles.map(([c,q]) => `${c}-${q}`));
const isDD = ddSet.has(`${ci}-${qi}`);
const val = ROUND_VALUES[r][qi];
const q = data.questions[ci][qi];
pendingCell = { r, ci, qi, key, val, q };
if (isDD) {
showDailyDouble();
return;
}
showQuestionModal(q.clue, q.answer, data.categories[ci], val, []);
}
function showQuestionModal(clue, answer, cat, val, extraBtns) {
document.getElementById('m-cat').textContent = cat.toUpperCase();
document.getElementById('m-val').textContent = '$' + val.toLocaleString();
document.getElementById('m-clue').textContent = clue;
document.getElementById('m-answer').style.display = 'none';
document.getElementById('m-answer').textContent = answer;
const btns = document.getElementById('m-btns');
btns.innerHTML = `<button class="mbtn mbtn-show" onclick="revealAnswer()">Show Answer</button>`;
extraBtns.forEach(b => btns.innerHTML += b);
startTimer('m-timer', TIMER_SECS);
document.getElementById('q-overlay').style.display = 'flex';
}
function revealAnswer() {
stopTimer();
document.getElementById('m-answer').style.display = 'block';
document.getElementById('m-timer').style.display = 'none';
renderScoringBtns();
}
function renderScoringBtns() {
const { key, val } = pendingCell;
const btns = document.getElementById('m-btns');
let html = '';
teams.forEach((t, i) => {
html += `<button class="mbtn" style="background:var(--green);color:#fff" onclick="scoreTeam(${i},${val})">✓ ${t}</button>`;
});
html += `<button class="mbtn mbtn-close" onclick="closeQuestion()">Close</button>`;
btns.innerHTML = html;
}
function scoreTeam(i, amount) {
awardScore(i, amount);
closeQuestion();
}
function closeQuestion() {
stopTimer();
if (pendingCell) {
usedCells[pendingCell.key] = true;
// update cell on board
const el = document.querySelector(`[data-key="${pendingCell.key}"]`);
if (el) { el.classList.add('used'); el.textContent = ''; el.onclick = null; }
pendingCell = null;
}
document.getElementById('q-overlay').style.display = 'none';
document.getElementById('m-timer').style.display = 'block';
}
/* ═══════════════════════════════════════════
DAILY DOUBLE
═══════════════════════════════════════════ */
function showDailyDouble() {
const grid = document.getElementById('dd-wager-grid');
grid.innerHTML = teams.map((t, i) => `
<div class="dd-team-box">
<div class="dd-team-name">${t}</div>
<div class="dd-team-bal">Balance: $${scores[i].toLocaleString()}</div>
<input type="number" id="dd-wager-${i}" placeholder="0" min="0" max="${Math.max(scores[i],pendingCell.val)}" value="0"/>
</div>`).join('');
document.getElementById('dd-overlay').style.display = 'flex';
}
function revealDDQuestion() {
ddWagers = teams.map((_,i) => parseInt(document.getElementById('dd-wager-'+i).value)||0);
document.getElementById('dd-overlay').style.display = 'none';
const { q, r, ci, val } = pendingCell;
showQuestionModal(q.clue, q.answer, ROUNDS[r].categories[ci], val, []);
// Override scoring buttons to use wagers
const origReveal = window.revealAnswer;
window.revealAnswer = function() {
stopTimer();
document.getElementById('m-answer').style.display = 'block';
document.getElementById('m-timer').style.display = 'none';
const btns = document.getElementById('m-btns');
let html = '';
teams.forEach((t, i) => {
if (ddWagers[i] > 0) {
html += `<button class="mbtn" style="background:var(--green);color:#fff" onclick="scoreDDTeam(${i},true)">✓ ${t} +$${ddWagers[i].toLocaleString()}</button>`;
html += `<button class="mbtn" style="background:var(--red);color:#fff" onclick="scoreDDTeam(${i},false)">✗ ${t} −$${ddWagers[i].toLocaleString()}</button>`;
}
});
html += `<button class="mbtn mbtn-close" onclick="closeQuestion()">Close</button>`;
btns.innerHTML = html;
window.revealAnswer = origReveal;
};
}
function scoreDDTeam(i, correct) {
awardScore(i, correct ? ddWagers[i] : -ddWagers[i]);
}
/* ═══════════════════════════════════════════
FINAL JEOPARDY
═══════════════════════════════════════════ */
function renderFinalJeopardy() {
let html = '<div class="fj-wrap">';
html += `<div class="fj-header">Final Jeopardy</div>`;
html += `<div class="fj-cat">Category: ${FINAL_JEOPARDY.category}</div>`;
if (fjPhase === 'wager') {
html += `<div class="fj-instr">Teams must place their final wagers before seeing the question.</div>`;
html += `<div class="fj-panel">`;
html += `<div class="fj-team-grid">` + teams.map((t,i) => `
<div class="fj-team-box">
<div class="fj-team-name">${t}</div>
<div class="fj-team-bal">Current: $${scores[i].toLocaleString()}</div>
<div class="fj-bid-label">Wager</div>
<input type="number" id="fj-wager-${i}" min="0" max="${scores[i]}" value="0" placeholder="0"/>
</div>`).join('') + `</div>`;
html += `<div class="fj-center"><button class="mbtn mbtn-gold" style="font-size:16px;padding:14px 40px" onclick="revealFJQuestion()">Reveal Final Question</button></div>`;
html += `</div>`;
}
else if (fjPhase === 'question') {
html += `<div class="fj-panel">`;
html += `<div class="fj-center">`;
html += `<div class="fj-timer ${getFJTimerClass()}" id="fj-timer">${timerVal}s</div>`;
html += `<div class="fj-clue">${FINAL_JEOPARDY.clue}</div>`;
html += `</div>`;
html += `<div class="fj-team-grid">` + teams.map((t,i) => {
const w = parseInt(document.getElementById('fj-wager-'+i)?.value)||fjWagers[i]||0;
return `<div class="fj-team-box">
<div class="fj-team-name">${t}</div>
<div class="fj-team-bal">Bid: $${w.toLocaleString()}</div>
<textarea id="fj-ans-${i}" placeholder="Team's answer..."></textarea>
</div>`;
}).join('') + `</div>`;
html += `<div class="fj-center"><button class="mbtn mbtn-close" style="font-size:15px;padding:12px 32px" onclick="revealFJAnswer()">Time's Up — Show Correct Answer</button></div>`;
html += `</div>`;
}
else if (fjPhase === 'result') {
html += `<div class="fj-panel">`;
html += `<div class="fj-center"><div class="fj-clue">${FINAL_JEOPARDY.clue}</div>`;
html += `<div class="fj-answer">${FINAL_JEOPARDY.answer}</div></div>`;
html += `<div class="fj-team-grid">` + teams.map((t,i) => `
<div class="fj-team-box">
<div class="fj-team-name">${t}</div>
<div class="fj-team-bal">Bid: $${fjWagers[i].toLocaleString()}</div>
<div style="font-size:12px;color:var(--muted);margin-bottom:8px;font-style:italic">${fjAnswers[i]||'—'}</div>
<button class="mbtn" style="background:var(--green);color:#fff;width:100%;margin-bottom:6px" onclick="scoreFJ(${i},true)">✓ Correct +$${fjWagers[i].toLocaleString()}</button>
<button class="mbtn" style="background:var(--red);color:#fff;width:100%" onclick="scoreFJ(${i},false)">✗ Wrong −$${fjWagers[i].toLocaleString()}</button>
</div>`).join('') + `</div>`;
html += `</div>`;
}
html += '</div>';
document.getElementById('board-area').innerHTML = html;
if (fjPhase === 'question') {
startFJTimer();
}
}
let fjWagers = [0,0,0], fjAnswers = ['','',''];
function getFJTimerClass() {
if (timerVal > 20) return 'timer-green';
if (timerVal > 10) return 'timer-amber';
return 'timer-red';
}
function revealFJQuestion() {
fjWagers = teams.map((_,i) => parseInt(document.getElementById('fj-wager-'+i)?.value)||0);
fjPhase = 'question';
renderFinalJeopardy();
}
function startFJTimer() {
timerVal = FJ_TIMER_SECS;
clearInterval(timerInterval);
timerInterval = setInterval(() => {
timerVal--;
const el = document.getElementById('fj-timer');
if (el) {
el.textContent = timerVal + 's';
el.className = 'fj-timer ' + getFJTimerClass();
}
if (timerVal <= 0) stopTimer();
}, 1000);
}
function revealFJAnswer() {
stopTimer();
fjAnswers = teams.map((_,i) => document.getElementById('fj-ans-'+i)?.value||'');
fjPhase = 'result';
renderFinalJeopardy();
}
function scoreFJ(i, correct) {
awardScore(i, correct ? fjWagers[i] : -fjWagers[i]);
}
/* ═══════════════════════════════════════════
TIMER
═══════════════════════════════════════════ */
function startTimer(elId, secs) {
timerVal = secs;
const el = document.getElementById(elId);
el.textContent = secs + 's';
el.className = 'modal-timer timer-green';
clearInterval(timerInterval);
timerInterval = setInterval(() => {
timerVal--;
el.textContent = timerVal + 's';
if (timerVal > 10) el.className = 'modal-timer timer-green';
else if (timerVal > 5) el.className = 'modal-timer timer-amber';
else el.className = 'modal-timer timer-red';
if (timerVal <= 0) stopTimer();
}, 1000);
}
function stopTimer() { clearInterval(timerInterval); timerInterval = null; }
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment