diff --git a/docs/challenge-viewer.js b/docs/challenge-viewer.js index 4d14e50..9c46ec5 100644 --- a/docs/challenge-viewer.js +++ b/docs/challenge-viewer.js @@ -126,6 +126,21 @@ function initChallengeSelect(root, selectedChallenge) { }); } +function initAdjacentLinks(root, selectedChallenge) { + const currentIndex = window.SQL_CHALLENGES.findIndex((challenge) => challenge.folder === selectedChallenge.folder); + const previous = window.SQL_CHALLENGES[(currentIndex - 1 + window.SQL_CHALLENGES.length) % window.SQL_CHALLENGES.length]; + const next = window.SQL_CHALLENGES[(currentIndex + 1) % window.SQL_CHALLENGES.length]; + const previousLink = root.querySelector("[data-previous-challenge]"); + const nextLink = root.querySelector("[data-next-challenge]"); + + previousLink.href = `challenge.html?id=${encodeURIComponent(previous.folder)}`; + previousLink.textContent = `Previous: ${previous.id}`; + previousLink.setAttribute("aria-label", `Open previous challenge, ${previous.title}`); + nextLink.href = `challenge.html?id=${encodeURIComponent(next.folder)}`; + nextLink.textContent = `Next: ${next.id}`; + nextLink.setAttribute("aria-label", `Open next challenge, ${next.title}`); +} + function getFileUrl(challenge, file) { return `${RAW_BASE}/${challenge.folder}/${file.file}`; } @@ -224,9 +239,11 @@ function initChallengeViewer() { root.querySelector("[data-viewer-id]").textContent = challenge.id; root.querySelector("[data-viewer-skill]").textContent = window.SQL_SKILL_LABELS[challenge.skill]; root.querySelector("[data-viewer-difficulty]").textContent = window.SQL_DIFFICULTY_LABELS[challenge.difficulty]; + root.querySelector("[data-breadcrumb-current]").textContent = `Challenge ${challenge.id}`; root.querySelector("[data-github-link]").href = `${GITHUB_BASE}/${challenge.folder}`; root.querySelector("[data-run-command]").textContent = getRunCommand(challenge); initChallengeSelect(root, challenge); + initAdjacentLinks(root, challenge); const state = { challenge, diff --git a/docs/challenge.html b/docs/challenge.html index 5575e97..1d2aa00 100644 --- a/docs/challenge.html +++ b/docs/challenge.html @@ -17,15 +17,25 @@
+ +

Challenge viewer

@@ -39,6 +49,11 @@

Loading challenge...

+
+
+
+

Start here

+

Move from selection to review without losing context.

+
+
+
+ 1 +

Pick a challenge

+

Use the finder to filter by skill area and difficulty, or jump straight into the first challenge.

+ Open Finder +
+
+ 2 +

Read it in the viewer

+

Open the prompt, schema, solution, and expected output without leaving the project site.

+ Open Viewer +
+
+ 3 +

Run it locally

+

Copy the SQL commands, compare against the validated snapshot, and inspect the source on GitHub.

+ Open Repository +
+
+
+

Challenge finder

@@ -169,7 +196,13 @@

Bring a realistic analytics question.

\ No newline at end of file diff --git a/docs/site.css b/docs/site.css index 87c414f..416c7cb 100644 --- a/docs/site.css +++ b/docs/site.css @@ -67,9 +67,17 @@ main, text-decoration: none; } +.nav-cta { + padding: 4px 10px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--bg); +} + .nav-links a:hover, .brand:hover, -.site-footer a:hover { +.site-footer a:hover, +.start-grid a:hover { color: var(--accent); } @@ -156,6 +164,7 @@ h3 { } .query-preview { + min-width: 0; overflow: hidden; border: 1px solid #263646; border-radius: 8px; @@ -210,6 +219,7 @@ code { } .metrics span, +.start-grid p, .path-grid p, .split p { color: var(--muted); @@ -341,10 +351,64 @@ code { min-height: auto; } +.start-section { + padding-top: 56px; +} + +.start-grid { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 16px; +} + +.start-grid article { + display: grid; + gap: 12px; + min-height: 220px; + padding: 22px; + border: 1px solid var(--line); + border-radius: 8px; + background: var(--surface); +} + +.start-grid span { + display: inline-flex; + align-items: center; + justify-content: center; + width: 34px; + height: 34px; + border-radius: 8px; + background: var(--accent); + color: #ffffff; + font-weight: 700; +} + +.start-grid a { + align-self: end; + color: var(--accent); + font-weight: 650; + text-decoration: none; +} + .viewer-main { padding-bottom: 72px; } +.breadcrumb { + display: flex; + flex-wrap: wrap; + gap: 8px; + padding-top: 28px; + color: var(--muted); + font-size: 0.92rem; +} + +.breadcrumb a { + color: var(--accent); + font-weight: 650; + text-decoration: none; +} + .viewer-hero { display: grid; grid-template-columns: minmax(0, 1.15fr) minmax(320px, 0.85fr); @@ -367,6 +431,13 @@ code { font-weight: 650; } +.viewer-sequence { + display: flex; + flex-wrap: wrap; + gap: 10px; + margin-top: 14px; +} + .viewer-meta span { padding: 6px 10px; border: 1px solid var(--line); @@ -376,6 +447,7 @@ code { .viewer-run-card, .viewer-panel { + min-width: 0; border: 1px solid var(--line); border-radius: 8px; background: var(--surface); @@ -525,10 +597,17 @@ code { margin-top: 18px; border-radius: 8px; background: var(--surface-strong); + white-space: pre-wrap; +} + +.command code { + overflow-wrap: anywhere; + white-space: pre-wrap; } .site-footer { display: flex; + flex-wrap: wrap; justify-content: space-between; gap: 16px; padding: 22px 0; @@ -542,6 +621,12 @@ code { text-decoration: none; } +.footer-links { + display: flex; + flex-wrap: wrap; + gap: 14px; +} + @media (max-width: 860px) { .nav, .site-footer { @@ -565,6 +650,7 @@ code { } .metrics, + .start-grid, .path-grid, .result-grid, .finder-controls {