Modern Sol package artifacts wired into Rails. Shared tokens, fonts, Tailwind v4 adapter utilities, and session color runtime running against real collection data.
MoMA Sans across six weights. Sol's atom system toggles regular (400)
and bold (900). The other four are available via
@font-face
declarations.
This page now loads the modern Sol core package through the Rails asset pipeline. The type scale, font faces, cap-height metrics, and baseline trimming all come from the shared package stylesheet.
/* Font sizes: mobile defaults, desktop overrides at 768px */ :root { --typography--size--small: 1.4rem; --typography--size--medium: 1.6rem; --typography--size--large: 2.0rem; --typography--size--xl: 2.8rem; --typography--size--hero: 3.6rem; /* Cap height metrics (inherent to MoMA Sans) */ --typography--cap-height: 0.75; /* Sol's baseline leading: 4/3 */ --typography--leading--body: 1.333; } /* Desktop (Sol's $desktop breakpoint) */ @media (min-width: 768px) { :root { --typography--size--hero: 8.0rem; } }
The principle: Sol's extraordinary typographic precision — cap-height calculations, baseline-aligned leading, responsive scaling — lives in the values, not the delivery mechanism. Extract the values; the precision travels.
12 brand colors. 7 are session-eligible (full opacity below). Each visitor gets one randomly on first visit. Click a session color to preview.
Session color is now app-wide, not demo-local. A shared Stimulus controller
persists the chosen key to the current Rails user and updates
<html>
for both the main app shell and this page.
// Shared Sol runtime helpers pick the color number. const colorNumber = getSessionColorNumber(colorKey) // Variables are written once and bridged for legacy app selectors. applySessionColorVariables({ rootElement: document.documentElement, colorNumber }) document.documentElement.style.setProperty("--session-color", `var(--color--brand--${colorNumber})`)
Integration detail:
the shared controller keeps the legacy
--session-color
alias in sync so existing app surfaces and the modern Sol page can run
side by side during migration.
Real artworks from MoMA's 160,000-work collection. Cards, typography, spacing, and accent colors all drawn from Sol's design tokens.
Click a neighbor and it becomes the featured artwork — its own neighbors load with a fade transition. Decade and department pills filter the random selection. Three bands sample from different depths of the 32-neighbor range, with the featured artist excluded from Distant Echoes to guarantee escape velocity.
// Flatten 32 neighbors, sort by similarity, sample 3 depths. // Very Similar: top 3 (same artist is accurate). // Related: middle of range, 1 per artist. // Distant Echoes: bottom, featured artist excluded. const echoPool = all.filter( c => c.artist !== featuredArtist ) // Click a neighbor → instantly swap featured, fade-load new neighbors async focusNeighbor(event) { // 1. Instant: swap featured card from clicked card's data // 2. Fade out neighbor grid (200ms) // 3. Fetch new neighbors for clicked artwork // 4. Fade in }
# Decade + department filters on random selection base = Artwork.where.not(image_url: [nil, ""]) base = base.by_department(department) if department base = base.where("date ~ ?", "\\m#{decade}\\d\\M") if decade base.order("RANDOM()").limit(1).pick(:id)
The pattern:
Backend filters with standard ActiveRecord scopes. Frontend manages
state transitions — fade out, swap, wait for image load, fade in.
Stimulus actions on every control; one
loadArtwork()
method rebuilds the query string from current filter state.
Type a concept and the collection arranges itself along the spectrum from the featured artwork toward your idea. 160,000 works projected onto a line through meaning-space.
The axis projects 160,000 artworks onto a line between two poles: a specific artwork and an abstract concept. Each work gets a position (0–100%) based on its proximity to each pole in 1,536-dimensional embedding space.
// The endpoint does the heavy lifting: // embed the concept, compute axis, project all works. const url = `/explore/axis?id_a=${id} &q_b=${encodeURIComponent(concept)}` // Visualization: stem height encodes distance from center. // Taller stems = closer to poles = stronger signal. const stemHeight = 2 + Math.abs(pos - 0.5) * 8
What this demonstrates:
Design tokens and collection data aren't separate concerns. The same
--color--session
that tints the card borders tints the axis gradient.
The same
sol-card
component renders the axis thumbnails. The design system
becomes a lens for exploring the collection.
Modern Sol now runs inside
moma-browser
as a shared Rails-served foundation rather than a standalone prototype.
sol/core.css
asset imported into the Rails pipeline.
--color--session
and the legacy
--session-color
alias on
<html>
while persisting the selected key to the current Rails user.
as_browser_json
interface across Artwork, Exhibition, Constituent, and ArtTerm lets one
card contract render all four types.