My financial markets report began as a learning project. Over time it became something I open most days—and with that habit came a gentler design problem than “bad data.” The page was faithful: every signal, methodology note, and disclaimer had a reason to exist. On a small screen, though, faithfulness can feel like noise.

One section, Opportunities, accounted for roughly 900 of ~1,120 lines. Eighteen cards that shared the same shape. The content was still useful; the rhythm of the page was tiring.

A fork in the road that looked tempting

Before I touched the HTML generator, I asked a familiar question: should this become a multi-tabbed SPA (single-page app with client-side routing, a component model, and the usual dependency graph)?

Mapping it out helped:

  • What a SPA can add: deep links, reusable components, richer table interactions, motion, and a path toward offline or PWA-style behavior.
  • What it does not change by itself: how fresh the data is. The numbers still come from a Python job in CI. A React shell does not make macro series update sooner.
  • What it asks for: a build step, more packages to tend, and days of scaffolding compared with hours of focused HTML and small JS changes.

I took the lighter path first: tabs and disclosure patterns now, and a backlog note for a possible SPA later—if people actually need interactions this static shape cannot grow into.

That choice is not “anti-framework.” It is preference for evidence: see whether navigation and progressive disclosure help before paying ongoing framework cost.

Six ideas I want to reuse on other dense pages

This work borrowed from terminal-style UX patterns, dashboard guidance, and a lot of quiet time with my own page. Six ideas made the cut:

  1. Data first, explanation second. When a section opens, the chart or table should greet you. Methodology can live in a nested disclosure—not because it is unimportant, but because it is usually not the first question.

  2. Two layers of progressive disclosure. Collapsing whole sections helps once. Tucking explanation inside a section helps again when the section itself is still busy.

  3. A five-second skim. Even when sections are collapsed, the page should still hint at posture and direction. Headers that carry a number or a range tend to read more kindly than titles that only name a topic.

  4. Find the longest stretch of sameness. If one block dominates vertical space, that is often the best return on design attention. Turning eighteen full cards into compact rows that expand did more for perceived length than smaller tweaks elsewhere—and nothing important was removed.

  5. Mobile tab isolation, desktop scroll-through. Below 768px, only the active tab panel shows. On wider screens, panels stay in the document flow for people who like to scroll the whole story. Same markup, a small script, no new dependencies.

  6. Hide rather than delete. Methodology, sources, and disclaimers remain available; they simply step out of the first path through the page.

What shipped

The HTML still comes from one Python module that emits static HTML. I grouped the report into six tab panels (Overview, Markets, Macro, Opportunities, Signals, Crisis), added a tiny helper for consistent <details> / <summary> blocks, and shifted Opportunities from _opp_card() to _opp_row() so each line stays scannable until you want the fuller thesis.

After that: push, CI, Pages. A green run and a live URL were enough to call it a day.

A small reflection

The common reflex is “reach for a framework.” Often that is the right call. For a content-first, CI-generated page where the hard part is data and judgment, static HTML with a little JavaScript can be a deliberate product choice: fewer moving parts between the person maintaining the report and the person reading it—sometimes the same person, half awake, checking from a phone.

If you have a dashboard that feels “too long,” consider a long-section audit before a framework migration. You might find that tabs, disclosures, and a calmer hierarchy were enough breathing room.