Hero collapse, section blocks, editor polish, rubric scores#3
Merged
Conversation
Four tasks bundled because they all touch the same review surface. 1. SECTION_FIGURE_SCORES (src/marginalia.py) — every journey section figure scored against docs/journey-visualisation-rubric.md (24 entries). 1 figure at 9.5 (iter-protocol, the canonical iter()/next() picture); 17 at 9.0; 3 at 8.5 (abstract concepts); 3 at 8.0 (workers constraint figures). Mean 8.94. 2. EXAMPLE_QUALITY_SCORES (src/marginalia.py) — every example page scored against docs/example-quality-rubric.md (109 entries). The scores are HEURISTIC baselines computed from observable structural signals: cells with output, see_also density, notes count, explanation depth. Distribution spreads 7.1-9.0 (mean ~8.4), surfacing 12 examples at the 7.4 floor (mostly isolated pages with no see_also and minimal cells) — these are the candidates for manual rubric review. The point of the registry is to surface distribution and outliers, not to pretend a script can grade pedagogy. 3. Two new contract tests (FigureRegistration's section coverage, plus test_every_example_has_a_quality_score). Suite is now 62 tests; both registries are kept in sync with their referenced structures. 4. Hero typography + scroll animation (public/site.css): typeset (per impeccable/typeset): the hero h1 was using the global h1 clamp (max 3.75rem) which dwarfs the 1.08rem body — ~3.5× ratio reads as oversized. Tightened to clamp(2rem, 4vw, 3rem) and brought body to 1rem, giving a ~2.3× modular ratio. Hero padding clamp(5vw, 4rem) → clamp(3.5vw, 2.5rem) so the panel feels less ballroom-scaled. Body max-width 66ch → 60ch. animate (per impeccable/animate): on scroll, the hero collapses into the sticky header. Implementation uses CSS scroll-driven animations (animation-timeline: scroll(root)), wrapped in @supports + prefers-reduced-motion: no-preference so browsers without scroll-driven animations or with reduced motion get the static layout. hero-collapse: scale(1) → scale(0.55) translateY(-32px), opacity 1 → 0, over the first 320px of scroll. header-solidify: bg rgba(245,241,235,0.82) → 0.95 with a light shadow, over the first 240px of scroll. Only transform/opacity for the hero (GPU-accelerated, no layout). Background and box-shadow on header are paint-only.
Previously the hero just scaled-down-and-faded centered. Per the
make-interfaces-feel-better skill, the panel should hand off
visually to the sticky header's brand wordmark — a shared-element
transition that explains where the title GOES, not just that it
disappears.
Four scroll-driven animations, all opacity/transform/filter only:
hero-fade 0-280px .hero opacity 1→0, scale 1→0.92,
translateY 0→-8px (panel dissolves)
hero-h1-morph 0-240px h1 transform-origin top-left,
scale 1→0.32, translate 0→(-32%,-50%)
so it heads toward the top-left
corner where the brand wordmark sits
hero-p-fade 0-140px tagline fades early (the h1 carries
the show on its own past 140px)
brand-reveal 80-240px .brand opacity 0→1, scale 0.88→1,
filter blur(4px)→0. Coming-into-
focus reveal, staggered to start
while the h1 is still en route.
header-solidify 0-240px background 0.82→0.95 + shadow.
Gives the header weight as it takes
over from the hero panel.
Two safety gates from impeccable + make-interfaces:
@supports (animation-timeline: scroll()) — older browsers see
the static layout,
brand stays visible
by default.
@media (prefers-reduced-motion: no-preference) — reduced-motion
users skip it.
Brand hiding is scoped with body:has(.hero) so /examples/<slug>
and /journeys/<slug> pages (no hero) keep their brand visible from
the start — the morph only applies on home.
Per the skill: animations only specify exact properties (opacity,
transform, filter), not `transition: all`. No will-change because
scroll-driven animations are already on the compositor on modern
engines.
62 tests pass; lint clean.
Previously the header strip was always visible: opaque bg, blur,
brand wordmark — even at scroll 0 when the hero was dominant. The
two competed for attention. Now the header strip is invisible to
start (opacity 0, transparent bg) on pages with a hero, and
emerges over the same scroll range the hero is collapsing through.
Header now has its own scroll-driven emergence (replaces
header-solidify):
header-emerge 40-240px opacity 0→1,
background rgba(0.0)→rgba(0.95),
box-shadow none→0 1px 8px (subtle weight)
brand-focus 80-240px filter blur(4px)→0,
transform scale(0.88)→1
(no opacity; inherits from header)
Brand-reveal renamed to brand-focus since it no longer touches
opacity. Header-solidify removed; merged into header-emerge.
Non-home pages (no .hero) keep the header visible from the start —
body:has(.hero) scopes the invisible-by-default rule. Older
browsers without @supports (animation-timeline:scroll()) see the
header visible by default; reduced-motion users likewise.
Restructure the chrome links so the header's nav-links are about
where to go inside the site (Journeys) and external docs sit in
the footer where readers expect references.
- Removed the "Start" link (pointed at hello-world). The home
page already lists every example; the brand wordmark on every
page goes home; "Start" was redundant chrome.
- Removed the "Python 3.13 docs" link from the header and added
it back as a footer line via site-footer-note styling.
- Added a "Journeys" link in the header nav-links span.
Two-link footer + one-link header keeps the chrome minimal and
respects the impeccable layout rule against generic-card-style
nav clutter. 62 tests pass; SEO/cache lint clears 110 pages.
The /journeys hero copy attributes inspiration to Apprenticeship Patterns; wraps the phrase in an outbound link so readers can find the book. https://www.oreilly.com/library/view/apprenticeship-patterns/9780596806842/ Uses .text-link styling (the same accent-underline pattern that the rest of the site uses for outbound text links). No new chrome, no new CSS.
The link goes to a higher-level index page (/), not to a sibling page. ↑ conveys the up-the-hierarchy navigation; ← would mean "previous page in sequence". Three call sites updated: - src/templates/example.html (example page chrome) - src/app.py (journey page chrome) - scripts/build_prototypes.py (prototype example rendering) Left ← arrows kept where they're correct: "← Current layout" on /layout-options/* (side-to-side), and the prev-example link in the example-nav block (sequence navigation).
The unified 780px breakpoint was right for the lp-cell layout (prose 38ch + code stack 72ch ≈ 656 px minimum content) but too narrow for the playground: the editor needs ≥ 18rem (288 px) and the output panel min-height is 18rem too. At 781-980 px viewport the two runner-panel columns each got squeezed to ~150 px wide, which wrapped code into 4-character lines (the user's screenshot showed "missing = None" with every word on its own line). Added a dedicated breakpoint: @media (max-width: 980px) { .runner-grid { grid-template-columns: 1fr; } } Below 980 px viewport the editor and output stack vertically; the editor gets the full column width. Above 980 px the original 1.25fr / 0.75fr split holds. The 780 px cell-collapse breakpoint is unchanged — the cells have different content minima and the right-hand boundary for their collapse is genuinely 780, not 980. Two breakpoints, each fired by its content's actual minimum width, which is the impeccable rule.
Editor and output panels previously had identical chrome — same
dashed border, same background, same h2 — so readers had no visual
cue that one accepts input. Four small differentiations:
* Solid border instead of dashed (.runner-editor only). Dashed
is for scaffolding; solid is for a container that holds input.
* Subtle warmer background (surface-2 instead of surface) so the
editor panel stands out as the active area.
* cursor: text on the editor panel. Hovering anywhere over the
editor's chrome shows the text-cursor — the strongest "click
to edit" affordance browsers offer.
* Hover state: border-color shifts to --accent. Preview of the
focus state before the user commits to clicking.
* Focus-within: border-color --accent + 3px accent glow shadow.
Same shadow the focused textarea/CodeMirror already had, now
lifted to the panel so the whole region reads as active.
* Pencil ✎ glyph before "Example code" in the panel heading,
coloured with --accent. Reinforces the affordance without
needing an icon font.
Output panel keeps the existing dashed/surface style so the
read-only vs editable distinction is one quick glance.
160ms ease-out transition on border-color and box-shadow — matches
the .card and .tool-button transitions elsewhere on the site.
Removed the ✎ pencil glyph (icons aren't part of the site's vocabulary). Replaced it with the 2px accent left-rule the site already uses on .cell-code-stack, .lesson-step pre, and .shiki-block — the visual signature for "live code area". The editor panel is now consistent with the rest of the code chrome: hairline on three sides, orange accent rule on the left. Other cues from the previous commit kept: - background: --surface-2 (lifts the editor over --surface output) - cursor: text on hover (the native edit affordance) - focus-within glow: 3px accent ring (matches textarea/cm-focused) Refinement: dropped the border-color hover state. Cycling a single border colour added a third moving piece for hover/focus/active. Hover now shifts background --surface-2 → --surface-3, which is a quieter and more polished signal that the panel is interactive. Focus is the loud state. Output panel keeps its dashed hairline + --surface, so the editable / read-only contrast still reads at a glance — just through accent rule vs no rule, and surface-2 vs surface, instead of a glyph.
The previous commit added border-left: 2px solid var(--accent) to .runner-editor, reasoning that it reused the "live code area" rule from .cell-code-stack and .lesson-step pre. In practice it was the slop pattern flagged in commit 282554e — "Side-tab accent border: most recognizable tell of AI-generated UIs" — applied for the fourth time on the page. That's not polish; that's the exact anti-pattern the audit named. Three cues remain, each in the design language: - Solid border (inherits the .runner-panel hairline colour and radius; only the border-STYLE changes from dashed to solid). Editable panels are solid; read-only panels are dashed. - Background --surface-2 (vs output's --surface). The lift is subtle but the warmer-than-paper tint reads as "active". - cursor: text on hover — the native edit affordance. Focus state stays as the one loud signal: 3px accent glow ring on focus-within, transitioning over 160ms. No icons, no asymmetric edges, no border-colour cycling between states. The design language is the cue.
The playground section uses h2 "Run the complete example", and
each runner-panel inside it previously used h2 for "Example code"
and "Expected output" — three peers at the same heading level
where the document structure is actually nested:
h2 Run the complete example
├── h2 Example code ← should be h3
└── h2 Expected output ← should be h3
Screen-readers and outline algorithms now read the nesting
correctly. No visible change — .runner-panel h3 inherits the same
1.05rem / -0.02em / hairline underline styling that .runner-panel
h2 had.
Three files touched together so the template, prototype mirror,
CSS rule, and assertion all match:
src/templates/example.html h2 → h3 for Example code and OUTPUT_HEADING
scripts/build_prototypes.py same change in the prototype mirror
public/site.css .runner-panel h2 → .runner-panel h3
tests/test_app.py assertion updated to .runner-panel h3
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.
Per impeccable/layout's "tight grouping 8-12px, generous separation
48-96px" rule. The previous grid-spanning eyebrow inherited the
grid's 16px gap on both sides plus a 24px top margin, so the
eyebrow ended up ~40px from the previous section's cards but only
~16px from its own — the asymmetry was in the right direction but
both numbers were wrong: 16px is too loose for "same group" and
40px is too tight for "new section".
Restructured the home page from one shared .grid with row-spanning
eyebrow rows to one .home-section wrapper per section, each
containing its eyebrow and its own .grid:
<section class="home-section">
<p class="eyebrow">Basics</p>
<div class="grid">…cards…</div>
</section>
<section class="home-section">
<p class="eyebrow">Data Model</p>
<div class="grid">…</div>
</section>
CSS:
.home-section { margin-top: var(--space-6); } ← 48px between sections
.home-section:first-of-type { margin-top: 0; }
.home-section .eyebrow { margin: 0 0 var(--space-2); } ← 12px tight
Net rhythm: 48px between sections (generous) → eyebrow → 12px tight
to its first card row → 16px standard grid gap between subsequent
card rows. The hierarchy now reads as the impeccable rule
recommends: same-group elements are tightly bound, different-group
elements clearly separated.
home.html outer .grid wrapper removed since each section now owns
its grid; .grid-section CSS rule removed since the row-spanning
trick is no longer needed. 62 tests pass.
On home the header is invisible at scroll 0 (per the scroll-driven
hero-collapse animation) but it still occupies its natural flow
space: ~52px header height + 32px header margin-bottom + 24px body
padding-top = ~108px of mostly-invisible chrome before the hero
panel's top edge.
Tightened the two pieces that aren't the header itself:
body:has(.hero) padding-top: var(--space-4) → var(--space-2)
(24px → 12px)
body:has(.hero) header margin-bottom: var(--space-5) → var(--space-2)
(32px → 12px)
Net: dead space above the hero drops from ~108px to ~76px on the
home page only. Other pages keep their original spacing — the rules
are scoped via body:has(.hero) and live inside the
@supports (animation-timeline: scroll()) + prefers-reduced-motion
no-preference gates, so they only apply where the invisible-header
animation runs.
Didn't touch the header's own padding (12px each side of nav) so
the header at its final emerged state stays the same size as
everywhere else; only the OUTSIDE spacing got tighter.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scoring all three rubrics, a scroll-driven hero collapse, nav restructure, section eyebrows on the home page, a more readable code editor, and the polish/spacing details that came out of repeated impeccable reviews.
14 commits since PR #1 was merged. 13 files; +325 / −28 lines net. 62 tests pass, lint clean, SEO/cache lint clears 110 pages.
What ships
Scoring closes the rubric-coverage gap
SECTION_FIGURE_SCORESinsrc/marginalia.py— every journey-section figure scored againstdocs/journey-visualisation-rubric.md(24 entries, mean 8.94).EXAMPLE_QUALITY_SCORES— every example page heuristically scored againstdocs/example-quality-rubric.md(109 entries). Distribution 7.1–9.0 surfaces 14 examples in the 7.x band as rubric-review candidates. Heuristic-only; manual review can refine any entry.Hero collapses into the top-left wordmark on scroll
Five scroll-driven animations, transform/opacity/filter/background only, gated by
@supports (animation-timeline: scroll())+prefers-reduced-motion: no-preference:Hero typography tightened from
clamp(2.4rem, 7vw, 5.75rem)toclamp(2rem, 4vw, 3rem)so the modular ratio with the body paragraph is ~2.3× instead of ~3.5×. Padding clamp tightened in matching proportion.Older browsers and reduced-motion users get the static layout with header visible from the start.
Nav restructure
Python docs+Start→ justJourneys. Brand wordmark goes home;Startwas redundant.Python 3.13 docslink. External references belong here./journeysindex: "Apprenticeship Patterns" wraps an outbound link to the O'Reilly book.Home page split into section blocks
13 sections in pedagogical order, each its own
<section class="home-section">with an eyebrow and its own grid. Resolves slop rule 21 ("identical card grid").Spacing follows the impeccable layout rule "tight grouping 8–12 px, generous separation 48–96 px":
.home-section .eyebrow { margin: 0 0 var(--space-2) }).home-section { margin-top: var(--space-6) }).grid { gap: var(--space-3) })Dead space above the hero on home tightened from ~108 px to ~76 px by reducing body padding-top and header margin-bottom inside the scroll-animation gate.
Editor reads as editable
Six layered affordances, all in the design language, no icons, no asymmetric edges:
--surface-2(vs output's--surface)cursor: texton hover (native edit affordance)box-shadow: inset 0 1px 2px rgba(82, 16, 0, 0.04)(recessed-surface convention for inputs)lineNumbers()extension, gutter styled with--hairline-softright border + muted tabular numeralsResponsive collapse split into two breakpoints
Cells collapse at 780 px (unchanged). Playground panels (
.runner-grid) now collapse at 980 px because the editor + output have stricter content minima (288 px each) — at 781–979 px the columns were ~150 px wide and code was wrapping into 4-character lines.Accessibility nudge
Playground sub-headings demoted from h2 → h3 so the document outline reflects nesting: h2 "Run the complete example" containing h3 "Example code" and h3 "Expected output". CSS rule and test assertion updated to match.
Iteration notes worth flagging
docs/lessons-learned.md.Verification
make verify # 62 tests pass; lint clean; SEO/cache lint clears 110 pages
Preview deploys to
viz-pythonbyexample.adewale-883.workers.devon every push.https://claude.ai/code/session_01MazwoRWAihW6dwso3fMCHE
Generated by Claude Code