DESIGN.md
Rama master · réplica de lectura del repositorio original.
---
name: Rybbit
description: Open-source, privacy-friendly web & product analytics. A flat, dark-mode-default instrument panel where the data is the interface.
colors:
# Surfaces (dark mode is the default theme; light values noted in prose)
background: "#141414" # hsl(0 0% 8%) — app canvas (dark)
surface: "#1b1b1b" # hsl(0 0% 10.5%) — card / panel (neutral-900)
surface-raised: "#242424" # hsl(0 0% 14%) — default button / elevated chrome (neutral-850)
foreground: "#fafafa" # hsl(0 0% 98%) — primary text (dark)
muted-foreground: "#999999" # hsl(0 0% 60%) — secondary text / descriptions
border: "#1f1f1f" # hsl(0 0% 12%) — hairline dividers (dark)
# Accent — emerald ramp carries primary action + success
accent: "#10b981" # emerald-500
accent-strong: "#059669" # emerald-600 (border / dark-mode fill)
accent-deep: "#065f46" # emerald-800 (dark success fill)
# Data visualization
dataviz: "#b3bfff" # hsl(230 100% 85%) — primary chart series (periwinkle, dark)
dataviz-2: "#c2caff" # hsl(230 100% 92%) — secondary chart series (dark)
# Semantic state
destructive: "#ef4444" # red-500
warning: "#eab308" # yellow-500
info: "#3b82f6" # blue-500
success: "#10b981" # emerald-500
typography:
headline:
fontFamily: "Inter, system-ui, sans-serif"
fontSize: "1.875rem"
fontWeight: 600
lineHeight: 1.1
letterSpacing: "-0.01em"
title:
fontFamily: "Inter, system-ui, sans-serif"
fontSize: "1rem"
fontWeight: 600
lineHeight: 1
letterSpacing: "-0.01em"
body:
fontFamily: "Inter, system-ui, sans-serif"
fontSize: "0.875rem"
fontWeight: 400
lineHeight: 1.4
letterSpacing: "normal"
label:
fontFamily: "Inter, system-ui, sans-serif"
fontSize: "0.75rem"
fontWeight: 500
lineHeight: 1.3
letterSpacing: "normal"
rounded:
sm: "0.8px" # calc(var(--radius) - 4px)
md: "2.8px" # calc(var(--radius) - 2px)
lg: "4.8px" # var(--radius) = 0.3rem
spacing:
xs: "6px"
sm: "8px"
md: "16px"
lg: "24px"
components:
button-default:
backgroundColor: "{colors.surface-raised}"
textColor: "{colors.foreground}"
rounded: "{rounded.lg}"
padding: "8px 12px"
height: "36px"
button-accent:
backgroundColor: "{colors.accent-strong}"
textColor: "#fafafa"
rounded: "{rounded.lg}"
padding: "8px 12px"
height: "36px"
button-accent-hover:
backgroundColor: "{colors.accent}"
textColor: "#fafafa"
button-outline:
backgroundColor: "transparent"
textColor: "{colors.muted-foreground}"
rounded: "{rounded.lg}"
padding: "8px 12px"
height: "36px"
card:
backgroundColor: "{colors.surface}"
textColor: "{colors.foreground}"
rounded: "{rounded.lg}"
padding: "16px"
input:
backgroundColor: "transparent"
textColor: "{colors.foreground}"
rounded: "{rounded.lg}"
padding: "4px 12px"
height: "36px"
badge-success:
backgroundColor: "rgba(16,185,129,0.20)"
textColor: "#34d399"
rounded: "{rounded.md}"
padding: "2px 6px"
---
# Design System: Rybbit
## 1. Overview
**Creative North Star: "The Instrument Panel"**
Rybbit is a precise, legible cockpit for understanding a website. Every surface is flat; depth comes from tonal layering of a pure-grayscale ramp, not from shadows. The canvas recedes (near-black `#141414` in the default dark theme), panels lift one notch (`#1b1b1b`), interactive chrome lifts one more (`#242424`), and hairline 1px borders draw the seams. Onto that calm gray instrument, two signals are painted with intent: a single **emerald** for action and success, and a single **periwinkle** (`#b3bfff`) for the data lines themselves. The data is the instrument; the chrome stays out of the way.
The personality is friendly, precise, and trustworthy: approachable without being toy-like (PRODUCT.md). Warmth lives at the edges (the frog, an empty state, a small touch), never on top of the numbers. Density is a feature here, not a flaw: this is a tool people open to answer a specific question, and the layout should put that answer in front of them in seconds with depth one layer down. Radii are deliberately tight (`0.3rem` / 4.8px max), so the UI reads as engineered and exact rather than soft or playful.
This system explicitly rejects the **generic SaaS template** (purple gradients, the hero-metric template, endless identical icon+heading+text card grids), anything **over-playful or toy-like** that undermines trust in the data, the **cluttered GA4** maze, and **cold enterprise-BI** heaviness (PRODUCT.md anti-references). It should feel like Linear or a Vercel/Stripe dashboard: confident, quiet, fast.
**Key Characteristics:**
- Dark-mode-default; a full light theme mirrors every token.
- Flat by default: depth via a 21-step grayscale ramp + 1px borders, never shadows.
- One accent (emerald) for action/success, one data hue (periwinkle) for charts.
- Tight geometry: 4.8px max radius, Inter throughout, compact 36px controls.
- Density with legibility: dense data, but body text stays ≥4.5:1 contrast.
## 2. Colors
A pure-neutral grayscale chassis carrying exactly two chromatic signals; semantic state colors appear only on state.
### Primary
- **Emerald Signal** (`#10b981`, `--accent-500`; border `#059669`, `--accent-600`): The single brand accent. Primary buttons, success buttons, current selection, positive deltas, the `success` badge. In dark mode the fill deepens to `#059669`/`#065f46` so it sits calmly on near-black. This is the only color allowed to mean "act here" or "this went well."
### Secondary
- **Periwinkle Data Line** (`#b3bfff` dark / `#99aaff` light, `--dataviz`; second series `#c2caff` / `#b3bfff`, `--dataviz-2`): Reserved exclusively for data visualization (line/bar/area series, the activity globe). It is a *data* color, never a UI color: it must not appear on buttons, links, or chrome.
### Neutral
The workhorse. A 21-step pure-grayscale ramp (`--neutral-0` at 97% L through `--neutral-1000` at 4% L, chroma 0).
- **Canvas** (`#141414`, `--background` dark / `#f7f7f7` light): The app backdrop.
- **Surface** (`#1b1b1b`, `--neutral-900` / white light): Cards and panels, one step off the canvas.
- **Raised Surface** (`#242424`, `--neutral-850` / white): Default buttons and elevated chrome.
- **Primary Text** (`#fafafa`, `--foreground` dark / `#0a0a0a` light): Body and headings.
- **Muted Text** (`#999999`, `--neutral-400`/`--neutral-500`): Descriptions, secondary labels, placeholders. Verify ≥4.5:1 before going lighter.
- **Border** (`#1f1f1f`, `--border` dark / `#e6e6e6` light): Hairline seams and dividers, always 1px.
### Tertiary (semantic state only)
- **Destructive Red** (`#ef4444`, `--red-500`): Errors, delete actions, negative deltas.
- **Warning Yellow** (`#eab308`, `--yellow-500`): Warnings, caution thresholds.
- **Info Blue** (`#3b82f6`, `--blue-500`): Informational badges only (distinct from the periwinkle data hue).
A full Tailwind-style ramp (red, orange, amber, yellow, lime, green, emerald, teal, cyan, sky, blue, indigo, violet, purple, fuchsia, pink, rose) is defined in `globals.css` for chart palettes and category coloring. Outside data viz and state, do not reach into it.
### Named Rules
**The Signal-Not-Decoration Rule.** Emerald marks primary actions, success, and current selection only. It never tints a surface, never fills a background, never decorates. If emerald is on screen and nothing actionable or successful is being communicated, remove it.
**The Two-Voice Rule.** The interface speaks in grayscale. Exactly two chromatic voices are allowed on top: emerald for *action*, periwinkle for *data*. State colors (red/yellow/blue) are whispers that appear only when state demands them.
**The Flat Rule.** No `box-shadow` for resting elevation. Depth is the grayscale ramp plus 1px borders. If two surfaces need separating, change the neutral step or add a hairline border, not a shadow.
## 3. Typography
**Display / Body / Label Font:** Inter (with `system-ui, sans-serif` fallback), loaded via `next/font`.
**Character:** One family, carried by weight and size contrast, not by pairing. Inter is neutral, screen-legible at small sizes, and tabular-friendly for numbers, which is exactly right for a dense analytics tool. There is no display typeface; product UI does not need one.
### Hierarchy
- **Headline** (600, 1.875rem / 30px, line-height 1.1): Large stat values and primary metric numbers. Use tabular figures for aligned columns.
- **Title** (600, 1rem / 16px, `leading-none`, `tracking-tight`): Card titles, section headers, panel headings.
- **Body** (400, 0.875rem / 14px, line-height ~1.4): The dominant size. Table cells, descriptions, most UI text. Cap prose at 65–75ch; tables may run denser.
- **Label** (500, 0.75rem / 12px): Badges, chips, small captions, axis labels, secondary metadata.
### Named Rules
**The One-Family Rule.** Inter does every job: headings, body, labels, data. Do not introduce a second UI typeface. Hierarchy comes from weight (400/500/600) and size, never from a new face.
**The No-Shrinking-Headline Rule.** Type uses a fixed rem scale, not fluid `clamp()`. Users view at a consistent DPI inside an app shell; a heading that shrinks in a sidebar looks worse, not better.
## 4. Elevation
This system is **flat by default**. There is no resting shadow vocabulary; depth is conveyed entirely through the grayscale ramp (canvas → surface → raised surface) and 1px borders. Cards declare `transition-all duration-300` but carry no shadow at rest; the only motion-borne depth is a hairline border-color shift on hover. If a surface looks like a 2014 app, the shadow is the problem: remove it and step the neutral instead.
### Named Rules
**The Tonal-Depth Rule.** Separation is achieved by moving one step on the neutral ramp or adding a 1px border, never by stacking shadows. Overlays that genuinely float (dropdowns, dialogs, popovers via Radix) may use the platform's minimal elevation, but in-page surfaces stay flat.
## 5. Components
### Buttons
- **Shape:** `rounded-lg` (4.8px) for default/sm/lg; `rounded-md` (2.8px) for xs/icon. Height 36px default (`h-9`), 32px sm, 24px xs. `font-medium`, `transition-colors`.
- **Default:** Raised neutral surface with a 1px border (`#242424` fill, `#3d3d3d` border in dark; white fill, `#d9d9d9` border in light). Hover lightens the fill and border one step. This is the neutral workhorse button.
- **Accent / Success:** Emerald fill (`#059669` dark / `#10b981` light) with a deeper emerald border, white text. The primary call to action.
- **Destructive / Warning:** Red / yellow fills, used only for genuinely destructive or cautionary actions.
- **Outline / Ghost / Link:** Transparent surfaces with muted neutral text; hover fills with the faintest neutral step. For secondary and tertiary actions.
- **Focus:** `focus-visible:ring-1` in `--neutral-950` (light) / `--neutral-300` (dark). Always keep a visible focus ring.
### Cards / Containers
- **Corner Style:** `rounded-lg` (4.8px).
- **Background:** Surface step (`#1b1b1b` dark / white light), one notch off the canvas.
- **Border:** 1px hairline (`--neutral-850` dark / `--neutral-100` light). `overflow-hidden`.
- **Shadow Strategy:** None at rest (see Elevation). Hover may shift border color only.
- **Internal Padding:** 16px (`p-4`); header uses `space-y-1.5` (6px). Descriptions in muted text at 14px.
- **Loading:** Cards use an in-place `CardLoader` (ldrs Zoomies) tinted from the neutral ramp; prefer skeletons over a centered spinner for content.
### Inputs / Fields
- **Style:** Transparent background, 1px border (`--neutral-150` light / `--neutral-800` dark), `rounded-lg`, 36px tall (`h-9`), 14px text. Placeholder in muted neutral (meets 4.5:1).
- **Focus:** `focus-visible:ring-1` neutral; no border color change beyond the ring.
- **Error:** `aria-[invalid=true]` flips the border to red-500 (dark: red-400).
- **Search variant:** Leading 16px search icon in `--neutral-400`, `pl-9`.
### Badges / Chips
- **Shape:** `rounded-md` (2.8px), `px-1.5 py-0.5`, 12px `font-medium`.
- **Semantic variants:** success/destructive/warning/info render as a 20% tint of the hue with the 600 (light) / 400 (dark) text shade and a transparent border. Neutral default/secondary/outline/ghost use the grayscale ramp.
### Data Visualization (signature)
- Charts (Nivo bar/line/calendar, D3, Mapbox/OpenLayers globe) render series in periwinkle (`--dataviz`, `--dataviz-2`) by default, escalating to the full Tailwind ramp only when many categories must be distinguished. Axes, gridlines, and labels use the muted neutral steps so the data line stays dominant. Prefer hue+shape/position separation over hue-alone (PRODUCT.md a11y).
## 6. Do's and Don'ts
### Do:
- **Do** keep surfaces flat: convey depth with the neutral ramp (`#141414` → `#1b1b1b` → `#242424`) and 1px borders.
- **Do** reserve emerald for action, success, and current selection (The Signal-Not-Decoration Rule).
- **Do** keep data viz in periwinkle (`--dataviz`) and the broader ramp; never use the data hue on chrome.
- **Do** use Inter at fixed rem sizes with weight contrast (400/500/600) for all hierarchy.
- **Do** keep radii tight (≤4.8px) and controls compact (36px default height).
- **Do** keep a visible `focus-visible` ring on every interactive element, and honor `prefers-reduced-motion`.
- **Do** mirror every token across dark and light themes; dark is the default.
### Don't:
- **Don't** ship the **generic SaaS template**: no purple gradients, no hero-metric template (big number + tiny label + gradient accent), no endless identical icon+heading+text card grids.
- **Don't** let the frog or friendly tone tip into **over-playful / toy-like** territory that undermines trust in the numbers.
- **Don't** recreate the **cluttered GA4** maze or **cold enterprise-BI** heaviness: surface the likely answer first, depth one layer down.
- **Don't** add resting `box-shadow` for elevation (The Flat Rule); don't use shadows where a neutral step or border works.
- **Don't** use `border-left`/`border-right` > 1px as a colored accent stripe; use full hairline borders or a tint.
- **Don't** use gradient text (`background-clip: text`) or decorative glassmorphism.
- **Don't** introduce a second UI typeface or fluid `clamp()` headings.
- **Don't** tint surfaces or backgrounds with emerald, or paint chrome in the periwinkle data hue.