# Divetracx design system ## Atmosphere and identity Divetracx is a calm, precise dive logbook. It combines a cool sea-glass page, crisp white cards, deep-water ink, and a single teal accent. Dive data — depths, times, pressures, gases, coordinates — should read like instrument output: compact, aligned, and unambiguous. Reliability is part of the visual identity. Profiles, maps, and pictures must show useful content or an intentional empty state, never a broken placeholder. Imported data is never silently altered by presentation: units, precision, and provenance stay visible. ## Color Use these semantic tokens from `src/styles.css`: - Page: `--background`, `#f4f8f8`. The body adds a faint teal radial glow at the top right; do not repeat it inside components. - Ink: `--foreground`, `#102c31`. - Card: `--card` / `--card-foreground`, `#ffffff` / `#102c31`. - Muted surface: `--muted`, `#e9f1f1`. Muted text: `--muted-foreground`, `#597176`. - Accent surface: `--accent` / `--accent-foreground`, `#d9eeef` / `#102c31`. Used for icon tiles and selected rows, never for text emphasis. - Primary action: `--primary` / `--primary-foreground`, `#087f8c` / `#f5ffff`. - Border: `--border`, `#d3e1e2`. - Destructive: `--destructive` / `--destructive-foreground`, `#dc2626` / `#ffffff`. Inline destructive text uses `text-red-600`. - Warning: `--warning` / `--warning-foreground`, `#d97706` / `#92400e`. Teal identifies interaction and current state, not decoration. Section eyebrows, active navigation, icon tiles, focus rings, and the site-map pin all derive from `--primary`. Add new semantic roles to `src/styles.css` before using them. The one sanctioned exception is data visualization: the dive-profile chart and per-tank series use a fixed categorical palette (`#0891b2`, `#7c3aed`, `#ea580c`, `#16a34a`, `#db2777`, `#4f46e5`) so tanks stay distinguishable regardless of theme. Certification cards use a `from-primary via-primary to-cyan-800` gradient as their card surface. Agency logos use a rounded-rectangle frame everywhere they appear. Profile-card agency badges show both the agency name and membership number and fall back to initials when a logo cannot be loaded. The profile certification list uses a star control to select up to eight certifications for the Divetracx card. The card shows only starred certifications; one to three starred items use one column, while four to eight use two columns. Certification pill labels keep their full names and reduce their font size only as needed to fit the available width. When insurance details are recorded and enabled for the card, the card footer shows the insurer, optional plan, and policy number. Emergency-contact name, phone, and email are likewise shown only when available and enabled. Both groups default to visible and have independent controls in their profile-form sections. The card is titled “Diver Profile”; its certification labels reduce their font size as needed to retain full names. Agency badges are visually prominent beneath a slightly smaller profile image. On desktop, the Divetracx card and certifications occupy the profile page's left column while the personal-details form occupies the right column. Body text must meet WCAG 2.2 AA contrast on its actual surface. Form-control outlines need at least 3:1 contrast with adjacent colors. There is currently a single light theme; do not add `dark:` utilities piecemeal — a dark theme is a token-level change. ## Typography - UI and body: `Manrope`, sans-serif (`font-sans`). - Data: `DM Mono`, monospace (`font-mono`). Use it for depths, durations, pressures, coordinates, certification numbers, serials, and dive numbers so columns of values align. Scale: - Page title: `text-4xl`, weight 600, `tracking-tight`; the hero variant grows to `md:text-5xl` with `text-balance`. - Section eyebrow: `text-xs`, weight 700, uppercase, `tracking-[0.2em]`, `text-primary`. Muted eyebrows for grouped metadata use weight 500 or 600, `tracking-wide`, and `text-muted-foreground`. - Section title: `text-xl` or `font-semibold` at body size, weight 600. - Body: `text-sm` to `text-base`, weight 400 to 600. - Helper text: `text-xs`, `text-muted-foreground`. Body content never drops below 14px. Helper text, attribution, and control labels may use 12px when legible. ## Spacing and layout - The page frame is `mx-auto w-full max-w-6xl px-4 py-8 md:px-8 md:py-12`. - The header is sticky, `h-16`, `bg-background/90 backdrop-blur-xl`, with a `border-b border-border/80`. - Cards use `p-5` or `p-6`; card headers stack with `gap-1.5`. - Grids step from one column to `sm:grid-cols-2` and, for stat tiles, `lg:grid-cols-4`, with `gap-4` or `gap-5`. - Minimum tap target is 44px: interactive controls use `min-h-11`, icon buttons `size-11`, list rows `min-h-16`. ## Radius and depth - Cards, panels, buttons, and inputs: `rounded-2xl` for surfaces, `rounded-xl` for controls and inline tiles, `rounded-lg` for small badges and the `sm` button size. - Surfaces are flat: `border border-border bg-card` with no shadow. Shadows are reserved for the brand mark (`shadow-lg shadow-primary/20`), error cards (`shadow-xl shadow-primary/5`), and the map pin. - Layering uses transparency, not elevation: `bg-muted/40` for callouts, `bg-muted/50` for row hover, `color-mix` with `--card` for map controls. ## Components Shared primitives live in `src/components/ui/` (shadcn-style wrappers over Base UI): `alert`, `badge`, `button`, `card`, `checkbox`, `input`, `scroll-area`, `select`, `textarea`. Domain components live in `src/components/`. When a UI pattern gains a second consumer, promote it to a shared component with one visual contract instead of copying classes. ### Buttons `Button` variants: `default` (filled primary), `outline` (`border bg-card`), `ghost`, `destructive`. Sizes: `default` (`min-h-11 px-5`), `sm` (`min-h-9 rounded-lg px-3`), `lg` (`min-h-12 px-7`), `icon` (`size-11`). Every button is `rounded-xl`, `text-sm font-semibold`, and shows a `focus-visible:ring-2 ring-primary/30`. `SaveButton` wraps the pending state so forms never show two competing submit affordances. ### Cards and stat tiles `Card` is `rounded-2xl border border-border bg-card`; `CardLink` is the same surface as a whole-card link. `StatCard` is an `article` with a `text-primary` Lucide icon, a muted uppercase label, and a large mono value. Use it for aggregate numbers on Overview, Stats, and detail pages. ### Badges and states `Badge` variants: `default`, `secondary`, `outline`, `warning`, `destructive`. Import statuses (discovered, new, changed, unchanged, failed) and integration capabilities map onto these; do not introduce ad-hoc pill styles. ### Notifications Sonner provides app-wide transient notifications at the bottom right. Use an error toast for action failures whose inline location may be outside the current viewport; keep field-validation errors directly beneath their fields. Toasts must use plain-language titles and retain the actionable server message as their description. ### Record lists `DiveLinkList` and similar lists are `grid` rows with an icon tile (`size-9 rounded-lg bg-accent text-primary`), a truncating primary label, a mono trailing value, and a chevron. Rows separate with `border-b border-border` and highlight with `hover:bg-muted/50`. Empty lists render a centered `text-sm text-muted-foreground` sentence in the form "No dives yet." ### Forms `EntityForm` provides the field stack and inline star rating. Dive editors select existing taxonomy values; creating or renaming taxonomies belongs in Settings, not alongside the dive fields. Dive operator and boat are adjacent taxonomy selects in the main Dive section. The dive people picker is a searchable popover; selected people render as rows with a role select and remove action. New assignments default to Buddy. The Depth & time section carries the paper-logbook extras — a safety-stop toggle with an inline minutes input, and a Dive tables group with single-letter pressure-group inputs (upper-cased, mono) and residual nitrogen time; total bottom time is derived and displayed read-only. Field labels are `text-sm font-medium`; validation and server errors appear directly under the field in `text-sm text-red-600` with `aria-live="polite"`. Recorded dive profiles, import history, and external provenance are view-only and must not gain edit affordances. ### Destructive actions `DeleteRecordButton` renders a ghost button in `text-red-600`, asks for confirmation before deleting, and reports failures inline with `aria-live="polite"`. Never delete on the first click. The AI access settings page groups MCP tools into Read, Create and update, and Delete cards. Every tool has an independent switch, while the page-level MCP switch pauses the endpoint without discarding clients or history. Destructive tools and scopes use the destructive badge treatment; connected clients show their live scopes and require confirmation before revocation. Each integration card on the Integrations page ends with a “What to sync” list using the same switch rows: label, one-line description, and a mono `on/total` count in the heading. A required entity shows a disabled switch and says why it is always synced. A row that depends on a switched-off row is shown off and disabled with a `--warning` “Off because …” note, and a row with dependants warns what else switches off with it before it is toggled. The list saves with a small outline `SaveButton` that is disabled until something changed. ### Media `PictureGallery` is a `sm:grid-cols-2` grid of `aspect-[4/3]` images in `rounded-xl bg-muted` frames with a subtle `hover:scale-[1.02]`. `PhotoManager` adds upload and removal; failures show inline in red. Profile and buddy avatars fall back to the bundled illustrated diver (`src/assets/diver-fallback.png`), never to initials or an empty box. ### Certification cards `CertificationCard` renders a physical-card-shaped surface with the teal gradient, a bold tracked eyebrow, the certification name, and a mono card number; scanned fronts and backs are bounded to `856×540`. Agency names, codes, and logos under `public/agency-logos/` are resolved through `src/modules/profile/agency-catalog.ts`. ### Dive profile chart `dive-profile-chart.tsx` is hand-written SVG: depth on an inverted axis, temperature and decompression-ceiling series, per-tank pressure lines in the categorical palette, and tank-switch markers drawn as labelled circles with connector lines and an `aria-label`. Axes use the mono face and gridlines use `--border`. Do not introduce a charting library for this chart. Heart rate, when a computer recorded it, shares the temperature band as a dashed rose (`rose-500`) line with its own scale and a peak marker in `bpm`; it appears in the legend and the selected-sample panel only when the dive has readings. Alerts the computer raised sit on the depth curve at their time: a `--warning` triangle for critical ones (ascent rate, broken ceiling or safety stop, ppO₂, CNS/OTU, battery) and a small hollow circle for the rest, each with a dotted connector and a `