Files
exercises-dataset/setup.html
T
2026-07-02 12:29:39 -04:00

1698 lines
58 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Developer Setup · ExerciseDB</title>
<style>
/* ── Design Tokens ──────────────────────────────── */
:root {
--accent: #ff4f00;
--accent-muted: rgba(255,79,0,0.09);
--bg-base: #f4f4f5;
--bg-surface: #ffffff;
--bg-elevated: #f0f0f1;
--border: #e4e4e7;
--border-hover: #c4c4c7;
--text-primary: #111111;
--text-secondary: #71717a;
--text-tertiary: #a1a1aa;
--radius-sm: 6px;
--radius-md: 10px;
--radius-lg: 14px;
--radius-xl: 18px;
--nav-w: 210px;
}
/* ── Reset ──────────────────────────────────────── */
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
html { scroll-behavior: smooth; }
body {
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif;
background: var(--bg-base);
color: var(--text-primary);
min-height: 100vh;
font-size: 14px;
line-height: 1.6;
}
a { color: inherit; text-decoration: none; }
code {
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
font-size: 12px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-sm);
padding: 1px 5px;
}
/* ── Header ─────────────────────────────────────── */
.site-header {
background: var(--bg-surface);
border-bottom: 1px solid var(--border);
height: 52px;
display: flex;
align-items: center;
padding: 0 24px;
gap: 20px;
position: sticky;
top: 0;
z-index: 100;
}
.site-logo {
font-size: 17px;
font-weight: 700;
letter-spacing: -0.4px;
color: var(--text-primary);
}
.site-logo span { color: var(--accent); }
.back-link {
display: flex;
align-items: center;
gap: 5px;
font-size: 13px;
color: var(--text-secondary);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 4px 11px;
margin-left: auto;
transition: color .15s, border-color .15s;
}
.back-link:hover { color: var(--text-primary); border-color: var(--border-hover); }
.back-link svg { flex-shrink: 0; }
.header-divider {
width: 1px; height: 20px;
background: var(--border);
}
.header-label {
font-size: 13px;
color: var(--text-secondary);
}
/* ── Shell ───────────────────────────────────────── */
.page-shell {
display: grid;
grid-template-columns: var(--nav-w) 1fr;
max-width: 1160px;
margin: 0 auto;
padding: 0 16px;
gap: 0;
min-height: calc(100vh - 52px);
}
/* ── Left Nav ────────────────────────────────────── */
.page-nav {
position: sticky;
top: 52px;
height: calc(100vh - 52px);
overflow-y: auto;
padding: 32px 0 32px 0;
border-right: 1px solid var(--border);
}
.nav-group-label {
font-size: 11px;
font-weight: 600;
letter-spacing: .06em;
text-transform: uppercase;
color: var(--text-tertiary);
padding: 0 16px 8px 16px;
}
.nav-link {
display: flex;
align-items: center;
gap: 8px;
font-size: 13px;
padding: 7px 16px;
border-radius: 0 var(--radius-md) var(--radius-md) 0;
color: var(--text-secondary);
transition: color .15s, background .15s;
cursor: pointer;
border-left: 2px solid transparent;
margin-right: 12px;
}
.nav-link:hover { color: var(--text-primary); background: var(--bg-elevated); }
.nav-link.active {
color: var(--accent);
background: var(--accent-muted);
border-left-color: var(--accent);
font-weight: 500;
}
.nav-link svg { opacity: .5; flex-shrink: 0; }
.nav-link.active svg { opacity: 1; }
.nav-spacer { height: 20px; }
/* ── Main Content ────────────────────────────────── */
.page-content {
padding: 48px 0 80px 48px;
max-width: 820px;
}
/* ── Section ─────────────────────────────────────── */
.section {
margin-bottom: 72px;
}
.section-header {
display: flex;
align-items: center;
gap: 14px;
margin-bottom: 6px;
}
.section-num {
width: 30px; height: 30px;
background: var(--accent);
color: #fff;
font-size: 14px;
font-weight: 700;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.section-title {
font-size: 22px;
font-weight: 700;
letter-spacing: -0.4px;
}
.section-desc {
font-size: 14px;
color: var(--text-secondary);
margin-bottom: 24px;
padding-left: 44px;
max-width: 620px;
line-height: 1.65;
}
.section-body { padding-left: 44px; }
/* ── DB Tabs ─────────────────────────────────────── */
.tab-bar {
display: flex;
gap: 2px;
border-bottom: 1px solid var(--border);
margin-bottom: 24px;
}
.tab-btn {
font-size: 13px;
font-weight: 500;
padding: 8px 16px;
background: none;
border: none;
border-bottom: 2px solid transparent;
margin-bottom: -1px;
color: var(--text-secondary);
cursor: pointer;
transition: color .15s, border-color .15s;
white-space: nowrap;
}
.tab-btn:hover { color: var(--text-primary); }
.tab-btn.active { color: var(--accent); border-bottom-color: var(--accent); }
/* ── Steps ───────────────────────────────────────── */
.step-list { display: flex; flex-direction: column; gap: 28px; }
.step {
display: flex;
gap: 16px;
}
.step-num {
width: 24px; height: 24px;
background: var(--accent);
color: #fff;
font-size: 12px;
font-weight: 700;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
flex-shrink: 0;
margin-top: 1px;
}
.step-body { flex: 1; min-width: 0; }
.step-title { font-size: 14px; font-weight: 600; margin-bottom: 4px; }
.step-desc { font-size: 13px; color: var(--text-secondary); margin-bottom: 10px; line-height: 1.6; }
/* ── Code Block ──────────────────────────────────── */
.code-block {
position: relative;
background: #1a1a1a;
border-radius: var(--radius-lg);
overflow: hidden;
}
.code-pre {
font-family: 'SF Mono', 'Fira Code', 'Cascadia Code', Consolas, monospace;
font-size: 12px;
line-height: 1.75;
color: #e4e4e7;
padding: 16px 20px;
overflow-x: auto;
white-space: pre;
tab-size: 2;
}
.copy-btn {
position: absolute;
top: 10px;
right: 10px;
font-size: 11px;
font-weight: 500;
padding: 4px 10px;
background: rgba(255,255,255,0.10);
color: #a1a1aa;
border: 1px solid rgba(255,255,255,0.12);
border-radius: var(--radius-sm);
cursor: pointer;
transition: background .15s, color .15s;
}
.copy-btn:hover { background: rgba(255,255,255,0.18); color: #fff; }
.copy-btn.copied { color: #4ade80; border-color: rgba(74,222,128,0.3); }
/* ── Generate Button ─────────────────────────────── */
.generate-btn {
display: inline-flex;
align-items: center;
gap: 7px;
background: var(--accent);
color: #fff;
font-size: 13px;
font-weight: 600;
padding: 9px 18px;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: opacity .15s;
}
.generate-btn:hover { opacity: .88; }
.generate-btn:disabled { opacity: .5; cursor: not-allowed; }
.generate-status {
font-size: 12px;
color: var(--text-secondary);
margin-left: 12px;
}
/* ── URL Input ───────────────────────────────────── */
.url-input-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 20px;
background: var(--bg-surface);
border: 1px solid var(--border);
border-radius: var(--radius-md);
padding: 8px 14px;
}
.url-label {
font-size: 12px;
font-weight: 600;
color: var(--text-tertiary);
white-space: nowrap;
}
.url-input {
flex: 1;
border: none;
outline: none;
font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
font-size: 13px;
color: var(--text-primary);
background: transparent;
}
.url-input::placeholder { color: var(--text-tertiary); }
/* ── API Endpoint Cards ──────────────────────────── */
.endpoint-list { display: flex; flex-direction: column; gap: 24px; }
.endpoint-card {
border: 1px solid var(--border);
border-radius: var(--radius-lg);
overflow: hidden;
background: var(--bg-surface);
}
.endpoint-header {
display: flex;
align-items: center;
gap: 10px;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
background: var(--bg-elevated);
}
.method-badge {
font-size: 11px;
font-weight: 700;
padding: 2px 7px;
border-radius: var(--radius-sm);
background: #dcfce7;
color: #166534;
letter-spacing: .04em;
}
.endpoint-path {
font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
font-size: 13px;
font-weight: 500;
color: var(--text-primary);
}
.endpoint-desc {
font-size: 12px;
color: var(--text-secondary);
margin-left: auto;
}
.endpoint-body { position: relative; }
.endpoint-body .code-block { border-radius: 0; }
/* ── LLM Section ─────────────────────────────────── */
.llm-selectors {
display: flex;
gap: 12px;
margin-bottom: 20px;
flex-wrap: wrap;
}
.selector-group { display: flex; flex-direction: column; gap: 5px; }
.selector-label { font-size: 11px; font-weight: 600; color: var(--text-tertiary); text-transform: uppercase; letter-spacing: .06em; }
.selector-tabs { display: flex; gap: 2px; }
.selector-btn {
font-size: 12px;
font-weight: 500;
padding: 5px 12px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-md);
color: var(--text-secondary);
cursor: pointer;
transition: all .15s;
}
.selector-btn:hover { border-color: var(--border-hover); color: var(--text-primary); }
.selector-btn.active { background: var(--accent-muted); border-color: var(--accent); color: var(--accent); font-weight: 600; }
.llm-prompt-wrap {
position: relative;
}
.llm-prompt-box {
width: 100%;
min-height: 380px;
font-family: 'SF Mono', 'Fira Code', Consolas, monospace;
font-size: 12px;
line-height: 1.75;
background: #1a1a1a;
color: #e4e4e7;
border: none;
border-radius: var(--radius-lg);
padding: 20px;
resize: vertical;
outline: none;
tab-size: 2;
}
.llm-actions {
display: flex;
align-items: center;
gap: 12px;
margin-top: 12px;
}
.copy-prompt-btn {
display: inline-flex;
align-items: center;
gap: 7px;
background: var(--accent);
color: #fff;
font-size: 13px;
font-weight: 600;
padding: 9px 18px;
border: none;
border-radius: var(--radius-md);
cursor: pointer;
transition: opacity .15s;
}
.copy-prompt-btn:hover { opacity: .88; }
.copy-prompt-btn.copied { background: #16a34a; }
.llm-hint {
font-size: 12px;
color: var(--text-tertiary);
}
/* ── Media Note ──────────────────────────────────── */
.media-note {
display: flex;
align-items: flex-start;
gap: 8px;
margin-top: 12px;
padding: 10px 14px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: var(--radius-md);
font-size: 13px;
color: var(--text-secondary);
line-height: 1.55;
}
.media-note code { font-size: 11px; }
/* ── Misc helpers ────────────────────────────────── */
.inline-badge {
font-size: 11px;
font-weight: 600;
padding: 2px 7px;
border-radius: var(--radius-sm);
background: var(--accent-muted);
color: var(--accent);
}
.divider { height: 1px; background: var(--border); margin: 48px 0; }
/* ── Scrollbar ───────────────────────────────────── */
::-webkit-scrollbar { width: 6px; height: 6px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: var(--border-hover); border-radius: 3px; }
/* ── Responsive ──────────────────────────────────── */
@media (max-width: 768px) {
.page-shell { grid-template-columns: 1fr; }
.page-nav { display: none; }
.page-content { padding: 32px 0 60px 0; }
.section-body { padding-left: 0; }
.section-desc { padding-left: 0; }
}
</style>
</head>
<body>
<!-- Header -->
<header class="site-header">
<div class="site-logo">Exercise<span>DB</span></div>
<div class="header-divider"></div>
<span class="header-label">Developer Setup</span>
<a class="back-link" href="index.html">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<path d="M10 4L6 8l4 4"/>
</svg>
Back to Browse
</a>
</header>
<!-- Shell -->
<div class="page-shell">
<!-- Left Nav -->
<nav class="page-nav" id="page-nav">
<div class="nav-group-label">Contents</div>
<a class="nav-link active" href="#db-setup" data-section="db-setup">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<ellipse cx="8" cy="4" rx="6" ry="2.5"/>
<path d="M2 4v3c0 1.38 2.69 2.5 6 2.5s6-1.12 6-2.5V4"/>
<path d="M2 7v3c0 1.38 2.69 2.5 6 2.5s6-1.12 6-2.5V7"/>
<path d="M2 10v2c0 1.38 2.69 2.5 6 2.5s6-1.12 6-2.5v-2"/>
</svg>
Database Setup
</a>
<div class="nav-spacer"></div>
<a class="nav-link" href="#api-integration" data-section="api-integration">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<polyline points="5 4 1 8 5 12"/>
<polyline points="11 4 15 8 11 12"/>
<line x1="8" y1="2" x2="8" y2="14"/>
</svg>
API Integration
</a>
<div class="nav-spacer"></div>
<a class="nav-link" href="#ask-llm" data-section="ask-llm">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round">
<path d="M14 2H2a1 1 0 0 0-1 1v9a1 1 0 0 0 1 1h3l3 2 3-2h3a1 1 0 0 0 1-1V3a1 1 0 0 0-1-1z"/>
<path d="M8 6v2M8 10h.01"/>
</svg>
Ask Your LLM
</a>
</nav>
<!-- Main Content -->
<main class="page-content">
<!-- ── SECTION 1: Database Setup ──────────────────── -->
<section class="section" id="db-setup">
<div class="section-header">
<div class="section-num">1</div>
<h2 class="section-title">Database Setup</h2>
</div>
<p class="section-desc">Import all 1,324 exercises into your database in 3 steps. Choose your database engine below.</p>
<div class="section-body">
<!-- DB Tabs -->
<div class="tab-bar" id="db-tabs" role="tablist">
<button class="tab-btn active" data-db="mssql" role="tab">SQL Server</button>
<button class="tab-btn" data-db="postgresql" role="tab">PostgreSQL</button>
<button class="tab-btn" data-db="mysql" role="tab">MySQL</button>
<button class="tab-btn" data-db="sqlite" role="tab">SQLite</button>
</div>
<div class="step-list">
<!-- Step 1 -->
<div class="step">
<div class="step-num">1</div>
<div class="step-body">
<h3 class="step-title">Create Table</h3>
<p class="step-desc">Run this in your database client (SSMS, DBeaver, pgAdmin, TablePlus, etc.):</p>
<div class="code-block">
<button class="copy-btn" id="copy-create-btn">Copy</button>
<pre class="code-pre" id="create-table-sql"></pre>
</div>
</div>
</div>
<!-- Step 2 -->
<div class="step">
<div class="step-num">2</div>
<div class="step-body">
<h3 class="step-title">Import Data</h3>
<p class="step-desc">Generate a <code>.sql</code> file with all 1,324 INSERT statements. The file is built entirely in your browser — nothing is uploaded to any server.</p>
<div style="display:flex;align-items:center;gap:0;">
<button class="generate-btn" id="generate-sql-btn">
<svg viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round" width="14" height="14">
<path d="M8 2v8M5 7l3 3 3-3"/><path d="M2 12h12"/>
</svg>
Generate INSERT SQL
</button>
<span class="generate-status" id="generate-status"></span>
</div>
</div>
</div>
<!-- Step 3 -->
<div class="step">
<div class="step-num">3</div>
<div class="step-body">
<h3 class="step-title">Media Files</h3>
<p class="step-desc">Download the media archives and place them in your web server root or CDN. The <code>image</code> and <code>gif_url</code> columns store relative paths — prepend your base URL in your app.</p>
<div class="code-block">
<pre class="code-pre">your-server.com/
├── images/ ← 1,324 JPG thumbnails
└── videos/ ← 1,324 GIF animations</pre>
</div>
<div class="media-note">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round" style="flex-shrink:0;margin-top:1px">
<circle cx="8" cy="8" r="6"/><path d="M8 7v4M8 5.5h.01"/>
</svg>
The <code>images/</code> and <code>videos/</code> folders are already included in the repository. After cloning, just copy them to your server or CDN — no separate download needed.
</div>
</div>
</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- ── SECTION 2: API Integration ─────────────────── -->
<section class="section" id="api-integration">
<div class="section-header">
<div class="section-num">2</div>
<h2 class="section-title">API Integration</h2>
</div>
<p class="section-desc">Client-side code examples showing how to call your backend API from your app. Enter your API base URL below — all examples update live.</p>
<div class="section-body">
<!-- Base URL Input -->
<div class="url-input-row">
<span class="url-label">BASE URL</span>
<input
type="url"
class="url-input"
id="base-url-input"
value="https://api.yourapp.com"
placeholder="https://api.yourapp.com"
spellcheck="false"
/>
</div>
<!-- Language Tabs -->
<div class="tab-bar" id="lang-tabs" role="tablist">
<button class="tab-btn active" data-lang="curl" role="tab">cURL</button>
<button class="tab-btn" data-lang="js" role="tab">JavaScript</button>
<button class="tab-btn" data-lang="python" role="tab">Python</button>
<button class="tab-btn" data-lang="csharp" role="tab">C#</button>
<button class="tab-btn" data-lang="java" role="tab">Java</button>
<button class="tab-btn" data-lang="php" role="tab">PHP</button>
<button class="tab-btn" data-lang="go" role="tab">Go</button>
</div>
<!-- Endpoints -->
<div class="endpoint-list">
<!-- GET /exercises/:id -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge">GET</span>
<span class="endpoint-path">/exercises/<span style="color:var(--accent)">:id</span></span>
<span class="endpoint-desc">Fetch a single exercise by ID</span>
</div>
<div class="endpoint-body">
<div class="code-block">
<button class="copy-btn" onclick="copyBlock(this)">Copy</button>
<pre class="code-pre api-code" data-endpoint="getOne" id="code-getOne"></pre>
</div>
</div>
</div>
<!-- GET /exercises?page= -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge">GET</span>
<span class="endpoint-path">/exercises<span style="color:var(--text-tertiary)">?page=1&amp;limit=20</span></span>
<span class="endpoint-desc">Paginated list of exercises</span>
</div>
<div class="endpoint-body">
<div class="code-block">
<button class="copy-btn" onclick="copyBlock(this)">Copy</button>
<pre class="code-pre api-code" data-endpoint="getAll" id="code-getAll"></pre>
</div>
</div>
</div>
<!-- GET /exercises?category=&body_part= -->
<div class="endpoint-card">
<div class="endpoint-header">
<span class="method-badge">GET</span>
<span class="endpoint-path">/exercises<span style="color:var(--text-tertiary)">?category=Strength&amp;body_part=Chest</span></span>
<span class="endpoint-desc">Filter by category, body part, equipment</span>
</div>
<div class="endpoint-body">
<div class="code-block">
<button class="copy-btn" onclick="copyBlock(this)">Copy</button>
<pre class="code-pre api-code" data-endpoint="getFiltered" id="code-getFiltered"></pre>
</div>
</div>
</div>
</div>
</div>
</section>
<div class="divider"></div>
<!-- ── SECTION 3: Ask Your LLM ─────────────────────── -->
<section class="section" id="ask-llm">
<div class="section-header">
<div class="section-num">3</div>
<h2 class="section-title">Ask Your LLM</h2>
</div>
<p class="section-desc">
Don't want to write a backend from scratch? Copy this prompt into ChatGPT, Claude, Gemini, or any LLM — it contains everything needed to generate a complete, production-ready API in one shot.
</p>
<div class="section-body">
<div class="llm-selectors">
<div class="selector-group">
<div class="selector-label">Framework</div>
<div class="selector-tabs" id="framework-tabs">
<button class="selector-btn active" data-fw="express">Express.js</button>
<button class="selector-btn" data-fw="fastapi">FastAPI</button>
<button class="selector-btn" data-fw="aspnet">ASP.NET Core</button>
<button class="selector-btn" data-fw="spring">Spring Boot</button>
<button class="selector-btn" data-fw="laravel">Laravel</button>
<button class="selector-btn" data-fw="gin">Gin (Go)</button>
</div>
</div>
<div class="selector-group">
<div class="selector-label">Database</div>
<div class="selector-tabs" id="llm-db-tabs">
<button class="selector-btn active" data-llmdb="postgresql">PostgreSQL</button>
<button class="selector-btn" data-llmdb="mysql">MySQL</button>
<button class="selector-btn" data-llmdb="mssql">SQL Server</button>
<button class="selector-btn" data-llmdb="sqlite">SQLite</button>
</div>
</div>
</div>
<div class="llm-prompt-wrap">
<textarea class="llm-prompt-box" id="llm-prompt" readonly spellcheck="false"></textarea>
</div>
<div class="llm-actions">
<button class="copy-prompt-btn" id="copy-prompt-btn">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round">
<rect x="4" y="4" width="9" height="11" rx="1.5"/>
<path d="M11 4V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h1"/>
</svg>
Copy Prompt
</button>
<span class="llm-hint">Paste into ChatGPT, Claude, Gemini, or any LLM</span>
</div>
</div>
</section>
</main>
</div>
<script>
// ══════════════════════════════════════════════════
// ── DB SQL Templates ───────────────────────────────
// ══════════════════════════════════════════════════
const DB_SQL = {
mssql: `CREATE TABLE exercises (
id NVARCHAR(10) PRIMARY KEY,
name NVARCHAR(255) NOT NULL,
category NVARCHAR(100),
body_part NVARCHAR(100),
equipment NVARCHAR(100),
instructions_en NVARCHAR(MAX),
instructions_es NVARCHAR(MAX),
instructions_it NVARCHAR(MAX),
instructions_tr NVARCHAR(MAX),
instructions_ru NVARCHAR(MAX),
instructions_zh NVARCHAR(MAX),
muscle_group NVARCHAR(100),
secondary_muscles NVARCHAR(MAX), -- JSON array stored as string
target NVARCHAR(100),
image NVARCHAR(500),
gif_url NVARCHAR(500),
created_at DATETIME2
);`,
postgresql: `CREATE TABLE exercises (
id VARCHAR(10) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
category VARCHAR(100),
body_part VARCHAR(100),
equipment VARCHAR(100),
instructions_en TEXT,
instructions_es TEXT,
instructions_it TEXT,
instructions_tr TEXT,
instructions_ru TEXT,
instructions_zh TEXT,
muscle_group VARCHAR(100),
secondary_muscles JSONB,
target VARCHAR(100),
image VARCHAR(500),
gif_url VARCHAR(500),
created_at TIMESTAMPTZ
);`,
mysql: `CREATE TABLE exercises (
id VARCHAR(10) PRIMARY KEY,
name VARCHAR(255) NOT NULL,
category VARCHAR(100),
body_part VARCHAR(100),
equipment VARCHAR(100),
instructions_en TEXT,
instructions_es TEXT,
instructions_it TEXT,
instructions_tr TEXT,
instructions_ru TEXT,
instructions_zh TEXT,
muscle_group VARCHAR(100),
secondary_muscles JSON,
target VARCHAR(100),
image VARCHAR(500),
gif_url VARCHAR(500),
created_at DATETIME
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;`,
sqlite: `CREATE TABLE exercises (
id TEXT PRIMARY KEY,
name TEXT NOT NULL,
category TEXT,
body_part TEXT,
equipment TEXT,
instructions_en TEXT,
instructions_es TEXT,
instructions_it TEXT,
instructions_tr TEXT,
instructions_ru TEXT,
instructions_zh TEXT,
muscle_group TEXT,
secondary_muscles TEXT, -- JSON array stored as string
target TEXT,
image TEXT,
gif_url TEXT,
created_at TEXT
);`
};
// ══════════════════════════════════════════════════
// ── API Code Templates ─────────────────────────────
// ══════════════════════════════════════════════════
const API_TEMPLATES = {
curl: {
getOne: (base) =>
`# GET single exercise by ID
curl -s "${base}/exercises/0001"
# Pretty-print the response (Python 3 built-in)
curl -s "${base}/exercises/0001" | python3 -m json.tool`,
getAll: (base) =>
`# GET all exercises — page 1, 20 per page
curl -s "${base}/exercises?page=1&limit=20"
# Page 2, 50 per page
curl -s "${base}/exercises?page=2&limit=50"
# Response shape:
# {
# "data": [ ...exercises ],
# "total": 1324,
# "page": 1,
# "limit": 20,
# "totalPages": 67
# }`,
getFiltered: (base) =>
`# Filter by category
curl -s "${base}/exercises?category=Strength&page=1&limit=20"
# Filter by body part
curl -s "${base}/exercises?body_part=Chest&page=1&limit=20"
# Combine filters
curl -s "${base}/exercises?category=Strength&body_part=Chest&page=1&limit=20"
# With equipment filter
curl -s "${base}/exercises?equipment=Barbell&target=Pectorals&page=1&limit=20"`,
},
js: {
getOne: (base) =>
`const BASE_URL = '${base}';
// GET /exercises/:id — fetch a single exercise
async function getExercise(id) {
const res = await fetch(\`\${BASE_URL}/exercises/\${id}\`);
if (!res.ok) throw new Error(\`HTTP \${res.status}\`);
return res.json();
}
// Usage
const exercise = await getExercise('0001');
console.log(exercise.name); // "Barbell Bench Press"
console.log(exercise.category); // "Strength"
console.log(exercise.gif_url); // "videos/0001.gif"`,
getAll: (base) =>
`const BASE_URL = '${base}';
// GET /exercises?page=&limit= — paginated list
async function getExercises({ page = 1, limit = 20 } = {}) {
const params = new URLSearchParams({ page, limit });
const res = await fetch(\`\${BASE_URL}/exercises?\${params}\`);
if (!res.ok) throw new Error(\`HTTP \${res.status}\`);
return res.json();
}
// Usage
const result = await getExercises({ page: 1, limit: 20 });
console.log(result.data); // Array of exercises
console.log(result.total); // 1324
console.log(result.page); // 1
console.log(result.totalPages); // 67`,
getFiltered: (base) =>
`const BASE_URL = '${base}';
// GET /exercises with filters
async function getExercisesFiltered({
page = 1,
limit = 20,
category,
bodyPart,
equipment,
target,
} = {}) {
const params = new URLSearchParams({ page, limit });
if (category) params.set('category', category);
if (bodyPart) params.set('body_part', bodyPart);
if (equipment) params.set('equipment', equipment);
if (target) params.set('target', target);
const res = await fetch(\`\${BASE_URL}/exercises?\${params}\`);
if (!res.ok) throw new Error(\`HTTP \${res.status}\`);
return res.json();
}
// Usage
const result = await getExercisesFiltered({
category: 'Strength',
bodyPart: 'Chest',
page: 1,
limit: 20,
});
console.log(result.data[0].name); // e.g. "Barbell Bench Press"`,
},
python: {
getOne: (base) =>
`import requests
BASE_URL = "${base}"
# GET /exercises/:id — fetch a single exercise
def get_exercise(exercise_id: str) -> dict:
res = requests.get(f"{BASE_URL}/exercises/{exercise_id}")
res.raise_for_status()
return res.json()
# Usage
exercise = get_exercise("0001")
print(exercise["name"]) # Barbell Bench Press
print(exercise["category"]) # Strength
print(exercise["gif_url"]) # videos/0001.gif`,
getAll: (base) =>
`import requests
BASE_URL = "${base}"
# GET /exercises?page=&limit= — paginated list
def get_exercises(page: int = 1, limit: int = 20) -> dict:
res = requests.get(f"{BASE_URL}/exercises", params={
"page": page,
"limit": limit,
})
res.raise_for_status()
return res.json()
# Usage
result = get_exercises(page=1, limit=20)
print(result["data"]) # list of exercises
print(result["total"]) # 1324
print(result["page"]) # 1
print(result["totalPages"]) # 67`,
getFiltered: (base) =>
`import requests
BASE_URL = "${base}"
# GET /exercises with filters
def get_exercises_filtered(
page: int = 1,
limit: int = 20,
category: str = None,
body_part: str = None,
equipment: str = None,
target: str = None,
) -> dict:
params = {"page": page, "limit": limit}
if category: params["category"] = category
if body_part: params["body_part"] = body_part
if equipment: params["equipment"] = equipment
if target: params["target"] = target
res = requests.get(f"{BASE_URL}/exercises", params=params)
res.raise_for_status()
return res.json()
# Usage
result = get_exercises_filtered(
category="Strength",
body_part="Chest",
page=1,
limit=20,
)
print(result["data"][0]["name"]) # e.g. Barbell Bench Press`,
},
csharp: {
getOne: (base) =>
`using System.Net.Http.Json;
var client = new HttpClient { BaseAddress = new Uri("${base}") };
// GET /exercises/:id — fetch a single exercise
async Task<Exercise?> GetExerciseAsync(string id)
{
var res = await client.GetAsync($"/exercises/{id}");
res.EnsureSuccessStatusCode();
return await res.Content.ReadFromJsonAsync<Exercise>();
}
// Usage
var exercise = await GetExerciseAsync("0001");
Console.WriteLine(exercise?.Name); // Barbell Bench Press
Console.WriteLine(exercise?.Category); // Strength
Console.WriteLine(exercise?.GifUrl); // videos/0001.gif
// Model
record Exercise(
string Id, string Name, string Category, string BodyPart,
string Equipment, string? InstructionsEn, string? InstructionsEs, string? InstructionsIt, string? InstructionsTr, string? InstructionsRu,
string MuscleGroup, string[] SecondaryMuscles, string Target,
string Image, string GifUrl, string? CreatedAt
);`,
getAll: (base) =>
`using System.Net.Http.Json;
var client = new HttpClient { BaseAddress = new Uri("${base}") };
// GET /exercises?page=&limit= — paginated list
async Task<PagedResult<Exercise>> GetExercisesAsync(int page = 1, int limit = 20)
{
return await client.GetFromJsonAsync<PagedResult<Exercise>>(
$"/exercises?page={page}&limit={limit}"
) ?? throw new Exception("Null response");
}
// Usage
var result = await GetExercisesAsync(page: 1, limit: 20);
Console.WriteLine(result.Total); // 1324
Console.WriteLine(result.TotalPages); // 67
// Models
record Exercise(string Id, string Name, string Category, string BodyPart,
string Equipment, string? InstructionsEn, string Target, string GifUrl);
record PagedResult<T>(T[] Data, int Total, int Page, int Limit, int TotalPages);`,
getFiltered: (base) =>
`using System.Net.Http.Json;
using System.Web;
var client = new HttpClient { BaseAddress = new Uri("${base}") };
// GET /exercises with filters
async Task<PagedResult<Exercise>> GetExercisesFilteredAsync(
int page = 1, int limit = 20,
string? category = null, string? bodyPart = null,
string? equipment = null, string? target = null)
{
var qs = HttpUtility.ParseQueryString(string.Empty);
qs["page"] = page.ToString();
qs["limit"] = limit.ToString();
if (category != null) qs["category"] = category;
if (bodyPart != null) qs["body_part"] = bodyPart;
if (equipment != null) qs["equipment"] = equipment;
if (target != null) qs["target"] = target;
return await client.GetFromJsonAsync<PagedResult<Exercise>>(
$"/exercises?{qs}"
) ?? throw new Exception("Null response");
}
// Usage
var result = await GetExercisesFilteredAsync(
category: "Strength", bodyPart: "Chest", page: 1, limit: 20);
Console.WriteLine(result.Data[0].Name); // e.g. Barbell Bench Press`,
},
java: {
getOne: (base) =>
`import java.net.URI;
import java.net.http.*;
HttpClient client = HttpClient.newHttpClient();
String BASE_URL = "${base}";
// GET /exercises/:id — fetch a single exercise
String getExercise(String id) throws Exception {
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(BASE_URL + "/exercises/" + id))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() != 200)
throw new RuntimeException("HTTP " + response.statusCode());
return response.body(); // parse with Jackson/Gson
}
// Usage (with Jackson ObjectMapper)
// ObjectMapper mapper = new ObjectMapper();
// Map<?,?> exercise = mapper.readValue(getExercise("0001"), Map.class);`,
getAll: (base) =>
`import java.net.URI;
import java.net.http.*;
HttpClient client = HttpClient.newHttpClient();
String BASE_URL = "${base}";
// GET /exercises?page=&limit= — paginated list
String getExercises(int page, int limit) throws Exception {
String url = BASE_URL + "/exercises?page=" + page + "&limit=" + limit;
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url))
.header("Accept", "application/json")
.GET()
.build();
HttpResponse<String> response =
client.send(request, HttpResponse.BodyHandlers.ofString());
return response.body();
// { "data": [...], "total": 1324, "page": 1, "limit": 20, "totalPages": 67 }
}
// Usage
String json = getExercises(1, 20);`,
getFiltered: (base) =>
`import java.net.URI;
import java.net.URLEncoder;
import java.net.http.*;
import java.nio.charset.StandardCharsets;
HttpClient client = HttpClient.newHttpClient();
String BASE_URL = "${base}";
// GET /exercises with filters
String getExercisesFiltered(
int page, int limit,
String category, String bodyPart, String equipment
) throws Exception {
StringBuilder url = new StringBuilder(
BASE_URL + "/exercises?page=" + page + "&limit=" + limit
);
if (category != null)
url.append("&category=").append(URLEncoder.encode(category, StandardCharsets.UTF_8));
if (bodyPart != null)
url.append("&body_part=").append(URLEncoder.encode(bodyPart, StandardCharsets.UTF_8));
if (equipment != null)
url.append("&equipment=").append(URLEncoder.encode(equipment, StandardCharsets.UTF_8));
HttpRequest request = HttpRequest.newBuilder()
.uri(URI.create(url.toString()))
.header("Accept", "application/json")
.GET()
.build();
return client.send(request, HttpResponse.BodyHandlers.ofString()).body();
}
// Usage
String json = getExercisesFiltered(1, 20, "Strength", "Chest", null);`,
},
php: {
getOne: (base) =>
`<?php
define('BASE_URL', '${base}');
// GET /exercises/:id — fetch a single exercise
function getExercise(string $id): array
{
$url = BASE_URL . '/exercises/' . urlencode($id);
$json = file_get_contents($url);
if ($json === false) throw new RuntimeException("Request failed");
return json_decode($json, true);
}
// Usage
$exercise = getExercise('0001');
echo $exercise['name']; // Barbell Bench Press
echo $exercise['category']; // Strength
echo $exercise['gif_url']; // videos/0001.gif`,
getAll: (base) =>
`<?php
define('BASE_URL', '${base}');
// GET /exercises?page=&limit= — paginated list
function getExercises(int $page = 1, int $limit = 20): array
{
$params = http_build_query(['page' => $page, 'limit' => $limit]);
$json = file_get_contents(BASE_URL . '/exercises?' . $params);
if ($json === false) throw new RuntimeException("Request failed");
return json_decode($json, true);
}
// Usage
$result = getExercises(1, 20);
print_r($result['data']); // array of exercises
echo $result['total']; // 1324
echo $result['totalPages']; // 67`,
getFiltered: (base) =>
`<?php
define('BASE_URL', '${base}');
// GET /exercises with filters
function getExercisesFiltered(
int $page = 1,
int $limit = 20,
?string $category = null,
?string $bodyPart = null,
?string $equipment = null,
?string $target = null
): array {
$params = ['page' => $page, 'limit' => $limit];
if ($category) $params['category'] = $category;
if ($bodyPart) $params['body_part'] = $bodyPart;
if ($equipment) $params['equipment'] = $equipment;
if ($target) $params['target'] = $target;
$json = file_get_contents(BASE_URL . '/exercises?' . http_build_query($params));
if ($json === false) throw new RuntimeException("Request failed");
return json_decode($json, true);
}
// Usage
$result = getExercisesFiltered(1, 20, 'Strength', 'Chest');
echo $result['data'][0]['name']; // e.g. Barbell Bench Press`,
},
go: {
getOne: (base) =>
`package main
import (
"encoding/json"
"fmt"
"net/http"
)
const baseURL = "${base}"
// GetExercise fetches a single exercise by ID
func GetExercise(id string) (map[string]any, error) {
resp, err := http.Get(baseURL + "/exercises/" + id)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != 200 {
return nil, fmt.Errorf("HTTP %d", resp.StatusCode)
}
var result map[string]any
return result, json.NewDecoder(resp.Body).Decode(&result)
}
// Usage
func main() {
exercise, err := GetExercise("0001")
if err != nil { panic(err) }
fmt.Println(exercise["name"]) // Barbell Bench Press
fmt.Println(exercise["category"]) // Strength
}`,
getAll: (base) =>
`package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
)
const baseURL = "${base}"
// GetExercises fetches paginated exercises
func GetExercises(page, limit int) (map[string]any, error) {
params := url.Values{
"page": {strconv.Itoa(page)},
"limit": {strconv.Itoa(limit)},
}
resp, err := http.Get(baseURL + "/exercises?" + params.Encode())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]any
return result, json.NewDecoder(resp.Body).Decode(&result)
}
// Usage
func main() {
result, err := GetExercises(1, 20)
if err != nil { panic(err) }
fmt.Println(result["total"]) // 1324
fmt.Println(result["totalPages"]) // 67
}`,
getFiltered: (base) =>
`package main
import (
"encoding/json"
"fmt"
"net/http"
"net/url"
"strconv"
)
const baseURL = "${base}"
// GetExercisesFiltered fetches exercises with optional filters
func GetExercisesFiltered(page, limit int, category, bodyPart, equipment string) (map[string]any, error) {
params := url.Values{
"page": {strconv.Itoa(page)},
"limit": {strconv.Itoa(limit)},
}
if category != "" { params.Set("category", category) }
if bodyPart != "" { params.Set("body_part", bodyPart) }
if equipment != "" { params.Set("equipment", equipment) }
resp, err := http.Get(baseURL + "/exercises?" + params.Encode())
if err != nil {
return nil, err
}
defer resp.Body.Close()
var result map[string]any
return result, json.NewDecoder(resp.Body).Decode(&result)
}
// Usage
func main() {
result, err := GetExercisesFiltered(1, 20, "Strength", "Chest", "")
if err != nil { panic(err) }
data := result["data"].([]any)
fmt.Println(data[0].(map[string]any)["name"]) // e.g. Barbell Bench Press
}`,
},
};
// ══════════════════════════════════════════════════
// ── LLM Prompt Templates ───────────────────────────
// ══════════════════════════════════════════════════
const FRAMEWORK_META = {
express: { name: 'Express.js (Node.js)', lang: 'JavaScript', pkg: 'express, pg / mysql2 / better-sqlite3', run: 'node index.js' },
fastapi: { name: 'FastAPI (Python)', lang: 'Python', pkg: 'fastapi, uvicorn, sqlalchemy, psycopg2-binary', run: 'uvicorn main:app --reload' },
aspnet: { name: 'ASP.NET Core (C#)', lang: 'C#', pkg: 'Npgsql / MySql.Data / Microsoft.Data.Sqlite', run: 'dotnet run' },
spring: { name: 'Spring Boot (Java)', lang: 'Java', pkg: 'spring-web, spring-data-jpa, db driver', run: 'mvn spring-boot:run' },
laravel: { name: 'Laravel (PHP)', lang: 'PHP', pkg: 'laravel/laravel, db driver', run: 'php artisan serve' },
gin: { name: 'Gin (Go)', lang: 'Go', pkg: 'gin-gonic/gin, database/sql + db driver', run: 'go run main.go' },
};
const DB_META = {
postgresql: { name: 'PostgreSQL' },
mysql: { name: 'MySQL' },
mssql: { name: 'SQL Server' },
sqlite: { name: 'SQLite' },
};
function buildLlmPrompt(fwKey, dbKey) {
const fw = FRAMEWORK_META[fwKey];
const db = DB_META[dbKey];
const schema = DB_SQL[dbKey];
return `You are a senior ${fw.lang} developer. Build a complete REST API using ${fw.name} for an exercise/fitness database.
## Dataset Overview
- 1,324 fitness exercises
- Fields: id (string, e.g. "0001"), name, category, body_part, equipment, instructions_en (full text), instructions_es (Spanish text), instructions_it (Italian text), instructions_tr (Turkish text), instructions_ru (Russian text), instructions_zh (Chinese text), muscle_group, secondary_muscles (JSON array of strings), target, image (relative path like "images/0001.jpg"), gif_url (relative path like "videos/0001.gif"), created_at
## Database Schema (${db.name})
\`\`\`sql
${schema}
\`\`\`
## Required Endpoints
### 1. GET /exercises/:id
- Return a single exercise by its id
- Return 404 JSON error if not found: { "error": "Exercise not found" }
### 2. GET /exercises
Query parameters (all optional):
- page (integer, default: 1)
- limit (integer, default: 20, max: 100)
- category (string, case-insensitive partial match)
- body_part (string, case-insensitive partial match)
- equipment (string, case-insensitive partial match)
- muscle_group (string, case-insensitive partial match)
- target (string, case-insensitive partial match)
Response format:
\`\`\`json
{
"data": [ /* array of exercise objects */ ],
"total": 1324,
"page": 1,
"limit": 20,
"totalPages": 67
}
\`\`\`
### 3. GET /exercises/random
- Return 1 random exercise object
### 4. GET /categories
- Return sorted array of unique category strings
### 5. GET /body-parts
- Return sorted array of unique body_part strings
### 6. GET /equipment
- Return sorted array of unique equipment strings
## Technical Requirements
- Read DB connection string from an environment variable
- Use parameterized queries (never interpolate user input into SQL)
- Return JSON with Content-Type: application/json
- Enable CORS for all origins (or read allowed origins from env var \`ALLOWED_ORIGINS\`)
- Validate page/limit: must be positive integers; reject with 400 if invalid
- Return 500 with { "error": "Internal server error" } on unexpected errors
- Log each request: method, path, status code, duration in ms
## Packages to use
${fw.pkg}
## Deliverables
1. Complete, runnable source code (not pseudocode — every file needed)
2. Brief setup instructions:
- Install dependencies
- Run: \`${fw.run}\`
Write clean, production-quality code. Do not skip error handling.`;
}
// ══════════════════════════════════════════════════
// ── State ──────────────────────────────────────────
// ══════════════════════════════════════════════════
let currentDb = 'mssql';
let currentLang = 'curl';
let currentFw = 'express';
let currentLlmDb = 'postgresql';
let currentBaseUrl = 'https://api.yourapp.com';
let EXERCISES = null; // lazy-loaded when generate is clicked
// ══════════════════════════════════════════════════
// ── DB Setup ───────────────────────────────────────
// ══════════════════════════════════════════════════
const createSqlEl = document.getElementById('create-table-sql');
const copyCreateBtn = document.getElementById('copy-create-btn');
const generateBtn = document.getElementById('generate-sql-btn');
const generateStatus = document.getElementById('generate-status');
function switchDbTab(db) {
currentDb = db;
document.querySelectorAll('#db-tabs .tab-btn').forEach(t => {
t.classList.toggle('active', t.dataset.db === db);
});
createSqlEl.textContent = DB_SQL[db];
}
function copyToClipboard(text, btn, resetLabel = 'Copy') {
navigator.clipboard.writeText(text).then(() => {
btn.textContent = 'Copied!';
btn.classList.add('copied');
setTimeout(() => {
btn.textContent = resetLabel;
btn.classList.remove('copied');
}, 2000);
});
}
function escStr(val, db) {
if (val === null || val === undefined) return 'NULL';
const s = String(val).replace(/'/g, "''");
return db === 'mssql' ? `N'${s}'` : `'${s}'`;
}
function buildInserts(exercises, db) {
const lines = [];
const header = db === 'mssql' ? 'BEGIN TRANSACTION;\nGO\n' : 'BEGIN;\n';
const footer = db === 'mssql' ? '\nCOMMIT;\nGO' : '\nCOMMIT;';
lines.push(header);
exercises.forEach((ex, i) => {
const muscles = JSON.stringify(Array.isArray(ex.secondary_muscles) ? ex.secondary_muscles : []);
const instrEn = ex.instructions && ex.instructions.en
? ex.instructions.en
: (Array.isArray(ex.instruction_steps) ? ex.instruction_steps.join(' ') : (ex.instructions || ''));
const instrEs = ex.instructions && ex.instructions.es ? ex.instructions.es : '';
const instrIt = ex.instructions && ex.instructions.it ? ex.instructions.it : '';
const instrTr = ex.instructions && ex.instructions.tr ? ex.instructions.tr : '';
const instrRu = ex.instructions && ex.instructions.ru ? ex.instructions.ru : '';
const instrZh = ex.instructions && ex.instructions.zh ? ex.instructions.zh : '';
const vals = [
escStr(ex.id, db),
escStr(ex.name, db),
escStr(ex.category, db),
escStr(ex.body_part, db),
escStr(ex.equipment, db),
escStr(instrEn, db),
escStr(instrEs, db),
escStr(instrIt, db),
escStr(instrTr, db),
escStr(instrRu, db),
escStr(instrZh, db),
escStr(ex.muscle_group, db),
escStr(muscles, db),
escStr(ex.target, db),
escStr(ex.image, db),
escStr(ex.gif_url, db),
escStr(ex.created_at, db),
].join(', ');
lines.push(`INSERT INTO exercises (id, name, category, body_part, equipment, instructions_en, instructions_es, instructions_it, instructions_tr, instructions_ru, instructions_zh, muscle_group, secondary_muscles, target, image, gif_url, created_at) VALUES (${vals});`);
if (db === 'mssql' && (i + 1) % 50 === 0 && i + 1 < exercises.length) {
lines.push('GO');
}
});
lines.push(footer);
return lines.join('\n');
}
async function generateInsertSql() {
generateBtn.disabled = true;
try {
if (!EXERCISES) {
generateStatus.textContent = 'Loading exercise data…';
const res = await fetch('data/exercises.json');
if (!res.ok) throw new Error('Could not load exercises.json');
EXERCISES = await res.json();
}
generateStatus.textContent = 'Generating…';
await new Promise(r => setTimeout(r, 10));
const sql = buildInserts(EXERCISES, currentDb);
const blob = new Blob([sql], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `exercises_insert_${currentDb}.sql`;
a.click();
URL.revokeObjectURL(url);
generateStatus.textContent = `✓ Downloaded exercises_insert_${currentDb}.sql`;
} catch (e) {
generateStatus.textContent = 'Error generating file.';
console.error(e);
}
generateBtn.disabled = false;
}
// Wire DB tab events
document.querySelectorAll('#db-tabs .tab-btn').forEach(tab => {
tab.addEventListener('click', () => switchDbTab(tab.dataset.db));
});
copyCreateBtn.addEventListener('click', () => copyToClipboard(createSqlEl.textContent, copyCreateBtn));
generateBtn.addEventListener('click', generateInsertSql);
// Init
switchDbTab('mssql');
// ══════════════════════════════════════════════════
// ── API Integration ────────────────────────────────
// ══════════════════════════════════════════════════
const baseUrlInput = document.getElementById('base-url-input');
function renderApiCode() {
const templates = API_TEMPLATES[currentLang];
const base = currentBaseUrl || 'https://api.yourapp.com';
document.getElementById('code-getOne').textContent = templates.getOne(base);
document.getElementById('code-getAll').textContent = templates.getAll(base);
document.getElementById('code-getFiltered').textContent = templates.getFiltered(base);
}
function switchLangTab(lang) {
currentLang = lang;
document.querySelectorAll('#lang-tabs .tab-btn').forEach(t => {
t.classList.toggle('active', t.dataset.lang === lang);
});
renderApiCode();
}
baseUrlInput.addEventListener('input', () => {
currentBaseUrl = baseUrlInput.value.trim() || 'https://api.yourapp.com';
renderApiCode();
});
document.querySelectorAll('#lang-tabs .tab-btn').forEach(tab => {
tab.addEventListener('click', () => switchLangTab(tab.dataset.lang));
});
// Copy button for code blocks
function copyBlock(btn) {
const pre = btn.nextElementSibling;
copyToClipboard(pre.textContent, btn);
}
// Init
renderApiCode();
// ══════════════════════════════════════════════════
// ── Ask Your LLM ───────────────────────────────────
// ══════════════════════════════════════════════════
const llmPromptBox = document.getElementById('llm-prompt');
const copyPromptBtn = document.getElementById('copy-prompt-btn');
function renderLlmPrompt() {
llmPromptBox.value = buildLlmPrompt(currentFw, currentLlmDb);
}
document.querySelectorAll('#framework-tabs .selector-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentFw = btn.dataset.fw;
document.querySelectorAll('#framework-tabs .selector-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderLlmPrompt();
});
});
document.querySelectorAll('#llm-db-tabs .selector-btn').forEach(btn => {
btn.addEventListener('click', () => {
currentLlmDb = btn.dataset.llmdb;
document.querySelectorAll('#llm-db-tabs .selector-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
renderLlmPrompt();
});
});
copyPromptBtn.addEventListener('click', () => {
navigator.clipboard.writeText(llmPromptBox.value).then(() => {
copyPromptBtn.textContent = '✓ Copied!';
copyPromptBtn.classList.add('copied');
setTimeout(() => {
copyPromptBtn.innerHTML = `<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="9" height="11" rx="1.5"/><path d="M11 4V3a1 1 0 0 0-1-1H3a1 1 0 0 0-1 1v10a1 1 0 0 0 1 1h1"/></svg> Copy Prompt`;
copyPromptBtn.classList.remove('copied');
}, 2500);
});
});
// Init
renderLlmPrompt();
// ══════════════════════════════════════════════════
// ── Scrollspy ──────────────────────────────────────
// ══════════════════════════════════════════════════
const sections = document.querySelectorAll('section[id]');
const navLinks = document.querySelectorAll('.nav-link[data-section]');
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
navLinks.forEach(link => {
link.classList.toggle('active', link.dataset.section === entry.target.id);
});
}
});
}, { rootMargin: '-20% 0px -70% 0px', threshold: 0 });
sections.forEach(s => observer.observe(s));
</script>
</body>
</html>