Skip to content

Commit 92691aa

Browse files
committed
Editor: inset shadow + line-number gutter; home: section eyebrows
Three independent changes per the latest impeccable review. 1. Inset shadow on .runner-editor Adds the "recessed surface" convention for editable text inputs: box-shadow: inset 0 1px 2px rgba(82, 16, 0, 0.04). Very subtle — the editor reads as a slot the eye can rest into rather than a flat panel like the output. The focus-within state stacks the inset shadow with the existing 3px accent glow. 2. Line-number gutter CodeMirror gains lineNumbers() in public/editor.js. The .cm-gutters CSS (was display: none) now styles the gutter as a transparent strip with --hairline-soft right border, --muted tabular-nums numerals, .85em font-size, right-aligned with 2ch minimum width and var(--space-2) right padding. This is the IDE-style "code area" convention from Replit/Codecademy/Stripe docs — strongest single signal that the panel is editable code. 3. Section eyebrows on the home page (prototype) render_home() groups the 109 examples by section in the order each section first appears in the manifest, then emits one eyebrow divider per section followed by every example in that section. 13 eyebrows total: Basics, Data Model, Text, Control Flow, Iteration, Collections, Functions, Classes, Errors, Modules, Types, Standard Library, Async. The eyebrow markup uses the existing .eyebrow class plus a new .grid-section rule that spans grid-column: 1 / -1 to act as a full-width row break inside the auto-fit grid. No new chrome, one CSS rule. Cards lose their per-card section eyebrow (was redundant with the divider above) — each card is now just h2 (title) + meta (summary). The test that asserts the card markup still passes because it only checks the <a class="card" href="..."> opening tag. 62 tests pass; SEO/cache lint clears 110 pages; ruff clean.
1 parent 20d4e6f commit 92691aa

6 files changed

Lines changed: 36 additions & 21 deletions

File tree

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EditorState } from 'https://esm.sh/@codemirror/state@6.5.2';
2-
import { EditorView } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2';
2+
import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2';
33
import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1';
44
import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3';
55

@@ -15,6 +15,7 @@ if (textarea && form) {
1515
extensions: [
1616
python(),
1717
syntaxHighlighting(defaultHighlightStyle),
18+
lineNumbers(),
1819
EditorView.lineWrapping,
1920
EditorView.updateListener.of((update) => {
2021
if (update.docChanged) textarea.value = update.state.doc.toString();

public/editor.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { EditorState } from 'https://esm.sh/@codemirror/state@6.5.2';
2-
import { EditorView } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2';
2+
import { EditorView, lineNumbers } from 'https://esm.sh/@codemirror/view@6.41.1?deps=@codemirror/state@6.5.2';
33
import { defaultHighlightStyle, syntaxHighlighting } from 'https://esm.sh/@codemirror/language@6.12.3?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1';
44
import { python } from 'https://esm.sh/@codemirror/lang-python@6.2.1?deps=@codemirror/state@6.5.2,@codemirror/view@6.41.1,@codemirror/language@6.12.3';
55

@@ -15,6 +15,7 @@ if (textarea && form) {
1515
extensions: [
1616
python(),
1717
syntaxHighlighting(defaultHighlightStyle),
18+
lineNumbers(),
1819
EditorView.lineWrapping,
1920
EditorView.updateListener.of((update) => {
2021
if (update.docChanged) textarea.value = update.state.doc.toString();

public/site.css

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
.cm-scroller { font-family: inherit; line-height: 1.5; }
2121
.cm-content { padding: 0; }
2222
.cm-line { padding: 0; }
23-
.cm-gutters { display: none; }
23+
.cm-gutters { background: transparent; border-right: 1px solid var(--hairline-soft); color: var(--muted); font-variant-numeric: tabular-nums; }
24+
.cm-lineNumbers .cm-gutterElement { padding: 0 var(--space-2) 0 0; min-width: 2ch; text-align: right; font-size: .85em; }
2425
.cm-activeLine, .cm-activeLineGutter { background: transparent; }
2526
.cm-selectionBackground, .cm-focused .cm-selectionBackground { background: rgba(255, 72, 1, 0.18) !important; }
2627
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums; }
@@ -56,6 +57,8 @@
5657
to { opacity: 1; background: rgba(245, 241, 235, 0.95); box-shadow: 0 1px 8px rgba(82, 16, 0, 0.06); }
5758
}
5859
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--space-3); }
60+
.grid-section { grid-column: 1 / -1; margin: var(--space-4) 0 0; }
61+
.grid-section:first-child { margin-top: 0; }
5962
.card { display: block; min-height: 10rem; border: 1px solid var(--hairline); border-radius: .75rem; padding: var(--space-3); background: var(--surface-2); color: inherit; text-decoration: none; box-shadow: 0 1px 3px rgba(82, 16, 0, 0.04), 0 4px 12px rgba(82, 16, 0, 0.02); transition-property: transform, background-color, border-color; transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, 0.2, 1); }
6063
.card:hover { transform: translateY(-2px); background: var(--surface-3); border-color: var(--accent); }
6164
.card h2 { text-decoration: underline; text-decoration-color: var(--hairline); text-underline-offset: .18em; }
@@ -110,8 +113,8 @@
110113
.runner-panel { min-height: 18rem; display: flex; flex-direction: column; border: 1px dashed var(--hairline); border-radius: .75rem; padding: var(--space-3); background: var(--surface); }
111114
.runner-panel h3 { margin: 0 0 var(--space-3); padding-bottom: var(--space-2); border-bottom: 1px solid var(--hairline-soft); font-size: 1.05rem; letter-spacing: -0.02em; }
112115
.runner-panel pre { flex: 1; min-height: 0; overflow: visible; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; }
113-
.runner-editor { border-style: solid; background: var(--surface-2); cursor: text; transition: box-shadow 160ms cubic-bezier(0.2, 0, 0, 1); }
114-
.runner-editor:focus-within { box-shadow: 0 0 0 3px rgba(255, 72, 1, 0.12); }
116+
.runner-editor { border-style: solid; background: var(--surface-2); cursor: text; box-shadow: inset 0 1px 2px rgba(82, 16, 0, 0.04); transition: box-shadow 160ms cubic-bezier(0.2, 0, 0, 1); }
117+
.runner-editor:focus-within { box-shadow: inset 0 1px 2px rgba(82, 16, 0, 0.04), 0 0 0 3px rgba(255, 72, 1, 0.12); }
115118
.execution-time { min-height: 1.5rem; margin: var(--space-2) 0 0; padding-top: var(--space-2); border-top: 1px solid var(--hairline-soft); color: var(--muted); font-size: .88rem; font-variant-numeric: tabular-nums; }
116119
.playground-toolbar { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin: .8rem 0 1rem; }
117120
.tool-button { min-height: 40px; border: 1px solid var(--hairline); border-radius: 9999px; padding: .62rem .9rem; background: var(--surface-2); color: var(--text); cursor: pointer; transition-property: transform, background-color, border-style; transition-duration: 150ms; }
Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020
.cm-scroller { font-family: inherit; line-height: 1.5; }
2121
.cm-content { padding: 0; }
2222
.cm-line { padding: 0; }
23-
.cm-gutters { display: none; }
23+
.cm-gutters { background: transparent; border-right: 1px solid var(--hairline-soft); color: var(--muted); font-variant-numeric: tabular-nums; }
24+
.cm-lineNumbers .cm-gutterElement { padding: 0 var(--space-2) 0 0; min-width: 2ch; text-align: right; font-size: .85em; }
2425
.cm-activeLine, .cm-activeLineGutter { background: transparent; }
2526
.cm-selectionBackground, .cm-focused .cm-selectionBackground { background: rgba(255, 72, 1, 0.18) !important; }
2627
code { font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace; font-variant-numeric: tabular-nums; }
@@ -56,6 +57,8 @@
5657
to { opacity: 1; background: rgba(245, 241, 235, 0.95); box-shadow: 0 1px 8px rgba(82, 16, 0, 0.06); }
5758
}
5859
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: var(--space-3); }
60+
.grid-section { grid-column: 1 / -1; margin: var(--space-4) 0 0; }
61+
.grid-section:first-child { margin-top: 0; }
5962
.card { display: block; min-height: 10rem; border: 1px solid var(--hairline); border-radius: .75rem; padding: var(--space-3); background: var(--surface-2); color: inherit; text-decoration: none; box-shadow: 0 1px 3px rgba(82, 16, 0, 0.04), 0 4px 12px rgba(82, 16, 0, 0.02); transition-property: transform, background-color, border-color; transition-duration: 200ms; transition-timing-function: cubic-bezier(0, 0, 0.2, 1); }
6063
.card:hover { transform: translateY(-2px); background: var(--surface-3); border-color: var(--accent); }
6164
.card h2 { text-decoration: underline; text-decoration-color: var(--hairline); text-underline-offset: .18em; }
@@ -110,8 +113,8 @@
110113
.runner-panel { min-height: 18rem; display: flex; flex-direction: column; border: 1px dashed var(--hairline); border-radius: .75rem; padding: var(--space-3); background: var(--surface); }
111114
.runner-panel h3 { margin: 0 0 var(--space-3); padding-bottom: var(--space-2); border-bottom: 1px solid var(--hairline-soft); font-size: 1.05rem; letter-spacing: -0.02em; }
112115
.runner-panel pre { flex: 1; min-height: 0; overflow: visible; white-space: pre-wrap; overflow-wrap: anywhere; margin: 0; }
113-
.runner-editor { border-style: solid; background: var(--surface-2); cursor: text; transition: box-shadow 160ms cubic-bezier(0.2, 0, 0, 1); }
114-
.runner-editor:focus-within { box-shadow: 0 0 0 3px rgba(255, 72, 1, 0.12); }
116+
.runner-editor { border-style: solid; background: var(--surface-2); cursor: text; box-shadow: inset 0 1px 2px rgba(82, 16, 0, 0.04); transition: box-shadow 160ms cubic-bezier(0.2, 0, 0, 1); }
117+
.runner-editor:focus-within { box-shadow: inset 0 1px 2px rgba(82, 16, 0, 0.04), 0 0 0 3px rgba(255, 72, 1, 0.12); }
115118
.execution-time { min-height: 1.5rem; margin: var(--space-2) 0 0; padding-top: var(--space-2); border-top: 1px solid var(--hairline-soft); color: var(--muted); font-size: .88rem; font-variant-numeric: tabular-nums; }
116119
.playground-toolbar { display: flex; gap: .5rem; flex-wrap: wrap; align-items: center; margin: .8rem 0 1rem; }
117120
.tool-button { min-height: 40px; border: 1px solid var(--hairline); border-radius: 9999px; padding: .62rem .9rem; background: var(--surface-2); color: var(--text); cursor: pointer; transition-property: transform, background-color, border-style; transition-duration: 150ms; }

src/app.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -499,19 +499,26 @@ def _layout(title: str, content: str, description: str | None = None, path: str
499499

500500

501501
def render_home() -> str:
502-
cards = []
502+
# Group examples by section in the order each section first appears
503+
# in the manifest, then emit one eyebrow divider per section followed
504+
# by every example in that section.
505+
by_section: dict[str, list[dict]] = {}
503506
for example in list_examples():
504-
cards.append(
505-
_replace(
506-
'<a class="card" href="/examples/__SLUG__"><p class="eyebrow">__SECTION__</p><h2>__TITLE__</h2><p class="meta">__SUMMARY__</p></a>',
507-
{
508-
"SECTION": html.escape(example["section"]),
509-
"SLUG": html.escape(example["slug"]),
510-
"TITLE": html.escape(example["title"]),
511-
"SUMMARY": html.escape(example["summary"]),
512-
},
507+
by_section.setdefault(example["section"], []).append(example)
508+
cards = []
509+
for section, examples in by_section.items():
510+
cards.append(f'<p class="eyebrow grid-section">{html.escape(section)}</p>')
511+
for example in examples:
512+
cards.append(
513+
_replace(
514+
'<a class="card" href="/examples/__SLUG__"><h2>__TITLE__</h2><p class="meta">__SUMMARY__</p></a>',
515+
{
516+
"SLUG": html.escape(example["slug"]),
517+
"TITLE": html.escape(example["title"]),
518+
"SUMMARY": html.escape(example["summary"]),
519+
},
520+
)
513521
)
514-
)
515522
content = _replace(
516523
_template("home.html"),
517524
{"PYTHON_VERSION": html.escape(PYTHON_VERSION), "CARDS": "".join(cards)},

src/asset_manifest.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# Generated by scripts/fingerprint_assets.py. Do not edit by hand.
2-
ASSET_PATHS = {'SITE_CSS': '/site.9ad0aa5c4ab6.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.dd81f5171b14.js'}
3-
HTML_CACHE_VERSION = 'eaa23cebb468'
2+
ASSET_PATHS = {'SITE_CSS': '/site.f3fd8c78f5ba.css', 'SYNTAX_JS': '/syntax-highlight.3b6c7f730d46.js', 'EDITOR_JS': '/editor.a4a7766e1b9b.js'}
3+
HTML_CACHE_VERSION = '6e21b65d3855'

0 commit comments

Comments
 (0)