Skip to content

Scroll zoom, a tinted first view, the UI review fixes, and a simpler layout - #6

Merged
davidheyman merged 8 commits into
masterfrom
claude/contours-improvements-d2db96
Nov 5, 2026
Merged

davidheyman merged 8 commits into
masterfrom
claude/contours-improvements-d2db96

Conversation

@davidheyman

@davidheyman davidheyman commented Nov 5, 2026 •

Copy link
Copy Markdown
Member

What changed

Mousewheel zoom. Scroll zoom was explicitly off. It is now on, pinned to whole zoom levels (the elevation layer only rasterizes tiles at the map's exact zoom, so a fractional zoom would yield no contours), with a short wheel debounce so one long scroll becomes one zoom and one redraw.

A better first view. The app used to open with hairline brown contours on a cream page under Esri's heavily haloed place labels.

  • The default background is now hypsometric tints.
  • A faint Esri World Hillshade is multiplied over the contour canvas on screen, so it shades the fills instead of being hidden by them. It is a reference layer only: PNG and SVG exports still read the contour data alone. It has its own toggle in the Style panel.
  • The place-label tiles are now Esri's Light Gray Reference layer, which has plain gray labels without the heavy halos. Roads are unchanged.

A simpler layout. The two-level tool rail plus sliding panels was a pattern for many tools and the app has two, so it is gone.

  • Style is a persistent sidebar beside the map on wide screens, open by default, collapsing to nothing with the map taking the space back (a ResizeObserver tells Leaflet, so the contours recompute). Below 720px it is a bottom sheet over the lower half of the map. A floating Style button over the map reopens it; focus moves between that button and the panel's close button as they swap, and Escape closes the panel.
  • Download is a primary button in the header with a menu of GeoJSON, SVG and PNG, each with its one-line description, disabled with a note while nothing can be exported.
  • The Axis Maps credit sits beside the wordmark; on phones it moves to a credit line in the Style panel's footer so the header keeps the search box usable.
  • The Style panel leads with map type and interval; the reference toggles come last as "Map guides".

High-severity UI review fixes

  • The too-many-lines error card no longer hides under an open panel (the sidebar is now in the flow, so the status layer centres over the actual map).
  • The interval field committed on every keystroke, so typing "100" passed through "1", tripped the line cap and wiped the map. NumberField gained a commitOn option; the interval uses "blur", so it applies on Enter or when leaving the field. A failed recompute of the same view keeps the last good contours drawn, and the message names the interval and suggests a round one that would fit (suggestInterval, with tests).
  • Downloads never disabled: GeoJSON and SVG silently did nothing without contours, and PNG saved a blank image after a pan. The hook exposes hasContours and drops the data as soon as the view moves or resizes, which also stops GeoJSON exporting old contours at the new view's coordinates.

Medium-severity UI review fixes

  • Escape closes the panel from anywhere on the page and returns focus; the panel comes before the map in the DOM, so Tab reaches its controls right after the header.
  • Below 720px the panel is a bottom sheet instead of covering the map entirely.
  • Fields render at 16px on narrow touch screens so iOS Safari does not zoom the fixed-height page.
  • Place search shows "No places found" or "Search is unavailable right now" instead of silently closing, and the no-token placeholder reads "Search unavailable".
  • The PNG export is rendered from the contour data at twice the on-screen size (or the display's pixel density if higher) instead of copied from the 1× canvas, with illuminated shadows scaled to match, and its description says it is transparent wherever nothing is filled.
  • Switching ft/m converts the interval to a round number in the new unit (500 ft → 150 m) instead of keeping the digits (convertInterval, with tests).

Low-severity UI review fixes

  • In illuminated mode the "None" background option is labelled "White" with a hint, since the bands are always filled.
  • Only the too-many-lines problem is announced by a live region; the busy card was read out on every pan. The map container reports aria-busy.
  • A global rule disables enter/exit animations under prefers-reduced-motion, covering cards, popovers and tooltips.
  • Dark mode follows the OS preference. A pre-paint script in layout.tsx toggles the theme classes on <html>, color-scheme and the theme-color meta follow, and the reference label tiles are inverted in dark mode so they stay readable. The ~250 lines of dark tokens the theme already shipped are now used.
  • On phones the attribution is one line that expands on tap or focus, with shorter credit strings.

Why

The two requested improvements make the tool feel like a map the moment it loads and let people zoom the way they expect. The layout change removes a level of chrome the app never needed and gives phones the full map width. The review fixes remove the most common ways a first-time visitor could get confused, and make the app work properly with a keyboard and in dark mode.

Notes for review

  • Keep zoomSnap at 1 (the default) if the map options are touched later; see the comment in use-contour-map.ts.
  • The hillshade opacity is a constant, HILLSHADE_OPACITY (0.65).
  • Dark mode is the one change with a visible effect for existing users whose OS is set to dark. If you would rather keep the app light-only, revert the layout.tsx script and the two CSS rules keyed on .dark-mode; everything else stands alone.
  • The sidebar transitions visibility only on the way out. Transitioning it on the way in left the panel computing as hidden at the instant focus moved into it, so the close button could not take focus. Worth knowing if the transition classes are touched.
  • One existing SVG test relied on the old default background and now sets background: "none" explicitly.
  • Verified locally in the dev server: wheel zoom; the default tinted view, no-fill and illuminated modes with the hillshade on and off; sidebar collapse and reopen with the map resizing and recomputing; the Download menu; focus handoff in both directions on desktop and phone; Escape; the interval field's Enter/blur behaviour; the disabled download state; the unit conversion; the phone layout at 375px; and dark mode with readable labels. npm run typecheck, npm run lint and npm test (64 tests) pass.

🤖 Generated with Claude Code

davidheyman and others added 2 commits November 5, 2026 14:57
Scroll-wheel zoom was off. Turn it on, pinned to whole zoom levels
because the elevation layer only rasterizes tiles at the map's exact
zoom, with a short wheel debounce so one long scroll is one zoom.

The first view was hairline brown contours on a cream page under
Esri's heavily haloed place labels. Default the background to
hypsometric tints, multiply a faint Esri World Hillshade over the
contour canvas for orientation (on screen only; exports still read
the canvas alone), and swap the label tiles for Esri's Light Gray
Reference layer. The hillshade gets its own toggle in the Style
panel's Reference section.

Make the SVG index-line test set its background explicitly instead
of relying on the old default.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Three ways a first-time visitor could get confused, found in a UI review:

- The status cards were centred over the whole map container, so with a
  side panel open the error card sat half under it. Shift the status
  layer right by the panel width above 720px, where the panel leaves
  map visible beside it.

- The interval field committed on every keystroke, so typing "100"
  passed through "1", tripped the line cap and wiped the map. Give
  NumberField a commitOn option and use "blur" for the interval, so it
  applies on Enter or when leaving the field. A failed recompute of the
  same view now keeps the last good contours drawn, and the message
  names the interval and suggests a round one that would fit.

- The download buttons never disabled. GeoJSON and SVG silently did
  nothing without contours, and PNG saved a blank image after a pan
  because the canvas is cleared on moveend. Expose hasContours from the
  hook, drop the data as soon as the view moves or resizes (which also
  stops GeoJSON exporting old contours at the new view's coordinates),
  and disable the buttons with an explanation while nothing can be
  exported.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@vercel

vercel Bot commented Nov 5, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
contours Ready Ready Preview Nov 5, 2026 3:59pm UTC

Request Review

- Escape closes the open panel from anywhere on the page and returns
  focus to its rail button. The panels now come before the map in the
  DOM, so Tab reaches their controls right after the rail instead of
  after the map, zoom buttons and attribution links.
- Below 720px the panel is a bottom sheet over the lower half of the
  map instead of covering it entirely, so style changes stay visible.
- The tool rail buttons show captions under their icons and announce
  the panel they control with aria-expanded; the hover-only tooltips
  are gone.
- Fields render at 16px on narrow touch screens so iOS Safari doesn't
  zoom the fixed-height page when one is focused.
- Place search keeps its popover open to say "No places found" or
  "Search is unavailable right now" instead of silently showing
  nothing, and the no-token placeholder no longer talks to the
  deployer.
- The PNG export is rendered from the contour data at twice the
  on-screen size (or the display's pixel density if higher) rather than
  copied from the soft 1x canvas, and its description says it is
  transparent wherever nothing is filled. Illuminated shadows scale
  with the pixel ratio.
- Switching ft/m converts the interval to a nice round number in the
  new unit (500 ft -> 150 m) instead of keeping the digits and tripling
  the line density.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@davidheyman davidheyman changed the title Scroll zoom, a tinted first view, and three UI feedback fixes Scroll zoom, a tinted first view, and the UI review fixes Nov 5, 2026
davidheyman and others added 3 commits November 5, 2026 15:26
- In illuminated mode the "None" background option is labelled
  "White", with a hint that the bands are always filled, since that is
  what it draws.
- Only the too-many-lines problem is in a live region now; the busy
  card was announced on every pan. The map container reports
  aria-busy instead.
- One global rule turns off tailwindcss-animate enter and exit
  animations under prefers-reduced-motion, covering the status cards,
  popovers and tooltips.
- Dark mode follows the OS preference: a pre-paint script toggles the
  theme classes on <html>, color-scheme and the theme-color meta follow
  it, and the reference label tiles are inverted in dark mode so they
  stay readable.
- On phones the app name stays visible, the Axis Maps link is a 40px
  target, and the attribution is one line that expands on tap or
  focus. The credit strings are shorter and the attribution control
  uses a plain Leaflet prefix.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…rovements-d2db96

# Conflicts:
#	src/app/layout.tsx
The two-level rail-plus-panel layout was built for many tools and had
two. It cost 64px of width, added a click before anything happened, and
on phones took a sixth of the screen.

- Style is now a persistent sidebar beside the map on wide screens, open
  by default and collapsing to nothing; the map is a flex sibling that
  gets the space back, and a ResizeObserver tells Leaflet about the new
  size so the contours recompute. Below 720px it stays a bottom sheet.
  A floating Style button over the map reopens it. Focus moves between
  that button and the panel's close button as they swap, after the
  press has finished, and Escape still closes the panel.
- Download is a primary button in the header with a menu of the three
  formats, each with its one-line description, disabled with a note
  while nothing can be exported. The Axis Maps credit moves beside the
  wordmark, hidden on phones where a credit line in the Style panel's
  footer takes over.
- The Style panel leads with map type and interval; the reference
  toggles are last, as "Map guides".
- The sidebar transitions visibility only on the way out. Transitioning
  it on the way in left the panel computing as hidden at the instant
  focus moved into it.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@davidheyman davidheyman changed the title Scroll zoom, a tinted first view, and the UI review fixes Scroll zoom, a tinted first view, the UI review fixes, and a simpler layout Nov 5, 2026
davidheyman and others added 2 commits November 5, 2026 15:56
More padding around and inside the items, larger gaps, and no height
cap on its popover, which the extra padding had pushed it past.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
The Low/High and Deep/Shallow fields share a row, and at 12px side
padding the last character of a seven-character hex was clipped. Use
8px in the color fields.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@davidheyman
davidheyman merged commit f8458a8 into master Nov 5, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant