Files
bucketeer/docs/DESIGN.md
T
StasonJatham fac79b6215 professionalize repo for public release
- rename ctcloud -> bucketeer everywhere (cmd/bucketeer, binary, compose service)
- group brand assets under assets/ (light/dark logo + UI screenshot)
- move long-form docs under docs/ (ARCHITECTURE, DESIGN, GOAL) and add
  docs/SKILL.md: how an agent queries/audits a running instance and answers
  user questions from the API and PostgreSQL
- README: large logo (light/dark picture) and UI screenshot, repository layout
- .gitignore: /bucketeer, .env, pgdata, build/test artifacts, OS junk
- .dockerignore: keep brand/docs/markdown out of the build context
2026-09-22 17:48:30 +02:00

737 lines
44 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
version: alpha
name: Ollama-inspired Web App
description: A light, content-first web application system informed by Ollama home and model search. Tokens are implementation targets; provenance
and unverified values are explicitly documented in Overview. Not an official or pixel-exact extraction.
colors:
primary: '#262626'
on-primary: '#ffffff'
primary-hover: '#404040'
primary-active: '#171717'
text-primary: '#000000'
text-secondary: '#525252'
text-muted: '#737373'
canvas: '#ffffff'
surface-subtle: '#fafafa'
surface-control: '#f2f2f2'
surface-hover: '#ededed'
border: '#e5e5e5'
border-strong: '#d4d4d4'
focus: '#2563eb'
capability-background: '#eef2ff'
capability-text: '#4338ca'
size-background: '#e0f2fe'
size-text: '#075985'
error-background: '#fef2f2'
error-text: '#b91c1c'
success-background: '#f0fdf4'
success-text: '#166534'
typography:
display-desktop:
fontFamily: '"SF Pro Rounded", ui-rounded, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
fontSize: 48px
fontWeight: 500
lineHeight: 52px
letterSpacing: -0.02em
display-mobile:
fontFamily: '"SF Pro Rounded", ui-rounded, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
fontSize: 36px
fontWeight: 500
lineHeight: 40px
letterSpacing: -0.015em
section-heading:
fontFamily: '"SF Pro Rounded", ui-rounded, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
fontSize: 32px
fontWeight: 500
lineHeight: 40px
letterSpacing: -0.015em
section-heading-mobile:
fontFamily: '"SF Pro Rounded", ui-rounded, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif'
fontSize: 28px
fontWeight: 500
lineHeight: 36px
letterSpacing: -0.01em
page-heading:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 24px
fontWeight: 500
lineHeight: 32px
letterSpacing: -0.01em
result-title:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 20px
fontWeight: 500
lineHeight: 28px
letterSpacing: 0em
body-lg:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 18px
fontWeight: 400
lineHeight: 28px
letterSpacing: 0em
body-md:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 16px
fontWeight: 400
lineHeight: 24px
letterSpacing: 0em
body-sm:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 14px
fontWeight: 400
lineHeight: 20px
letterSpacing: 0em
nav-link:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 18px
fontWeight: 400
lineHeight: 28px
letterSpacing: 0em
button-lg:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 18px
fontWeight: 500
lineHeight: 28px
letterSpacing: 0em
button-md:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 14px
fontWeight: 500
lineHeight: 20px
letterSpacing: 0em
filter-label:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 14px
fontWeight: 400
lineHeight: 20px
letterSpacing: 0em
badge:
fontFamily: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif, "Apple Color Emoji", "Segoe UI Emoji"
fontSize: 12px
fontWeight: 500
lineHeight: 16px
letterSpacing: 0em
code:
fontFamily: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", monospace
fontSize: 14px
fontWeight: 400
lineHeight: 20px
letterSpacing: 0em
rounded:
none: 0px
tag: 4px
sm: 6px
md: 8px
panel: 12px
full: 9999px
spacing:
hairline: 1px
micro: 2px
xs: 4px
tag-gap: 6px
sm: 8px
input-y: 10px
md: 12px
lg: 16px
xl: 24px
2xl: 32px
3xl: 40px
4xl: 48px
5xl: 64px
6xl: 80px
7xl: 96px
layout-site-max: 1280px
layout-section-max: 1024px
layout-search-max: 768px
layout-hero-max: 640px
layout-prose-max: 576px
components:
page:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.body-md}'
header:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
height: 72px
navigation-link:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.nav-link}'
button-primary:
backgroundColor: '{colors.primary}'
textColor: '{colors.on-primary}'
typography: '{typography.button-md}'
rounded: '{rounded.full}'
height: 40px
button-primary-hover:
backgroundColor: '{colors.primary-hover}'
textColor: '{colors.on-primary}'
typography: '{typography.button-md}'
rounded: '{rounded.full}'
button-primary-active:
backgroundColor: '{colors.primary-active}'
textColor: '{colors.on-primary}'
typography: '{typography.button-md}'
rounded: '{rounded.full}'
button-header:
backgroundColor: '{colors.primary}'
textColor: '{colors.on-primary}'
typography: '{typography.nav-link}'
rounded: '{rounded.full}'
height: 40px
button-hero:
backgroundColor: '{colors.primary}'
textColor: '{colors.on-primary}'
typography: '{typography.button-lg}'
rounded: '{rounded.full}'
height: 52px
button-secondary:
backgroundColor: '{colors.surface-control}'
textColor: '{colors.text-primary}'
typography: '{typography.button-md}'
rounded: '{rounded.full}'
height: 40px
button-secondary-hover:
backgroundColor: '{colors.surface-hover}'
textColor: '{colors.text-primary}'
typography: '{typography.button-md}'
rounded: '{rounded.full}'
button-outline:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.button-md}'
rounded: '{rounded.full}'
height: 40px
button-disabled:
backgroundColor: '{colors.surface-subtle}'
textColor: '{colors.text-muted}'
typography: '{typography.button-md}'
rounded: '{rounded.full}'
icon-button:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-secondary}'
rounded: '{rounded.full}'
size: 40px
header-search:
backgroundColor: '{colors.surface-control}'
textColor: '{colors.text-primary}'
typography: '{typography.body-sm}'
rounded: '{rounded.full}'
height: 40px
query-search:
backgroundColor: '{colors.surface-control}'
textColor: '{colors.text-primary}'
typography: '{typography.body-md}'
rounded: '{rounded.full}'
height: 48px
query-search-focused:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.body-md}'
rounded: '{rounded.full}'
search-placeholder:
backgroundColor: '{colors.surface-control}'
textColor: '{colors.text-secondary}'
typography: '{typography.body-sm}'
text-input:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.body-md}'
rounded: '{rounded.md}'
height: 44px
sort-select:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.body-sm}'
rounded: '{rounded.full}'
height: 40px
filter-chip:
backgroundColor: '{colors.surface-control}'
textColor: '{colors.text-secondary}'
typography: '{typography.filter-label}'
rounded: '{rounded.full}'
height: 32px
filter-chip-hover:
backgroundColor: '{colors.surface-hover}'
textColor: '{colors.text-primary}'
typography: '{typography.filter-label}'
rounded: '{rounded.full}'
filter-chip-selected:
backgroundColor: '{colors.primary}'
textColor: '{colors.on-primary}'
typography: '{typography.filter-label}'
rounded: '{rounded.full}'
height: 32px
result-row:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
rounded: '{rounded.none}'
result-row-hover:
backgroundColor: '{colors.surface-subtle}'
textColor: '{colors.text-primary}'
rounded: '{rounded.none}'
result-title:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.result-title}'
result-description:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-secondary}'
typography: '{typography.body-md}'
result-metadata:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-muted}'
typography: '{typography.body-sm}'
badge-capability:
backgroundColor: '{colors.capability-background}'
textColor: '{colors.capability-text}'
typography: '{typography.badge}'
rounded: '{rounded.tag}'
badge-size:
backgroundColor: '{colors.size-background}'
textColor: '{colors.size-text}'
typography: '{typography.badge}'
rounded: '{rounded.tag}'
badge-neutral:
backgroundColor: '{colors.surface-control}'
textColor: '{colors.text-secondary}'
typography: '{typography.badge}'
rounded: '{rounded.tag}'
feature-panel:
backgroundColor: '{colors.surface-subtle}'
textColor: '{colors.text-primary}'
rounded: '{rounded.panel}'
padding: 24px
popover:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-primary}'
typography: '{typography.body-sm}'
rounded: '{rounded.md}'
padding: 8px
empty-state:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-secondary}'
typography: '{typography.body-md}'
padding: 32px
skeleton:
backgroundColor: '{colors.surface-control}'
rounded: '{rounded.sm}'
notice-error:
backgroundColor: '{colors.error-background}'
textColor: '{colors.error-text}'
typography: '{typography.body-sm}'
rounded: '{rounded.md}'
padding: 12px
notice-success:
backgroundColor: '{colors.success-background}'
textColor: '{colors.success-text}'
typography: '{typography.body-sm}'
rounded: '{rounded.md}'
padding: 12px
code-snippet:
backgroundColor: '{colors.surface-subtle}'
textColor: '{colors.text-primary}'
typography: '{typography.code}'
rounded: '{rounded.md}'
padding: 12px
footer:
backgroundColor: '{colors.canvas}'
textColor: '{colors.text-muted}'
typography: '{typography.body-sm}'
divider:
backgroundColor: '{colors.border}'
height: 1px
---
# Ollama-inspired web application design system
## Overview
### Purpose
Build a web application with a quiet, developer-oriented interface: a white canvas, dark text, restrained controls, generous outer whitespace, and readable resource lists. Borrow the reference's visual language, not its logo, product claims, or model catalog.
Use two related compositions: a spacious **home/overview page** and a compact **search/discovery page**. Keep the same typography, navigation, buttons, and neutral palette across both. Do not turn the discovery page into a marketing page or a dashboard full of elevated cards.
This document follows Google's DESIGN.md alpha format: YAML design tokens followed by the eight canonical prose sections. Token names and values are normative **for the application being built**, not a claim that Ollama publishes these tokens. [S3]
### Evidence boundary — read before implementation
**Prepared:** 2026-09-21. **Requested references:** Ollama home and `/search?q=qwen`.
The current pages were readable as extracted web content. Their live stylesheets, browser-computed styles, desktop/mobile screenshots, and interaction states could not be captured in this environment. A separate author's July 2026 measurement report was available for the homepage and pricing surfaces. That report does not cover the requested search page. Consequently, this is a **reference-informed implementation specification, not a pixel-exact live CSS extraction**.
Use these provenance labels throughout:
| Label | Meaning | What it establishes |
| --- | --- | --- |
| **C — Current content** | Page content inspected on 2026-09-21 | Information hierarchy and available content/control types; not CSS or geometry. |
| **H — Historical report** | Measurements reported by another author on 2026-07-13 | A dated reference value; not independently reproduced here and not proof of today's rendering. |
| **R — Recommendation** | A deliberate application design decision | A concrete value or behavior to implement, pending visual comparison. |
**Default rule:** every token and behavior is **R** unless the table below explicitly identifies its historical basis. Where an H value is reused, it becomes an implementation target; its provenance does not become current.
| Item | Reported evidence / treatment | Status |
| --- | --- | --- |
| Header download control | `#262626` background, white text, `9999px` radius, `6px 16px` padding, `18px` text at weight `400` | H [S4] |
| Hero action | Same dark surface and pill radius; `12px 32px` padding; `18px`, weight `500` | H [S4] |
| Header sign-in control | `rgba(0,0,0,0.05)` background, black text, `6px 16px` padding, `18px` text | H [S4] |
| Product search input | Transparent input inside its container; pill radius; `10px 12px` padding; `14px/20px` typography | H [S4] |
| Outlined action | White surface, `#d4d4d4` 1px outline, pill radius, `8px 24px` padding; sampled on the historical home/pricing composition | H [S4] |
| Font families | System sans and system monospace stacks reported; a heading declaration containing `SF Pro Rounded` was reported, but no corresponding loaded font/source was established | H; exact rendered heading face unresolved [S4] |
| `surface-control: #f2f2f2` | Normalized approximation of 5% black composited over white; not the original RGBA declaration | R derived from H |
| All heading sizes, row spacing, widths, breakpoints, badge colors, and unobserved states | Chosen for a coherent, accessible implementation | R |
The measured historical input was transparent. The YAML `header-search` component describes the **outer search wrapper**, with a transparent input inside it; it does not claim that the sampled input itself had a gray fill.
### What the current references establish
**Home — C:** the current content proceeds from a short product proposition and download action to developer/social proof, performance evidence, model comparisons, integrations, privacy information, usage/pricing information, and a closing action. These are content motifs, not verified measurements or layouts. Do not reproduce an older terminal-install hero as though it were the current homepage. [S1]
**Search — C:** the current page provides a model query input, a sort control, five capability filters, and a linked sequence of results. Each result carries a name, short description, capability/size labels, and popularity/version/recency metadata. The selected query is `qwen`. The filters cover cloud availability, embeddings, vision, tools, and thinking. Current result names and counts are data, not design tokens. [S2]
### Design priorities
Content and whitespace carry the hierarchy. The largest contrast belongs to the page title and primary action; the smallest contrast belongs to metadata and dividers. Search should feel like browsing a well-maintained software registry, not browsing a card-based storefront. Keep personality in the heading treatment and the application's own small brand mark.
### Sources
- **[S1]** Ollama homepage; current extracted content inspected 2026-09-21. `https://ollama.com/`
- **[S2]** Ollama model search; current extracted content inspected 2026-09-21. `https://ollama.com/search?q=qwen`
- **[S3]** Google Labs DESIGN.md specification and README; inspected 2026-09-21. `https://github.com/google-labs-code/design.md/blob/main/docs/spec.md` and `https://github.com/google-labs-code/design.md/blob/main/README.md`. Retrieved spec file blob SHA: `5995e5482fc338a9f9923d5912551241a82e6e8a`.
- **[S4]** Original measurement report by the oh-my-design project, dated 2026-07-13; not an official Ollama design system. `https://github.com/kwakseongjae/oh-my-design/blob/main/web/references/ollama/.verification.md`. Retrieved file blob SHA: `f06f5515f3d3af93d894c91acf5ea8cf8a9aa919`. The referenced raw collector bundle was unavailable; only the report was inspected.
- **[S5]** W3C, WCAG 2.2 Understanding SC 1.4.3, text contrast. `https://www.w3.org/WAI/WCAG22/Understanding/contrast-minimum.html`
- **[S6]** W3C, WCAG 2.2 Understanding SC 2.5.8, target size. `https://www.w3.org/WAI/WCAG22/Understanding/target-size-minimum.html`
- **[S7]** MDN, CSS font-family and computed-style behavior. `https://developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/font-family` and `https://developer.mozilla.org/en-US/docs/Web/API/Window/getComputedStyle`
## Colors
### Neutral foundation
Use `{colors.canvas}` for the page and default content surfaces. Use `{colors.text-primary}` for headings and names, `{colors.text-secondary}` for descriptions, and `{colors.text-muted}` for timestamps and supporting statistics. Distinguish hierarchy primarily through size, weight, spacing, and text tone.
The primary action uses `{colors.primary}` with `{colors.on-primary}`. That slightly softened black is based on the historical control measurements, rather than the assumption that every dark control must be pure black. [S4]
Use `{colors.surface-control}` for low-emphasis controls and search wrappers. `{colors.surface-subtle}` is reserved for a quiet hover, code surface, or supporting panel. Hairlines use `{colors.border}`; outlined controls may use `{colors.border-strong}`. Neither pale border is sufficient on its own to communicate an essential control boundary: retain a recognizable shape and label, or strengthen the border for that use case.
### Small semantic accents — R
The app is neutral-first, **not literally color-free**. Use muted indigo for capability badges and pale blue for size/version badges. These exact badge colors are proposed, not captured from the current search page. Small colored labels must not become large colored sections or colored primary buttons.
Use error and success pairs only for real status messages. Include a textual label or icon so color is never the sole status signal. Do not color an entire result row by capability.
### Contrast rules — R
Normal text must meet at least **4.5:1** against its actual background. Large text has a **3:1** minimum under WCAG's large-text definition. Placeholder text is not exempt. The token pairs are checked numerically in the accompanying validation report; a passing palette does not establish page-level accessibility. [S5]
Do not lower an entire result row's opacity to create hierarchy. That also lowers contrast on its text and controls. Use the dedicated text tones instead. Do not use `#a3a3a3` for important small text on white.
Dark mode is intentionally **not specified**. Do not invent one and label it as extracted from these references. A future dark palette should be designed and tested separately.
## Typography
### Font stacks
Use the system sans stack for body text, navigation, inputs, metadata, and result titles. Use the display stack only for the home hero and marketing section headings. Reserve monospace for actual commands, identifiers requiring character alignment, and code; model/resource names and all metadata do not automatically become monospace.
The display stack begins with `"SF Pro Rounded"`, followed by `ui-rounded` and system fallbacks. This is an application choice informed by the historical CSS declaration, **not proof that a particular font file loaded on Ollama**. A computed `font-family` value is a fallback list, not a rendered-font report; platform availability can change the resulting face. [S4][S7]
Use locally available fonts and fallbacks. Do not download or bundle Apple's font files, and do not add an external font service as a hidden dependency. Expect platform-specific glyph widths and test line wrapping on the supported operating systems.
### Type hierarchy — R unless noted
| Role | Desktop | Small screens | Weight | Use |
| --- | --- | --- | --- | --- |
| Home display | 48px / 52px | 36px / 40px | 500 | The single main product proposition. |
| Section heading | 32px / 40px | 28px / 36px | 500 | Home content sections. |
| App page heading | 24px / 32px | Same | 500 | Non-marketing application views. |
| Result title | 20px / 28px | Same | 500 | The primary link in each result. |
| Lead / navigation | 18px / 28px | Lead can become 16px / 24px | 400 | Introductory copy and desktop navigation. |
| Body | 16px / 24px | Same | 400 | Descriptions and form values. |
| Supporting text | 14px / 20px | Same | 400 | Metadata, sort labels, footer. |
| Badge | 12px / 16px | Same | 500 | Short, non-paragraph labels. |
| Code | 14px / 20px | Same | 400 | Commands and technical snippets. |
The header and hero action's 18px sizes and weights have historical support; their 28px line-height and resulting target heights are implementation choices. All display sizes above are recommendations. Do not substitute the historically reported **pricing-page** H1 measurement for a current homepage H1 measurement.
Keep letter spacing normal for controls and body text. Slightly tighten large headings using the provided display tokens. Avoid uppercase navigation, heavy 700–900 headings, widely tracked labels, and giant 80–120px marketing headlines. Allow titles to wrap; do not truncate a meaningful resource name just to preserve a fixed row height.
## Layout
### Sizing model — R
Use `box-sizing: border-box`. All stated widths are **maximum widths**, never fixed widths that force overflow. Use a 4px-oriented rhythm with explicit 2px, 6px, and 10px micro adjustments for labels and controls.
| Region | Maximum width | Alignment / purpose |
| --- | --- | --- |
| Site/navigation shell | 1280px | Centered in the viewport. |
| Broad home section | 1024px | Proof, comparison, or integration content. |
| Search/results column | 768px | A readable single-column directory. |
| Home hero | 640px | Centered headline and primary action. |
| Introductory prose | 576px | Prevent overly long lines. |
Outer page gutters: 16px below 640px, 24px from 640px, and 32px from 1024px. The widths and breakpoints are proposed implementation values, not measurements from Ollama.
### Shared shell
Use a minimum 72px header on desktop with the application's compact brand mark at the left. Place a small set of navigation links beside it, an optional flexible global search region, and account/primary-action controls at the right. Leave 24px between major header groups and 16px between related controls.
The header sits on the same white canvas as the page. Do not add a dark bar, persistent left sidebar, or large drop shadow. Default to a normal-flow header. Sticky navigation is a product decision and requires separate overlap/focus testing.
On the discovery route, the main query field is authoritative. Avoid displaying two equally prominent search inputs. Hide the global header search or make its relationship to the query field unambiguous.
### Home / overview composition
The content sequence is informed by the current homepage; the geometry below is R. [S1]
1. **Hero:** 80–96px top space after the header on desktop, 48px on small screens. Center the headline, a short supporting paragraph 16–24px below it, and a primary action 24px below the paragraph. Keep the hero compact enough that useful content can appear within the first screen.
2. **Proof and explanation:** add a restrained proof row or compact metrics, followed by content sections spaced 64–96px apart. Use a two-column text/visual layout only when the content benefits from it; stack below 1024px.
3. **Closing action and footer:** repeat the primary action after the explanatory content, then provide a compact footer. Do not fill the home page with identical CTA buttons.
Use the application's actual features and evidence. Performance comparisons, logo rows, integrations, and usage displays are optional content patterns, not permission to copy Ollama's claims or invent customer logos. A command snippet belongs here only when a real installation workflow needs it.
### Search / discovery composition
Start the search controls about 48px below the header on desktop and 24–32px below it on small screens. Do not insert a large hero, oversized page title, or illustration above the query field.
```text
Shared header
Search/discovery column — max 768px
┌──────────────────────────────────┐
│ Search query × │
└──────────────────────────────────┘
[Filter] [Filter] [Filter] [Sort ▾]
Resource name
Short explanatory description, wrapping naturally.
[capability] [capability] [size] [size]
↓ popularity tags/versions updated recently
────────────────────────────────────────────────
Resource name
...
Load more / pagination, only when appropriate
```
This wireframe is a proposed arrangement of the observed search ingredients, **not a captured source screenshot**. Start with 16px between query and filter controls, 24px before the first result, and 24px vertical padding per result. Within a result, use 4–8px between title and description, 8px before badges, and 12px before metadata.
Keep results in one column at every viewport size. Use whitespace and optional 1px separators, not individual floating cards. Result height is content-driven; long names, multiple badges, and translated descriptions must expand the row.
### Responsive behavior — R
| Viewport / condition | Required behavior |
| --- | --- |
| Below 640px | 16px gutters; compact header; main search on its own line; filters wrap; sort can move to a separate line; metadata wraps; 36px home display. |
| 640–1023px | 24px gutters; preserve the single results column; show navigation only when it fits; home sections remain stacked by default. |
| 1024px and above | 32px gutters; full navigation; optional centered global search; two-column home sections where useful. |
| Coarse pointer | Increase interactive chip and icon-button targets to at least 44px, without overlapping adjacent targets. |
| 200% zoom / narrow effective viewport | Reflow rather than clip. No fixed row heights or hard-coded single-line headers. |
Do not hide filtering or sorting functionality on mobile. Prefer wrapping filters over an invisible horizontal scroll area. Where a real table cannot reflow, give only that table a labeled scroll region; the whole page must not scroll horizontally.
### CSS layout recipe
The companion `design-tokens.css` defines the custom properties used below. These classes are implementation examples, not source CSS.
```css
*, *::before, *::after { box-sizing: border-box; }
body { margin: 0; color: var(--color-text-primary); background: var(--color-canvas); }
:root { --page-gutter: 16px; }
.site-shell, .home-section, .search-layout, .hero {
width: calc(100% - 2 * var(--page-gutter));
margin-inline: auto;
}
.site-shell { max-width: var(--spacing-layout-site-max); }
.home-section { max-width: var(--spacing-layout-section-max); }
.search-layout { max-width: var(--spacing-layout-search-max); }
.hero { max-width: var(--spacing-layout-hero-max); text-align: center; }
.result-list { list-style: none; padding: 0; margin: 0; }
.result-row { padding-block: 24px; border-bottom: 1px solid var(--color-border); }
.badge-group, .result-meta, .filter-group { display: flex; flex-wrap: wrap; }
.badge-group { gap: 6px; }
.result-meta { gap: 8px 16px; }
.filter-group { gap: 8px; }
@media (min-width: 640px) { :root { --page-gutter: 24px; } }
@media (min-width: 1024px) { :root { --page-gutter: 32px; } }
```
Apply these width utilities to top-level regions. Do not nest two gutter-bearing shells and unintentionally double the side padding. Use flex children with `min-width: 0` where long content must wrap.
## Elevation & Depth
For this app, default to **no shadow** on page regions, buttons, search fields, and result rows. Build hierarchy through whitespace, text contrast, subtle fills, and hairlines. This is an intentional recommendation, not a verified statement that the reference never uses shadows.
A floating menu may use `0 4px 16px rgba(0,0,0,0.08)` with a 1px border. Do not use the menu shadow on cards or every control. Keep a simple layering order: normal content `0`, optional sticky chrome `10`, popovers `20`, dialogs `40`. These are proposed values.
Use 120ms color/border transitions with an `ease-out` timing curve. Never scale result rows or move buttons on hover. Avoid animated gradients, background blobs, glass effects, large blurred shadows, and scroll-triggered entrance animation. Respect reduced-motion preferences; loading skeletons can be static.
## Shapes
Use pill geometry for primary/secondary buttons, global search wrappers, query fields, sort controls, and interactive filter chips. The historical source supports pill-shaped header actions and the product input. [S4]
Use **4px corners for compact metadata badges**, 8px for generic form fields/popovers, and 12px for occasional explanatory panels. These are app recommendations; do not infer that every element needs a full pill radius. Result rows themselves remain flat and unrounded.
Icons should be simple, monochrome line icons using `currentColor`, normally 16px next to metadata and 18–20px inside controls. Use consistent stroke weight around 1.5–2px. The app's brand mark may have its own geometry; do not reuse or redraw the Ollama mascot as the application's identity.
Use 1px separators. Avoid thick card outlines, corner radii above 12px on ordinary panels, decorative cutouts, tilted cards, and oversized circular icon backgrounds.
## Components
All behavior in this section is R unless explicitly identified as H. The YAML intentionally uses the DESIGN.md specification's standard component properties. Directional padding, borders, max-widths, focus, and interaction semantics are specified here rather than hidden in unsupported token fields. [S3]
### Header and navigation
Use a semantic `header` and `nav`. Suggested application slots are brand, browse, documentation, optional pricing, account, and one primary action. These are slots to adapt, not a verified inventory of today's Ollama header. Mark the active destination with a restrained underline or weight change and `aria-current="page"`.
On small screens, use an accessible menu button when links no longer fit. It must expose its expanded state, provide predictable keyboard behavior, and return focus when closed. Keep the main content reachable through a skip link.
### Buttons
| Variant | Geometry and padding | Appearance / role |
| --- | --- | --- |
| Header primary | Target 40px; `6px 16px`; 18px text | Historical padding, size, and color basis; primary navigation action. |
| Hero primary | Target 52px; `12px 32px`; 18px medium text | Historical padding, size, and color basis; central home action. |
| App primary | Min 40px; `10px 20px`; 14px medium text | Dark pill; main action in a form or workflow. |
| Secondary | Min 40px; `10px 16px` | Light gray pill; lower-emphasis action. |
| Outline | Min 40px; `9px 20px` plus 1px border | White with a quiet outline; secondary standalone action. |
| Icon only | Min 40px square; 44px for coarse pointers | Transparent or subtle background; accessible name required. |
These are minimum heights, not clipping boxes. Let translated labels or user text scaling increase the height. Use links for navigation and buttons for actions. Disabled controls must really be disabled, not just gray. Loading retains the action label and dimensions; reserve space for its indicator.
For primary hover and pressed states, use the YAML variants. Secondary hover darkens its neutral surface slightly. Do not change corner radius or geometry between states.
### Search fields
The **header-search** wrapper is 40px tall with a transparent inner input. The historical input had `14px/20px` text and `10px 12px` padding; increase left padding to about 36–40px only when a leading search icon occupies that space. [S4]
The **query-search** is the more prominent 48px field in the discovery column. Use 16px text, a 20px leading icon, roughly 16px external horizontal padding, and reserved trailing space for a clear button. The placeholder is a hint, not the accessible label. A real visible or visually hidden label is required.
In the focused state, use a white surface, a clear border, and the focus outline described below. Do not show a redundant outline around both wrapper and input. For a clear control, preserve input focus after clearing. Escape can dismiss suggestions; do not assign it to unexpected navigation.
Persist the query in `?q=`. Enter submits. Optional live search uses a proposed 200–300ms debounce, aborts stale requests, and never overwrites newer results with an older response. IME composition must finish before submitting. These are recommended application behaviors, not observations of Ollama's implementation.
### Sort control and popover
Use a labeled native select by default, approximately 40px high. Preserve its native keyboard semantics. A custom menu is justified only when the product needs richer options and implements equivalent accessibility.
Keep the selected sort visible. Proposed options are relevance, popularity, and recently updated; adapt them to actual data. Do not pretend these are the source page's exact options. Update URL state alongside query/filter state and preserve it when navigating back.
### Filter chips
Use 32px visual height with `6px 12px` padding, 14px text, and 8px gaps. Selected chips become dark with white text; a checkmark or equivalent state cue should reinforce selection. On coarse pointers, increase the actual target to 44px rather than extending invisible hit areas into neighboring controls.
Use actual checkboxes with styled labels for independently selectable filters, or buttons with `aria-pressed` for toggle buttons. Define whether multiple selections are combined with AND or OR and communicate that rule. Do not use `role="tab"` for filters that do not switch between tab panels. Offer a quiet clear-filters action when selections are active.
### Result rows
Each result contains, in order, a linked resource title, short description, a wrapping badge group, and a wrapping metadata line. This information pattern is supported by the current search content; its exact spacing and typography here are R. [S2]
Keep titles black rather than turning the page into a column of saturated blue links. On pointer hover, underline the title and optionally apply the subtle row fill. Keyboard focus must remain visible independently of hover.
Use a semantic list. For a completely navigational row, a single block link can contain its non-interactive contents. When a row needs menus, favorite buttons, or other controls, keep the title as the link and place those controls as siblings. Never nest buttons or another anchor inside a whole-row anchor.
Do not impose a fixed height. Avoid truncating descriptions by default; a genuinely long description can have a deliberate, accessible expansion pattern. Long unbroken identifiers should wrap using `overflow-wrap: anywhere` where necessary, not force horizontal scrolling.
### Badges and metadata
Badges use 12px/16px medium text, `2px 6px` padding, and a 4px radius. Use indigo for capabilities, blue for size/version labels, and neutral gray for general categories. The **existence** of capability/size labels is C; these exact colors, radii, and sizes are R. [S2]
Keep non-interactive badges non-interactive: no pointer cursor, false focusability, or button-like hover. When a badge really is a filter action, expose that as a genuine interactive control with an adequate target instead of silently converting all labels into links.
Metadata uses 14px/20px muted text and 16px icons, with approximately 16px between groups. Provide understandable labels for counts; do not rely on an icon alone. Display relative time with access to the full date, and use `<time datetime="...">` where appropriate. Missing metadata is omitted or labeled unavailable, never replaced with invented counts.
### Home evidence panels
Use panels sparingly for real comparisons, code snippets, integration examples, or usage previews. Prefer 24px internal padding, 12px radius, a light neutral surface, and no shadow. A panel is an explanation container, not the default wrapper for every paragraph.
For charts, provide visible units and an accessible summary. Real comparisons need a source and date. Do not copy reference benchmark data, customer marks, or pricing values into the application as decorative fixtures. Avoid an inverted premium-pricing card unless the actual product has a reason for one.
### Loading, empty, error, and success states
A loading view uses a few restrained skeleton rows with the same column width and basic rhythm as loaded results. Keep the search field and filters usable where possible; preserve existing results while a new query is pending when this avoids a blank flash.
An empty view states that no items matched, retains the query, and offers clear-filters or clear-query controls. A network failure is not an empty result: show an inline error with retry and keep user input intact. Announce relevant result updates through a polite live region without reading the entire list repeatedly.
Success/error notices use the provided semantic pairs and a textual message. Do not use global toast messages for every filter change. Generic dialogs and full data-table systems are outside the extracted reference scope; derive them separately if the app needs them.
### Focus and accessibility
Use a visible 2px focus outline in `{colors.focus}`, offset by 3px, for keyboard-operable controls and links. This is an app accessibility rule, not a captured Ollama focus state. Never remove the browser outline without supplying an effective replacement.
Use at least 44px pointer targets for key mobile controls as an application goal. Do not call 44px the WCAG 2.2 AA minimum: SC 2.5.8 generally uses 24 by 24 CSS pixels with stated exceptions. Text contrast and target size still need testing in the assembled page. [S5][S6]
```css
:where(a, button, input, select, textarea, [tabindex]):focus-visible {
outline: 2px solid var(--color-focus);
outline-offset: 3px;
}
@media (pointer: coarse) {
.filter-chip, .icon-button, .button { min-height: 44px; }
.icon-button { min-width: 44px; }
}
@media (prefers-reduced-motion: reduce) {
.button, .result-row, .filter-chip, .skeleton {
transition: none;
animation: none;
}
}
```
For composite inputs, put the visual ring on the outer wrapper and suppress the inner ring **only while that equivalent wrapper focus indicator is present**. Verify that outlines are not clipped by overflow containers. Test meaningful keyboard order, accessible names, zoom, and forced-colors mode.
### Footer
Use a white surface, 14px muted text, and compact utility/legal links. Keep roughly 64–80px between the last major content region and the footer. Wrap links on small screens. Do not give the footer a contrasting dark background or large promotional content by default.
## Do's and Don'ts
### Do
- Keep the neutral canvas, small control vocabulary, readable single results column, and clear text hierarchy.
- Reuse the exact application tokens before adding local exceptions. Treat layout caps as max-widths and component heights as minimums.
- Keep metadata badges compact while ensuring real controls remain operable by keyboard and touch.
- Preserve query/filter/sort state, distinguish empty from failed requests, and use real data and the application's own identity.
- Treat C, H, and R as different evidence classes; refresh unverified values before claiming source fidelity.
### Don't
- Turn the search page into a multi-column card grid, sidebar-heavy dashboard, or second marketing hero.
- Add gradients, glass panels, oversized shadows, giant headlines, or a different accent color for every feature.
- Set every component to a pill: buttons and filters are pills; badges are small rounded rectangles; rows are flat.
- Import a generic component theme unchanged and assume its spacing, fonts, shadows, and borders will match this specification.
- Claim that the current source was screenshot-verified, that SF Pro Rounded definitely rendered, or that recommended colors/states are measured Ollama values.
### Implementation acceptance checklist
Check the built app at **390×844**, **768×1024**, and **1440×900**, plus a 320px-wide effective viewport and 200% zoom. These are test targets, not captured reference sizes.
The search column must remain centered and single-column; filters and metadata must wrap; long names must not overflow; inputs must retain clear labels and focus; loading must not erase user state; controls must work without a mouse. The interface should remain recognizably sparse even with real, variable-length data.
Visual sign-off against Ollama is **pending**. To close that gap, capture both reference URLs and the application at the same viewport, browser, OS, and zoom. Compare shell geometry first, then fonts/wrapping, controls, rows, and small details. Do not compare changing model counts or marketing copy as though they were stable visual tokens.
### Updating this document from live measurements
The optional `capture-design.js` can be run in a browser's DevTools console on each requested public page. It saves a local JSON snapshot of computed styles, visible DOM elements, CSS variables, available stylesheet rules, and font declarations. It does not upload data, fetch font files, or capture screenshots. Review the script before executing it.
A computed family list is not evidence of the actual rendered font. A declaration in a stylesheet is not evidence of a used state. The collector also does not prove hover/pressed behavior; inspect those states separately. Browser-native screenshots and rendered-font inspection are still required for visual verification. [S7]
When measurements arrive, update values and their provenance together. Do not silently promote R tokens to C. Retain both viewport and capture date with measurements, and avoid copying documentation-site styling into the marketing/search system.
### Validation and handoff
Place `DESIGN.md` at the application repository root. Import `design-tokens.css` once from the global stylesheet, or read `design-tokens.json` to generate equivalent project-native tokens. The JSON file is a direct serialization of this YAML, **not a W3C DTCG export**. The CSS contains variables only; it does not silently restyle the application.
Local checks validate the YAML structure, allowed token fields, reference resolution, section order, CSS-variable agreement, and declared component text/background contrast. Results are in `validation-report.json`. These checks do **not** constitute the official Google CLI linter or a visual/accessibility audit.
The official commands below are documented by the DESIGN.md project; they were not run in this environment. [S3]
```bash
npx @google/design.md lint DESIGN.md
npx @google/design.md export --format css-tailwind DESIGN.md > theme.css
npx @google/design.md export --format dtcg DESIGN.md > tokens.dtcg.json
```
**Agent instruction:** read this entire file before implementing the UI. Apply its tokens and layout rules, replace reference branding/content with the app's own, preserve the evidence boundary, and avoid adding unsupported components or decoration. Search is a usable product surface first, not a landing-page imitation.