diff --git a/CLAUDE.md b/CLAUDE.md index eb5f670..34f55a4 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -22,6 +22,8 @@ npm run dev # Vite on :3000, proxies /api to VITE_BACKEND_URL npm run build # outputs web/dist, which `serv` serves if present npm run build:check # tsc + build (type check) npm run lint +npm test # Vitest (jsdom) +npm run tokens # regenerate src/design/tokens.css from src/design/tokens.json (runs before dev and builds) npm run build:static # demo build with VITE_STATIC_MODE=true (GitHub Pages), see web/README.static.md task dev:all # backend + frontend together @@ -48,6 +50,25 @@ CI (`.github/workflows/go-test.yml`) runs golangci-lint, gosec (`-exclude=G103,G **Frontend (`web/src`).** `lib/api.ts` is the single API client (axios, base `/api/v1alpha1`); it swaps in `lib/staticApi.ts` when `VITE_STATIC_MODE=true`. The backend returns proto enums as numbers; `lib/apiConverters.ts` maps them to/from the string enums in `types/api.ts`. Runtime config (Jira, Slack, demo mode, Homer URL, Clarity ID) is injected by the backend's `/config.js` as `window.TRACKER_CONFIG` and read in `src/config.ts`. Data fetching uses TanStack Query; routes are in `App.tsx`; pages under `pages/` are large single-file components. `@/` aliases `src/`. +## Design system + +`docs/design-system/` is the source of truth for the UI, identity "Surligneur": `README.md` (brand book: principles, colour rule, status grammar, type, voice, density, motion, navigation), `tokens.json` (colours light and dark, type, spacing, radii, sizes, shadow), `components/*.md` (one spec per component), `reference/` (reference React implementation: re-implement in TypeScript, never import it), `mockups/` (validated pages; read `mockups/README.md` first) and `IMPLEMENTATION.md` (the plan in five lots). It supersedes the old `design.md`. + +How tokens reach the code: `web/src/design/tokens.json` is a copy of `docs/design-system/tokens.json`; `web/scripts/build-tokens.mjs` generates `web/src/design/tokens.css` (light under `:root, .light`, dark under `.dark`); `web/src/index.css` exposes them as utilities (`bg-paper`, `text-ink`, `border-rule-strong`, `bg-signature`, `text-impact`, `text-title`, `text-data-sm`…). Map API statuses and event types with `web/src/lib/status.ts`. `/theme-test` is the living showcase: add every new component there, in all its states, light and dark. The `--hud-*` variables are a temporary bridge to the tokens, removed in lot 5. + +Non-negotiable rules: + +1. **No hard-coded colour** in components and pages (no hex, no rgb, no Tailwind palette class such as `blue-500`). Everything goes through the tokens. +2. **Colour signals a deviation.** Only `failed` and `warning` carry a status colour. `done` and `open` stay neutral, never a success green. `impact` (magenta) marks a user impact whatever the status. `type-*` colours are for charts only. +3. **`signature` (chartreuse) marks the present only**: the "now" needle, what is running, the active page and the selection (`signature-wash`). Always a fill under `on-signature` text, never a text colour. +4. **A status always goes through `StatusMark`, an impact through `ImpactMark`.** Never draw a status by hand. +5. **Spline Sans Mono for facts** (times, figures, versions, identifiers, lowercase metadata joined by " · "), **Spline Sans for sentences**. Tabular figures, no zero padding, no letter-spaced uppercase labels. +6. **Rules rather than shadows.** `shadow-overlay` is reserved for the drawer, the palette and menus. +7. **Light and dark are equal.** Every screen is checked in both themes. +8. **Accessibility**: 2 px `focus-ring` on every interactive element, accessible names for icon buttons, glyphs and impact, listbox and option roles on navigable lists, `prefers-reduced-motion` respected, README contrasts held. +9. **Interface text in English**, sentence case. No emoji, no exclamation mark. +10. **No gradients, no generic purple or blue accent, no multicoloured pills** for type or environment, and no coloured left border on cards. + ## Conventions - Conventional Commits (enforced by a CI workflow); releases and `CHANGELOG.md` are managed by release-please, so don't edit the changelog or versions by hand. diff --git a/docs/design-system/IMPLEMENTATION.md b/docs/design-system/IMPLEMENTATION.md new file mode 100644 index 0000000..2e45fbc --- /dev/null +++ b/docs/design-system/IMPLEMENTATION.md @@ -0,0 +1,125 @@ +# Implémenter le design system Tracker « Surligneur » dans `web/` + +Tu vas refondre l'interface de Tracker (`web/`, React 19, TypeScript, Vite, Tailwind 4) pour appliquer le design system documenté dans `docs/design-system/`. Le travail est découpé en cinq lots. Chaque lot fait l'objet d'une PR séparée, et l'application doit rester fonctionnelle à la fin de chacun. + +## Sources, à lire avant tout code + +- `docs/design-system/README.md` : le livre de marque. Ses principes, la règle de couleur, la grammaire des statuts, la typographie, la voix, la densité, le mouvement et l'architecture de navigation font foi. +- `docs/design-system/tokens.json` : la source de vérité des couleurs (clair et sombre), de la typographie, des espacements, des rayons, des tailles et de l'ombre. +- `docs/design-system/components/*.md` : la spécification de chaque composant (rôle, variantes, règles d'usage, accessibilité). +- `docs/design-system/reference/` : `bundle.js` et `bundle.css` sont une implémentation de référence en React (sans JSX, avec un namespace global), et `index.d.ts` donne les props attendues. **Ne les importe pas.** Réimplémente proprement en TypeScript, mais reprends exactement les rendus, les classes visuelles, les tracés SVG (icônes, glyphes de statut) et les comportements. +- `docs/design-system/mockups/` : les maquettes de pages validées. Lis d'abord `mockups/README.md` pour comprendre leur format. +- `docs/design-system/assets/logos/` et `docs/design-system/fonts/` : le logo en SVG, et les polices Spline Sans et Spline Sans Mono (licence OFL, en secours si les paquets Fontsource posent problème). +- `CLAUDE.md`, `web/src/index.css`, `web/src/contexts/ThemeContext.tsx`, `web/src/components/Layout.tsx`, `web/src/components/ui/*`, `web/src/types/api.ts` et `proto/event/v1alpha1/event.proto` : l'existant à respecter. + +Ignore l'ancien `design.md` : il est remplacé par `docs/design-system/`. + +## Règles non négociables + +1. **Aucune couleur en dur** dans les composants et les pages (ni hex, ni rgb, ni classes de palette Tailwind comme `blue-500`). Tout passe par les tokens. +2. **La couleur signale l'écart.** Seuls `failed` et `warning` portent une couleur de statut. `done` et `open` restent neutres, et il n'y a jamais de vert de succès. `impact` (magenta) signale un impact utilisateur, quel que soit le statut. Les couleurs `type-*` ne servent que dans les graphiques. +3. **`signature` (chartreuse) marque uniquement le présent** : l'aiguille « now », ce qui est en cours, la page active et la sélection (`signature-wash`). On l'utilise toujours en aplat sous un texte `on-signature`, jamais comme couleur de texte. +4. **Un statut passe toujours par `StatusMark`, un impact par `ImpactMark`.** On ne dessine jamais un statut à la main. +5. **Spline Sans Mono pour les faits** (heures, chiffres, versions, identifiants, métadonnées en minuscules séparées par « · »), **Spline Sans pour les phrases**. Chiffres tabulaires, sans zéro de remplissage, et pas de labels en capitales espacées. +6. **Filets plutôt qu'ombres.** `shadow-overlay` est réservée au drawer, à la palette et aux menus. +7. **Clair et sombre sont égaux.** Chaque écran est vérifié dans les deux thèmes. +8. **Accessibilité** : anneau de focus `focus-ring` de 2 px sur tout élément interactif, noms accessibles pour les boutons icône, les glyphes et l'impact, rôles listbox et option sur les listes navigables, `prefers-reduced-motion` respecté, contrastes du README tenus. +9. **Textes de l'interface en anglais**, en casse de phrase. Pas d'emoji, pas de point d'exclamation. +10. **Pas de dégradés, pas d'accent violet ou bleu générique, pas de pastilles multicolores** pour le type ou l'environnement, et pas de bordure gauche colorée sur les cartes. + +## Méthode de travail + +- Au début de chaque lot, présente un plan court : fichiers touchés, décisions, risques. **Attends ma validation avant de coder.** +- Ne modifie ni le backend ni `proto/` sans me le demander. Quand une donnée manque, propose l'option côté client et l'option côté API. +- Avant de rendre un lot : `npm run lint`, `npm run build:check` et `npm test` doivent passer dans `web/`. Ajoute des tests Vitest pour la logique pure. +- Transforme `web/src/pages/ThemeTest.tsx` en vitrine vivante du design system : chaque nouveau composant y apparaît dans tous ses états, en clair et en sombre. Mets-la à jour à chaque lot. +- Garde les routes et les fonctionnalités existantes. Un écran pas encore refondu doit continuer de fonctionner. +- Fais des commits petits et explicites. + +## Lot 1 — Fondations + +1. Copie `docs/design-system/tokens.json` vers `web/src/design/tokens.json`, puis écris `web/scripts/build-tokens.mjs`, qui génère `web/src/design/tokens.css` : les tokens de couleur clairs sous `:root`, les sombres sous `.dark`, et les autres familles (espacement, rayon, taille, ombre) sous `:root`. Branche le script sur `prebuild` et `predev`. Le fichier généré porte une mention « ne pas modifier à la main ». +2. Dans `web/src/index.css`, importe `tokens.css` et déclare un bloc `@theme inline` qui expose les tokens à Tailwind : `--color-paper: var(--paper)`, et de même pour tous les tokens de couleur, plus les polices, les rayons et les espacements. Les utilitaires deviennent `bg-paper`, `text-ink`, `border-rule-strong`, `text-impact`, `bg-signature`, etc. Crée des utilitaires `@utility` pour les styles typographiques de `tokens.json` (`text-metric`, `text-title`, `text-summary`, `text-data-sm`…). +3. Polices : installe `@fontsource-variable/spline-sans` et `@fontsource-variable/spline-sans-mono`, et retire Plus Jakarta Sans, IBM Plex Mono et Space Grotesk. +4. **Pont de compatibilité** : redéfinis les variables `--hud-*` existantes (des composantes RGB utilisées par `rgb(var(--hud-…))`) à partir des nouveaux tokens, en clair et en sombre. `--hud-bg` reprend `paper`, `--hud-surface` reprend `surface`, `--hud-primary` reprend `ink`, `--hud-on-surface-var` reprend `muted`, `--hud-outline-var` reprend `rule`, `--hud-error` reprend `failed`, `--hud-warning` reprend `warning`, et `--hud-success` reprend `muted` (le succès reste neutre). Toute l'application adopte ainsi la palette sans toucher les fichiers qui utilisent `hud-*`. Marque ce bloc comme temporaire. +5. Crée `web/src/lib/status.ts`, avec ses tests : + - `toDsStatus(apiStatus)` : `failure` et `error` donnent `failed` ; `warning` donne `warning` ; `start` et `in_progress` donnent `running` ; `success`, `done` et `close` donnent `done` ; tout le reste (`open`, `planned`, `waiting_approval`, `snapshot`, `user_update`, `recommandation`, non spécifié) donne `open`. + - `toTypeIcon(type)` : `deployment`, `operation`, `drift` (icône `drifts`), `incident` et `rpa_usage` (icône `rpa`). +6. Ajoute à `CLAUDE.md` une section « Design system » qui pointe vers `docs/design-system/` et reprend les dix règles ci-dessus. + +Le lot 1 est terminé quand l'application tourne avec la nouvelle palette et les nouvelles polices dans les deux thèmes, sans régression fonctionnelle, et quand la vitrine affiche les couleurs et les styles typographiques. + +## Lot 2 — Composants de base, dans `web/src/components/ui` + +Implémente en TypeScript, avec `class-variance-authority` quand il y a des variantes, en suivant `components/*.md`, `reference/index.d.ts` et le rendu de `reference/bundle.js` et `bundle.css` : + +- **`Icon`** : le jeu d'icônes sur mesure. Reprends les tracés de `icons` dans `bundle.js`, sur une grille de 16 px, trait de 1.5, extrémités carrées. Expose `name`, `size` et `title`. +- **`Logo`** : symbole et logotype, à partir des tokens. Les SVG de `assets/logos` servent hors application (favicon, README). +- **`Button`** : variantes `primary`, `secondary` et `ghost`, tailles `md` et `sm`, bouton icône. Fusionne avec l'actuel `button.tsx` et `button-variants.ts`, ou remplace-les. +- **`Kbd`** et **`Highlight`**. +- **`StatusMark`** : étiquette de 22 px avec glyphe à signe intérieur, ou glyphe seul avec `label={false}`. Les tracés des glyphes sont dans `Glyph` de `bundle.js`. La rotation de `running` s'arrête avec `prefers-reduced-motion`. +- **`ImpactMark`** : versions compacte et avec libellé. +- **`SegmentedControl`** : sur `@radix-ui/react-toggle-group`, ou des boutons avec `aria-pressed`. +- **`MetricStrip`** : avec `tone` (`failed`, `warning`, `impact`), `delta` et `trend`. Seul `trend: 'worse'` est coloré. +- **`EventRow`** et **`EventList`** : trois colonnes (heure, titre avec métadonnées, puis `ImpactMark` et `StatusMark` à droite), densités confort et compacte, `role="listbox"`, sélection et navigation ↑ ↓ au clavier. +- **`Sidebar`** : groupes, page active en `signature`, compteurs, et mode `collapsed` en rail de 60 px avec infobulles. +- **`TimeRuler`** : props `startHour` et `endHour`. +- **`EventDrawer`** : mise en page « bandeau » (type, statut, titre, pastilles service, environnement et owner qui déclenchent `onFilter`, liens PR et ticket avec emplacement vide en pointillé, bande d'impact, détails, historique tiré du changelog). +- **`CommandPalette`** : sur `cmdk` dans un Dialog Radix, avec groupes, raccourcis et navigation clavier. + +Chaque composant apparaît dans la vitrine et a au moins un test de rendu et d'accessibilité : nom accessible, rôle, état. + +## Lot 3 — Coquille et navigation + +- **`Layout.tsx`** : la nouvelle `Sidebar` (quatre groupes : operations, services, infrastructure, resources), une barre du haut avec le bouton « Search or jump to… ⌘K » qui ouvre la `CommandPalette`, et **un seul bouton `New`** avec menu (event, lock, drift, service, RPA), branché sur `CreatePanelContext`, à la place des cinq boutons actuels. Supprime les emojis du pied de page. +- **Hook de raccourcis** : `/` focalise le filtre de la page, ⌘K ouvre la palette, G puis D, T ou S navigue, esc ferme. Les raccourcis sont ignorés pendant la saisie dans un champ. +- **`QueryBar`**, nouveau composant décrit dans `mockups/search-and-filters.dc.html` et `mockups/streamline.dc.html` : + - syntaxe `key:value`, `key:a,b`, `-key:value`, `"phrase"` et texte libre ; + - clés `env`, `status`, `type`, `owner`, `service`, `priority`, `impact` ; + - autocomplétion des clés, puis des valeurs avec leur nombre de résultats ; + - jetons supprimables, ⌫ sur un champ vide retire le dernier jeton. +- **Parseur `parseQuery` et `serializeQuery`**, testé, avec l'état des filtres dans l'URL. Les filtres et la période sont partagés entre Timeline, Streamline et Calendar. Traduis les filtres vers les paramètres de `SearchEvents` quand ils existent. `owner` n'y est pas : filtre côté client et signale-le-moi. +- **Vues enregistrées** (onglets avec compteur, « Save view ») stockées en localStorage pour commencer. + +## Lot 4 — Pages, dans cet ordre + +Pour chaque page, suis la maquette correspondante. La mise en page, les textes, les composants et les comportements de `renderVals()` font foi. Remplace les données d'exemple par celles de l'API. + +1. **Dashboard** (`mockups/dashboard.dc.html`) : + - phrase de synthèse dont chaque segment filtre l'activité ; + - `MetricStrip`, dont la métrique « users impacted » ; + - Needs attention (échecs, impacts, chevauchements à venir, warnings) et Up next (planifié, chevauchements, locks) ; + - Activity avec filtres par statut et leurs compteurs, histogramme des 24 h cliquable, `EventList` et `EventDrawer` ; + - sélecteur everyone / my team. +2. **Timeline** (pas de maquette dédiée) : `QueryBar`, `TimeRuler` en tête, `EventList` dans les deux densités et `EventDrawer`. Remplace `EventDetailsModal`, et fais en sorte que `/events/:id` ouvre le drawer. +3. **Streamline** (`mockups/streamline.dc.html`) : garde la logique de `EventsStreamline.tsx` et rhabille-la. + - vues enregistrées, `QueryBar`, regroupement par environnement ou par équipe, option « Overlaps only » ; + - navigateur de chevauchements Previous / Next (raccourcis P et N), qui réutilise le calcul de `EventsOverlaps` ; + - aperçu des 24 h, couloirs, passé grisé, aiguille « now », barres stylées selon le statut avec un liseré `impact`, hachures de chevauchement et de lock ; + - drawer de résolution de chevauchement. +4. **Architecture** (`mockups/architecture.dc.html`) : garde React Flow. + - type de nœud `ServiceNode`, et disposition automatique de gauche à droite via `dagre` ou `elkjs` (les points d'entrée à gauche) ; + - clic sur un service : simulation de panne, avec le rayon d'impact calculé côté client à partir de `dependencies_in` et `dependencies_out` ; + - panneau d'impact (phrase, métriques, services touchés directement et en cascade, équipes à prévenir via `communication_channels`, événements du jour), mode « what it needs », vue classement par rayon d'impact ; + - `Sidebar` repliée par défaut, et KPI ainsi que filtres en pastilles supprimés. +5. **Insights** (`mockups/insights.dc.html`) : garde Recharts, avec des couleurs en `var(--type-*)`, `var(--impact)` et `var(--ink)`. + - `MetricStrip` comparée à la période précédente ; + - activité quotidienne empilée par type avec la période précédente en pointillé ; + - répartition par type ; + - tableau « Reliability and impact by service », triable ; + - tendance sur 12 mois avec un sélecteur de mesure ; + - **pas de phrase de synthèse en haut de cette page**. + + Calculs : taux d'échec = déploiements en échec ÷ déploiements (via `GetEventStats` filtré) ; rétablissement = médiane de `metadata.duration` des incidents ; impact = durée cumulée des événements avec `impact = true` ; tendance via `GetEventStatsByMonth`. Signale-moi ce qui demanderait un endpoint dédié. +6. **Le reste**, avec les mêmes composants et règles : Calendar (surcharge le thème Schedule-X avec les tokens), Overlaps, Catalog, Compliance, Drifts, RPA usage, Locks, Links, Docs, ainsi que les formulaires de création. + +## Lot 5 — Nettoyage et garde-fous + +- Supprime le pont `--hud-*`, puis toute référence restante à `hud-*`. Supprime aussi les couleurs en dur (`T.primary`…), les `.icon-gradient`, Font Awesome et Lucide, une fois toutes les icônes passées au jeu sur mesure. +- Ajoute une règle de lint qui interdit les couleurs en dur et les classes de palette Tailwind dans `web/src`, sauf dans `web/src/design/`. +- Fais une passe d'accessibilité : navigation clavier complète, focus visible, contrastes, `prefers-reduced-motion`. +- Mets à jour `web/README.md` et les captures de `docs/assets/screenshots/`. + +## Pour commencer + +Lis toutes les sources listées plus haut, puis présente-moi le plan du lot 1. N'écris pas de code avant ma validation. diff --git a/docs/design-system/README.md b/docs/design-system/README.md new file mode 100644 index 0000000..b7a9f13 --- /dev/null +++ b/docs/design-system/README.md @@ -0,0 +1,115 @@ +# Tracker + +L'instrument de bord des équipes ops : ce qui s'est passé, ce qui se passe, ce qui coince. + +Tracker suit les déploiements, incidents, drifts, opérations et locks d'une infrastructure distribuée. Il est ouvert par tout le monde : le SRE d'astreinte qui creuse un échec, le dev qui vérifie son déploiement, le lead qui veut savoir en trois secondes si la journée est calme. L'identité, baptisée **Surligneur**, s'inspire du carnet d'ingénieur : papier chaud, encre, trame millimétrée, et un trait de surligneur chartreuse pour ce qui compte maintenant. + +## Principes + +1. **La couleur signale l'écart.** Seuls `failed` et `warning` portent une couleur de statut, et `impact` signale un impact utilisateur. Ce qui s'est bien passé (`done`) et ce qui attend (`open`) restent dans les neutres (`muted`, `chart-done`, `chart-open`). Le principe a été testé sur le dashboard face à une version « succès en vert » : en neutre, les deux échecs ressortent seuls, et un taux de succès de 60 % n'envoie plus de signal positif trompeur. +2. **Le chartreuse marque le présent.** `signature` signale l'aiguille « now », un événement en cours, la page active de la navigation et la sélection (`signature-wash`). Rien d'autre. S'il apparaît ailleurs, il perd son sens. +3. **Le mono pour les faits, la sans pour les phrases.** Heures, chiffres, versions, identifiants et métadonnées sont en Spline Sans Mono. Les titres et les phrases sont en Spline Sans. +4. **Deux niveaux de lecture.** Chaque écran commence par une phrase de synthèse (style `summary`), puis viennent les chiffres, et le détail s'ouvre au clic dans le drawer. On ne force jamais le lecteur pressé à décoder un graphique. +5. **Filets, pas d'ombres.** Les panneaux se posent sur `paper` et se délimitent par un filet `rule`. `shadow-overlay` est réservée à ce qui flotte : drawer, palette ⌘K, menus. +6. **Deux thèmes égaux.** Clair et sombre sont conçus ensemble. Chaque token a ses deux valeurs, et aucun écran n'est validé tant que les deux n'ont pas été regardés. + +## Couleur + +Les neutres sont chauds et ne descendent jamais au blanc ou au noir purs : `paper` (#F3F1EC / #131210), `surface`, `sunken`, `overlay`, avec l'encre `ink` et le secondaire `muted`. Les bords décoratifs utilisent `rule` et `rule-soft`. Un contrôle interactif est toujours bordé de `rule-strong`, qui atteint 3:1. + +`signature` (#D4F53C) s'utilise toujours en aplat avec `on-signature` par-dessus. Ne jamais écrire en chartreuse sur `paper` : le contraste est d'environ 1.1:1. Dans les graphiques, l'aiguille « now » utilise `needle` (encre le jour, chartreuse la nuit), et le libellé « now 15:31 » est un surligneur. + +Les types d'événements ont chacun une couleur de série, réservée aux graphiques : `type-deployment` (cobalt), `type-operation` (sarcelle), `type-drift` (violet), `type-rpa` (ardoise). Les incidents restent en `ink`. Ces teintes ont été choisies pour ne se confondre ni avec les statuts (`failed`, `warning`), ni avec `signature`, ni avec `impact`. On ne les utilise jamais pour colorer une ligne, une étiquette ou un fond : un type se lit par son icône dans l'interface, et par sa couleur seulement dans un graphique. + +Les autres données catégorielles (environnements, équipes) utilisent la rampe neutre `data-1` à `data-4`, jamais les couleurs de statut. Au-delà de quatre séries, on regroupe le reste en « other ». + +`impact` (magenta, #B5126F en clair, #FF5CB0 en sombre) est la seule couleur hors statut. Elle signale qu'un événement touche les utilisateurs (attribut `impact` de l'événement), quel que soit son statut : un déploiement réussi peut avoir un impact, un échec peut ne pas en avoir. On l'utilise en aplat (`impact` avec `on-impact`) pour la bande du drawer, ou en fond discret (`impact-wash`) pour un marqueur dans une liste. Jamais pour un statut, une catégorie ou une décoration. + +L'impact doit se voir sur chaque page, pas seulement dans le détail : marqueur `ImpactMark` dans chaque ligne d'événement et dans les listes de triage, liseré sur les barres de Streamline, métrique « users impacted » dans les bandes de métriques, segment dédié dans la phrase de synthèse, clé `impact:true` dans les filtres et vue enregistrée « Impact ». + +À ne pas faire : dégradés, violet ou bleu d'accent, badges de toutes les couleurs, vert de succès, couleur sur un élément purement décoratif. + +## Grammaire des statuts + +Un statut s'affiche comme une **étiquette** : un glyphe de 12 px à signe intérieur et un libellé mono, dans un contenant de 22 px. Chaque glyphe porte un signe (✕, !, ✓, arc ouvert), pour rester identifiable en niveaux de gris comme pour un lecteur daltonien. + +| Statut | Glyphe | Étiquette | Poids visuel | +| --- | --- | --- | --- | +| failed | carré plein marqué d'un ✕ | fond `failed-wash`, texte `failed` | fort | +| warning | triangle plein marqué d'un ! | fond `warning-wash`, texte `warning` | fort | +| running | arc ouvert et point, rotation lente | fond `signature`, texte `on-signature` | fort, c'est le présent | +| done | cercle et ✓ | filet plein `tag-line`, texte `muted` | faible | +| open | cercle vide | filet pointillé `tag-line`, texte `muted` | faible | + +Dans une liste, les étiquettes s'alignent sur le bord droit : en descendant la colonne, l'œil ne s'arrête que sur les écarts et sur ce qui est en cours. Le ✓ de done reste gris : il confirme sans célébrer, ce qui garde intact le principe « la couleur signale l'écart ». Ce choix a été testé face à trois autres lectures (glyphes seuls, symboles sans contenant, colonne de statut en tête de ligne). + +Hors des listes, quand l'étiquette serait trop lourde (historique du drawer, légendes, cellules de calendrier), on affiche le glyphe seul, coloré par son ton. Le composant `StatusMark` porte cette grammaire : on ne dessine jamais un statut à la main. + +## Typographie + +Deux familles de la même superfamille, fournies en fichiers variables (OFL) : **Spline Sans** (`--font-sans`) et **Spline Sans Mono** (`--font-mono`). + +- Les chiffres sont toujours en mono, avec `font-variant-numeric: tabular-nums`, sans zéro de remplissage : on écrit `2`, pas `02`. +- Les métadonnées sont en minuscules, en mono `data-sm`, séparées par « · » : `deployment · pré-production · P2 · platform-team`. +- On n'utilise pas de labels en capitales espacées. Un label de métrique s'écrit `events / 24h`, pas `TOTAL EVENTS`. +- Les titres sont en casse de phrase. L'interlettrage est négatif sur les grandes tailles, comme l'indique chaque style. + +## Voix et contenu + +L'interface est en anglais, en casse de phrase, avec des verbes concrets. + +- **Heures** au format 24 h : `15:04`, avec les secondes dans le drawer (`15:04:12`). +- **Dates** au format `07 apr 2026`, ou `today` et `yesterday` quand c'est plus clair. +- **Cibles de déploiement** avec une flèche : `Deploy monitoring v2.5.5 → mco`. +- **Phrase de synthèse** construite selon la formule « ce qui demande une action, puis ce qui est en cours, puis ce qui va bien » : *2 deployments failed and need attention. 1 deployment is running on production. 3 events completed, no scheduling overlaps.* +- **États vides** factuels plutôt qu'enjoués : `No overlaps in the last 7 days.` +- Pas d'emoji, pas de points d'exclamation, pas de « Oops ». + +## Espace et densité + +Grille de 4 px (`space-1` à `space-16`). Les pages ont une marge de `space-8` et les panneaux un padding de `space-5`. Les blocs sont séparés de `space-6`. + +Les listes et tableaux existent en deux densités, réglables par l'utilisateur et mémorisées : + +| Densité | Padding de ligne | Hauteur | Contenu | +| --- | --- | --- | --- | +| Confort (défaut) | `row-pad-comfy` 12 px | ≈ 62 px | titre puis métadonnées sur deux lignes | +| Compact | `row-pad-compact` 6 px | ≈ 32 px | titre et métadonnées sur une ligne, tronqués | + +## Motif : la règle graduée + +Le temps est la matière de Tracker, donc l'axe du temps est le motif de la marque. Le composant `TimeRuler` affiche des graduations sur une trame millimétrée `grid` au pas de 8 px, des repères majeurs à l'heure en `ink`, des marques d'événements colorées selon leur statut, et l'aiguille `needle` surmontée d'un surligneur « now ». On le trouve en tête de Timeline, de Streamline et dans le graphique de vélocité. On ne l'utilise pas comme décor. + +## Iconographie + +Le jeu d'icônes est dessiné pour Tracker. Il est exposé par `Tracker.Icon` et visible dans la carte Icon. + +- Grille de 16 px, trait de 1.5 px, extrémités carrées, angles vifs, sans remplissage. +- Les formes viennent du vocabulaire de l'instrument : axes, graduations, aiguilles, barres. +- On ne trouve ni robot, ni fusée, ni bouclier, ni clé à molette. Le triangle est réservé au statut warning et n'apparaît jamais dans une icône. +- Les glyphes de statut forment une famille à part : extrémités arrondies et signe intérieur, là où les icônes ont des extrémités carrées et restent vides. +- Les icônes servent à la navigation, aux types d'événements, aux faits d'un événement (`service`, `environment`, `owner`, `impact`, `pull-request`, `ticket`) et aux actions. Elles ne décorent pas un titre ou une carte. +- Couleur `muted` au repos, `ink` au survol, `on-signature` sur la page active. + +## Mouvement + +Le mouvement est bref et n'a qu'une seule direction. + +- **Durées** : 120 ms pour les survols et les fondus, 180 ms pour le drawer. +- **Courbe** : `cubic-bezier(0.2, 0, 0, 1)`, sans rebond. +- **Drawer** : il glisse depuis la droite sans voile, pour que la liste reste visible. +- **Palette ⌘K** : elle apparaît en fondu, avec un léger déplacement de 4 px. +- **Statut running** : l'arc ouvert du glyphe tourne en 1.6 s. Avec `prefers-reduced-motion`, plus rien ne bouge. + +## Architecture et patterns + +- **Navigation** : barre latérale de `sidebar-w`, en quatre groupes (operations, services, infrastructure, resources). La page active est surlignée en `signature`. +- **Vues temporelles** : Timeline, Streamline et Calendar restent trois pages, mais partagent une `FilterBar` commune. La période et les filtres suivent l'utilisateur d'une vue à l'autre. +- **Création** : un seul bouton `New` avec menu (event, lock, drift, service, RPA), doublé de la palette ⌘K qui sert aussi à chercher et à naviguer. +- **Détail** : un clic sur un événement ouvre l'`EventDrawer` à droite (`drawer-w`). La liste reste visible et navigable au clavier (↑ ↓), et le drawer suit la sélection. +- **Densité** : un réglage confort ou compact, global, dans le menu utilisateur. +- **Impact** : présent sur toutes les pages qui listent ou résument des événements (voir `ImpactMark`). Une page qui montre des événements sans leur impact est incomplète. + +## Logo + +Le symbole est un T dont la barre est un trait de surligneur : un fût `surface` sur un carré `ink`, barré de `signature`. Le logotype « tracker » est composé en Spline Sans SemiBold, en minuscules, avec un interlettrage de −0.035 em. Les fichiers se trouvent dans le groupe d'assets Logos, en version encre (fonds clairs) et inverse (fonds sombres). On ne recolore pas le logo, on ne remplace pas le chartreuse, et on ne pose jamais le symbole sur un fond `signature`. diff --git a/docs/design-system/assets/logos/README.md b/docs/design-system/assets/logos/README.md new file mode 100644 index 0000000..c956d41 --- /dev/null +++ b/docs/design-system/assets/logos/README.md @@ -0,0 +1,10 @@ +# Logos + +Quatre fichiers vectoriels, avec un logotype vectorisé depuis Spline Sans SemiBold, donc sans dépendance à la police. + +- `tracker-lockup.svg` : symbole et logotype en encre #141311, pour les fonds `paper` et `surface` clairs. +- `tracker-lockup-inverse.svg` : symbole inversé (carré #EDEAE3, fût #141311) et logotype #EDEAE3, pour les fonds sombres. +- `tracker-mark.svg` : symbole seul, carré #141311 et fût #FAF8F3. Sert de favicon et d'avatar. +- `tracker-mark-inverse.svg` : symbole seul pour les fonds sombres. + +La barre du T est toujours `signature` (#D4F53C). La zone de protection autour du logo vaut la largeur du fût du T, soit 6/44 de la hauteur du symbole. Le symbole ne descend pas sous 16 px de haut, et le logotype complet pas sous 20 px. diff --git a/docs/design-system/assets/logos/tracker-lockup-inverse.svg b/docs/design-system/assets/logos/tracker-lockup-inverse.svg new file mode 100644 index 0000000..2f662ac --- /dev/null +++ b/docs/design-system/assets/logos/tracker-lockup-inverse.svg @@ -0,0 +1 @@ +AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpjMTlhMjFiOC1lNjBjLTQwM2ItYjFiOS02Nzk1YzViY2FkNjgAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaE+9GkL0GOIygNt/rwFFlCAAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDpjMzQyZmRhYi03YTBhLTQ4YTUtYmE0Yy02MjA2NWY5YjYwMzdscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNokxog4vlcxNvfbnJhtJJFzgAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggt2QjveJwJs7Lj4bf8NiXcXgz+2w57Ay31zC9CkpEuPOkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaPsTeIYKPe9TttEXRzERH58AAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCD/p1+Uv5L5TD0TjRZ9CKYXtyb0438As3XrPvrWS20anWRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBi3Zmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmMxOWEyMWI4LWU2MGMtNDAzYi1iMWI5LTY3OTVjNWJjYWQ2OC9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOmQ5N2U5NGViLWNjYWMtNDllNy1iN2E3LTEyZmNkNmQzNTM0M3JjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCC3ZCO94nAmzsuPht/w2JdxeDP7bDnsDLfXML0KSkS486JjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggIH5UGJXbBJ4kMCOr31F0N6jPkzK/cdszdXjHOzEfa7aiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggkzXdWWTrT4Nz0XjIGS6nSKRrmT4o8RV/Qr5iUgZeFUJ0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQOLtPBpnNJ9JseNK3xg5KcghP5OyN70hahlUE0YsiPgg4k90GQHQNjbbRMql8FV0yh0xz/TMrfKdZNdx464k7Ys= diff --git a/docs/design-system/assets/logos/tracker-lockup.svg b/docs/design-system/assets/logos/tracker-lockup.svg new file mode 100644 index 0000000..f4e2f85 --- /dev/null +++ b/docs/design-system/assets/logos/tracker-lockup.svg @@ -0,0 +1 @@ +AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTpiM2RjY2M1ZS1hODBlLTQ1ZTYtOTQ1MS0xN2RiNDUwYTcxZmMAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaEjxnfOrN7xQFRxAsucadGAAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo2MTBjMGY4ZC02YmQwLTRlZTEtYTFhZi1kZDZjODBmZGVkMmFscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNoDccCC1i7WNRmu5GYRLv0KAAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggZWrDb18JgLULW7gIPCJ4n/KeDplvNyQMt7S+wLsYbe6kZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaKNvLnWVLN2N/U2eP0eeySkAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCDgK+XcVcxTOxc3I+m0PGT9spBAlxLPhtrcysNvAWia4mRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBi3Zmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOmIzZGNjYzVlLWE4MGUtNDVlNi05NDUxLTE3ZGI0NTBhNzFmYy9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjBmZGRiNTk1LTY4MTAtNDg1Mi1hNmMwLWRhOWQ5NmM3MDRjMnJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCBlasNvXwmAtQtbuAg8Inif8p4OmW83JAy3tL7Auxht7qJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggP1meGqV4vY3zLMCs7KKNXI26MJkZrhiXfkM5P26cQfGiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggOumhzzq9aQeFXREzv8sF3jUsNcqg+v7FYtrBkJKc6rh0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQMnJPkFWonQpvcWA/wspBIHtVXn2NNyymjNO0JswgYLSsbyJUgi7fBwdhcxnEFd8RNiEc/Cpqsva3G4j8N1ZgEA= diff --git a/docs/design-system/assets/logos/tracker-mark-inverse.svg b/docs/design-system/assets/logos/tracker-mark-inverse.svg new file mode 100644 index 0000000..7d05ecd --- /dev/null +++ b/docs/design-system/assets/logos/tracker-mark-inverse.svg @@ -0,0 +1 @@ +AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTozMjM4MzQ4Yy00ODM3LTQ0MzMtYjE1MS02N2E4MTI5MmE3MmEAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaB1WyOZyseph4iOYA+JWsXUAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDpjZGVjMDU1NC03YWEwLTQ0ZjctOWZmOC00MGEyMTNhNmM0MmJscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNo/KLSg3Rf/WKyYSKYnE+ZUwAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggjGgXT/nWTzYzO/rroXE5n7jlPR3wHS7wA4pAbi4/87SkZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaIZTv85VjcY1WEe8vWt+z2AAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCCN8MiJs5xSrWfHupZyogVpX0j0fCUwkWfHRschbDFfVmRuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBiyZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOjMyMzgzNDhjLTQ4MzctNDQzMy1iMTUxLTY3YTgxMjkyYTcyYS9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOjMxOTMyZjkzLWYzNTItNGZmZC04ZTQ3LWI1MWZhNWZjMmYzMHJjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCCMaBdP+dZPNjM7+uuhcTmfuOU9HfAdLvADikBuLj/ztKJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFgg+l1If06lU/TJiUElSEIl7GAAEmKWZcBsDEwXU2TdLj+iY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFgg3mVLKcgGftRcSk5FuJlzfIuslSDNX3K2UWOksHm5rSZ0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQKX+arcn2aC9fdhBs+prhPCNwGfBJ7rf/X9Zh6/9pY1AqtOinKJCB05ocxt3m5IDinTYFGmU7L7LHQPejTLdQk4= diff --git a/docs/design-system/assets/logos/tracker-mark.svg b/docs/design-system/assets/logos/tracker-mark.svg new file mode 100644 index 0000000..ba268e1 --- /dev/null +++ b/docs/design-system/assets/logos/tracker-mark.svg @@ -0,0 +1 @@ +AAAWgmp1bWIAAAAeanVtZGMycGEAEQAQgAAAqgA4m3EDYzJwYQAAABZcanVtYgAAAEdqdW1kYzJtYQARABCAAACqADibcQN1cm46YzJwYTozMTU1YmEzMy1iYmJlLTQ3NDUtYTI5Ni02M2QzZGJmYTY3OTIAAAADl2p1bWIAAAApanVtZGMyYXMAEQAQgAAAqgA4m3EDYzJwYS5hc3NlcnRpb25zAAAAALxqdW1iAAAARGp1bWRjYm9yABEAEIAAAKoAOJtxE2MycGEuaW5ncmVkaWVudC52MwAAAAAYYzJzaMRhYxdwAPJq+X84t2Ifu+sAAABwY2JvcqNpZGM6Zm9ybWF0bWltYWdlL3N2Zyt4bWxqaW5zdGFuY2VJRHgseG1wOmlpZDo1NjI0NjhlYi02NmExLTRiNDgtOGE4OS1mNTQ5YTE4NWEzODlscmVsYXRpb25zaGlwaHBhcmVudE9mAAAB4mp1bWIAAABBanVtZGNib3IAEQAQgAAAqgA4m3ETYzJwYS5hY3Rpb25zLnYyAAAAABhjMnNov0fAz2vo7D5yIRNRXdpHIQAAAZljYm9yomdhY3Rpb25zgqJmYWN0aW9ua2MycGEub3BlbmVkanBhcmFtZXRlcnOha2luZ3JlZGllbnRzgaJjdXJseC1zZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmluZ3JlZGllbnQudjNkaGFzaFggoaxnRNQw9WojCEzy/zc90EYrT7geQfkEF+YnstkgCq6kZmFjdGlvbngdY29tLmFudGhyb3BpYy5jbGF1ZGUucHJvdmlkZWRqcGFyYW1ldGVyc6F4H2NvbS5hbnRocm9waWMub3JpZ2luLWNvbmZpZGVuY2VndW5rbm93bmtkZXNjcmlwdGlvbnhmQ2xhdWRlIHByb3ZpZGVkIHRoaXMgZmlsZSBhdCB0aGUgcmVxdWVzdCBvZiBhIHVzZXIgYW5kIG1heSBoYXZlIGNyZWF0ZWQgb3IgbW9kaWZpZWQgdGhlIGZpbGUgY29udGVudHMubXNvZnR3YXJlQWdlbnShZG5hbWVmQ2xhdWRlcmFsbEFjdGlvbnNJbmNsdWRlZPUAAADIanVtYgAAAEBqdW1kY2JvcgARABCAAACqADibcRNjMnBhLmhhc2guZGF0YQAAAAAYYzJzaHPveTbXUQWit+nnlN4D3AkAAACAY2JvcqVjYWxnZnNoYTI1NmNwYWRNAAAAAAAAAAAAAAAAAGRoYXNoWCBupovRpLT/T8jDZBEozaeBcm8RPJOhKpzZJwCHfK3ZZ2RuYW1lbmp1bWJmIG1hbmlmZXN0amV4Y2x1c2lvbnOBomVzdGFydBiyZmxlbmd0aBkeBAAAAj5qdW1iAAAAJ2p1bWRjMmNsABEAEIAAAKoAOJtxA2MycGEuY2xhaW0udjIAAAACD2Nib3KlY2FsZ2ZzaGEyNTZpc2lnbmF0dXJleE1zZWxmI2p1bWJmPS9jMnBhL3VybjpjMnBhOjMxNTViYTMzLWJiYmUtNDc0NS1hMjk2LTYzZDNkYmZhNjc5Mi9jMnBhLnNpZ25hdHVyZWppbnN0YW5jZUlEeCx4bXA6aWlkOmYzYTE4Y2YxLTkyNDctNGJmMS1hN2E0LTVkNjdhZGZlMzNlM3JjcmVhdGVkX2Fzc2VydGlvbnODomN1cmx4LXNlbGYjanVtYmY9YzJwYS5hc3NlcnRpb25zL2MycGEuaW5ncmVkaWVudC52M2RoYXNoWCChrGdE1DD1aiMITPL/Nz3QRitPuB5B+QQX5iey2SAKrqJjdXJseCpzZWxmI2p1bWJmPWMycGEuYXNzZXJ0aW9ucy9jMnBhLmFjdGlvbnMudjJkaGFzaFggVFn2ZODkEed+erMB4wavnXsoPgrQrcB5fAJoXnhsRpOiY3VybHgpc2VsZiNqdW1iZj1jMnBhLmFzc2VydGlvbnMvYzJwYS5oYXNoLmRhdGFkaGFzaFggde7u9iMOqNpf32QRx5xOfi/HNK/bYBgeO662c2NLcUh0Y2xhaW1fZ2VuZXJhdG9yX2luZm+jZG5hbWVvQW50aHJvcGljIEZpbGVzZ3ZlcnNpb25lMS4wLjBrc3BlY1ZlcnNpb25lMi40LjAAABA4anVtYgAAAChqdW1kYzJjcwARABCAAACqADibcQNjMnBhLnNpZ25hdHVyZQAAABAIY2JvctKEWQISogEmGCFZAgowggIGMIIBjaADAgECAhRA5aAK7sI50L64g/oGQgU9Z1UTADAKBggqhkjOPQQDAzBJMRcwFQYDVQQKEw5BbnRocm9waWMsIFBCQzEuMCwGA1UEAxMlQW50aHJvcGljIENvbnRlbnQgQ3JlZGVudGlhbHMgUm9vdCBDQTAeFw0yNjA4MDcxODQzNTZaFw0yODA4MDYxOTQzNTZaMEQxFzAVBgNVBAoTDkFudGhyb3BpYywgUEJDMSkwJwYDVQQDEyBBbnRocm9waWMgQ2xhdWRlIENvbnRlbnQgU2lnbmluZzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABJh6CmvLUBgFFNU0vUKlOVtE6djd17L5SuwX0LemFisBM3dkd/3cyjxFA3Qo5S46fX0/ihY0VZ7mfb9KF703t5OjWDBWMA4GA1UdDwEB/wQEAwIHgDAVBgNVHSUEDjAMBgorBgEEAYPoXgIBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUzlHiBIFOZFsj+OPEz5o+nMHXXMIwCgYIKoZIzj0EAwMDZwAwZAIwMXMdFJ4BetLLVY7ORuE9noqbbAZOZn/aArXyTwFAZfKrPzxF2vPoJNf1+UCdg1XGAjBwX1zd9WGqYkqmL5SFqw1QySjr1zJfpJM9+1rdDwSPLMOPOjKuiXjoU/pUUeG9RwmhY3BhZFkNngAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAPZYQKRllsHdtZJpbkXl/Tf+K79IBnbERPbOs7fYd8kIz0tKonAafRmJXhzW4WDTbpqy8+zSXM787laffE+RTSgeY+k= diff --git a/docs/design-system/components/Button.md b/docs/design-system/components/Button.md new file mode 100644 index 0000000..01e5510 --- /dev/null +++ b/docs/design-system/components/Button.md @@ -0,0 +1,10 @@ +# Button + +Trois variantes, deux tailles. Le bouton primary en encre est rare : un seul par zone. + +- **primary** (`ink` / `on-ink`) : l'action principale de la page ou du drawer, par exemple `New` dans la barre du haut ou `Edit event` dans le drawer. +- **secondary** (`surface`, bord `rule-strong`) : les actions courantes comme `Lock service` ou `Export`. +- **ghost** : les actions tertiaires et les boutons icône (fermer, copier). +- **sm** (`control-sm`, 30 px) : à utiliser dans les barres de filtres et les en-têtes de panneau. + +Le bouton `New` porte un chevron (`iconRight="chevron-down"`) et ouvre un menu : event, lock, drift, service, RPA. On n'aligne jamais plusieurs boutons « New … » côte à côte. Le chartreuse n'est jamais une couleur de bouton : il marque le présent, pas une action. Le focus clavier dessine un anneau `focus-ring` de 2 px. diff --git a/docs/design-system/components/CommandPalette.md b/docs/design-system/components/CommandPalette.md new file mode 100644 index 0000000..44fbc1e --- /dev/null +++ b/docs/design-system/components/CommandPalette.md @@ -0,0 +1,10 @@ +# CommandPalette + +La palette ⌘K : chercher, créer et naviguer depuis un seul champ. Elle double le bouton `New`. + +- Elle s'ouvre avec ⌘K, ou avec `/` hors d'un champ, au-dessus d'un voile `scrim`. Elle utilise `overlay`, `shadow-overlay` et `radius-lg`. +- Les groupes s'affichent dans un ordre fixe : Jump to, Create, puis les résultats (events, services, teams). +- Chaque création a une lettre de raccourci (`E` event, `L` lock, `D` drift, `S` service, `R` RPA). +- L'item actif prend `signature-wash`. On le déplace avec ↑ ↓, ↵ l'ouvre et esc ferme la palette. +- Un résultat d'événement porte son statut et son heure en indice (`failed · 15:04`). +- Pour une recherche sans résultat, on affiche `No results for “…”` sans illustration. diff --git a/docs/design-system/components/EventDrawer.md b/docs/design-system/components/EventDrawer.md new file mode 100644 index 0000000..4353a45 --- /dev/null +++ b/docs/design-system/components/EventDrawer.md @@ -0,0 +1,24 @@ +# EventDrawer + +Le détail d'un événement, dans un panneau latéral de `drawer-w` ouvert à droite de la liste. Sa mise en page « bandeau » met en avant les faits sur lesquels on agit, tous dans l'en-tête. + +**En-tête, sur fond `sunken` :** + +- Le type en étiquette (icône du type et nom : deployment, operation, drift, incident, rpa usage), le `StatusMark`, l'heure, puis le bouton de fermeture. +- Le titre, en style `title`. +- Les pastilles service, environnement et owner, avec leurs icônes. Chaque pastille est un bouton qui filtre les vues temporelles sur sa valeur (`onFilter('service' | 'env' | 'owner', valeur)`). Les stakeholders figurent dans l'infobulle de l'owner. La priorité suit, en pastille discrète non cliquable. +- Les liens : `PR #1287` et la clé du ticket, en boutons qui ouvrent la source dans un nouvel onglet. Un lien absent (`null`) devient un emplacement en pointillé avec un bouton +. On le montre volontairement, pour inciter à compléter l'événement. + +**Bande d'impact.** Si `impact` vaut `true`, une bande pleine largeur en `impact` / `on-impact` affiche « Users impacted » et une note (canal notifié, status page). Si `impact` vaut `false`, une ligne discrète indique « No user impact declared ». L'impact est indépendant du statut : un déploiement réussi peut avoir un impact. C'est la seule couleur hors statut du système. + +**Corps :** la phrase de résumé si elle existe, puis `details` pour les champs secondaires (source, horaires, notifications, identifiant), et `history`, le changelog avec ses glyphes. + +**Pied :** au plus trois actions, avec une seule primary. + +**Comportement :** + +- Il n'y a pas de voile : la liste reste visible, et ↑ ↓ dans la liste font défiler le drawer d'un événement à l'autre. esc le ferme. +- Il glisse depuis la droite en 180 ms, `cubic-bezier(0.2, 0, 0, 1)`, et apparaît sans animation avec `prefers-reduced-motion`. +- Le drawer peut s'ouvrir via une URL (`?event=`), pour qu'un lien partagé ouvre directement le détail. + +Rétrocompatibilité : sans service, environnement ni owner, l'en-tête revient à la ligne de métadonnées `meta`, et la zone de liens n'apparaît que si `pullRequest` ou `ticket` est fourni. diff --git a/docs/design-system/components/EventRow.md b/docs/design-system/components/EventRow.md new file mode 100644 index 0000000..96f0b25 --- /dev/null +++ b/docs/design-system/components/EventRow.md @@ -0,0 +1,14 @@ +# EventRow + +La ligne d'événement, la brique la plus répétée de Tracker. On la trouve dans Timeline, dans le flux d'activité du dashboard, dans Overlaps et dans les résultats de recherche. + +Sa structure est fixe sur trois colonnes : l'heure en mono `muted`, le titre en `body-strong` avec la ligne de métadonnées en dessous, puis l'étiquette `StatusMark` alignée à droite sur la première ligne du titre. Il n'y a pas de glyphe en tête de ligne : le statut se lit en descendant le bord droit, où seuls les écarts (failed, warning) et ce qui est en cours (running) ressortent. + +- **Densité confort** (par défaut, `row-pad-comfy`) : le titre et les métadonnées tiennent sur deux lignes. +- **Densité compacte** (`density="compact"`, `row-pad-compact`) : tout tient sur une ligne, les métadonnées sont tronquées. +- La ligne sélectionnée prend `signature-wash`. Une sélection ouvre l'`EventDrawer`, et ↑ ↓ déplacent la sélection. +- `impact` ajoute un `ImpactMark` compact, en magenta, juste avant l'étiquette de statut. L'impact se lit ainsi sur le même bord droit que le statut, sans changer la structure de la ligne. +- Les métadonnées se passent sous forme de liste ordonnée : type, environnement, priorité, équipe. Elles sont jointes par « · ». +- Pas de pastilles colorées pour le type ou l'environnement. La couleur appartient au statut. + +Utilisez `EventList` pour une liste : elle porte le rôle `listbox` et gère la sélection. diff --git a/docs/design-system/components/FilterBar.md b/docs/design-system/components/FilterBar.md new file mode 100644 index 0000000..9bcd20a --- /dev/null +++ b/docs/design-system/components/FilterBar.md @@ -0,0 +1,8 @@ +# FilterBar + +La barre de filtres partagée par Timeline, Streamline et Calendar. La période et les filtres suivent l'utilisateur d'une vue à l'autre. + +- Chaque filtre est un jeton `clé valeur` en mono (`env production`), bordé de `rule-strong`, avec un bouton de retrait accessible. +- `+ Filter` ouvre un sélecteur de clés : type, environment, priority, status, owner, service. +- `scope` rappelle discrètement où s'appliquent les filtres (`synced · timeline, streamline, calendar`). C'est ce qui rend la synchronisation compréhensible. +- L'état des filtres vit dans l'URL, pour qu'une vue filtrée puisse se partager par lien. diff --git a/docs/design-system/components/Highlight.md b/docs/design-system/components/Highlight.md new file mode 100644 index 0000000..8c435ee --- /dev/null +++ b/docs/design-system/components/Highlight.md @@ -0,0 +1,9 @@ +# Highlight + +Le surligneur : un aplat `signature` sous un texte `on-signature`, signature visuelle de Tracker. + +Il ne sert qu'à marquer le présent : ce qui est en cours dans une phrase de synthèse, le libellé « now » de la règle, le statut running. Si deux surligneurs se disputent l'attention sur un écran, l'un des deux est de trop. + +- Dans du texte courant, utilisez la version par défaut. +- Pour une donnée (`running`, `now 15:31`), utilisez `data`, en mono 11 px. +- Ne jamais l'utiliser pour mettre en valeur un mot important, une nouveauté ou une promotion. diff --git a/docs/design-system/components/Icon.md b/docs/design-system/components/Icon.md new file mode 100644 index 0000000..78c4d7d --- /dev/null +++ b/docs/design-system/components/Icon.md @@ -0,0 +1,10 @@ +# Icon + +Le jeu d'icônes dessiné pour Tracker : grille de 16 px, trait de 1.5 px, extrémités carrées, angles vifs. + +Les formes viennent de l'instrument de mesure : axes, graduations, aiguilles, barres. Chaque icône hérite de `currentColor`. + +- Au repos, une icône est en `muted`. Elle passe en `ink` au survol, et en `on-signature` dans l'item de navigation actif. +- Une icône décorative n'a pas de `title` et reste masquée aux lecteurs d'écran. Une icône seule dans un bouton a besoin d'un `aria-label` sur le bouton. +- Le triangle est réservé au statut warning, donc aucune icône n'en contient. +- Pour ajouter une icône, on la dessine sur la même grille, avec les mêmes extrémités, et on l'ajoute à `Tracker.icons`. diff --git a/docs/design-system/components/ImpactMark.md b/docs/design-system/components/ImpactMark.md new file mode 100644 index 0000000..d25dc73 --- /dev/null +++ b/docs/design-system/components/ImpactMark.md @@ -0,0 +1,21 @@ +# ImpactMark + +Le marqueur d'impact utilisateur : l'icône impact (des ondes) en magenta `impact` sur un fond `impact-wash`, à la même hauteur de 22 px que l'étiquette de statut. + +Il signale qu'un événement touche les utilisateurs (attribut `impact` du modèle), quel que soit son statut. Il ne remplace jamais le statut : il s'affiche à côté de lui. + +Où il apparaît, sur chaque page : + +- **Lignes d'événement** (`EventRow` avec `impact`) : version compacte, juste avant l'étiquette de statut. +- **Listes de triage et d'agenda** (Needs attention, Up next) : version compacte à côté du titre. +- **Barres de Streamline** : liseré magenta en bas de la barre, et version compacte dans le libellé. +- **Métriques** : la valeur « users impacted » en `tone="impact"`. +- **Drawer** : la bande pleine largeur « Users impacted » (portée par `EventDrawer`). +- **Filtres** : la clé `impact:true` dans la barre de filtre, et une vue enregistrée « Impact ». + +Variantes : + +- **`compact`** : l'icône seule dans un carré de 22 px, avec « Users impacted » comme nom accessible et comme infobulle. C'est la variante par défaut dans les listes. +- **Avec libellé** (`text`, « impact » par défaut) : pour les légendes et les en-têtes, quand la place le permet. + +On ne l'utilise pas pour une alerte générique, ni pour une notion de gravité ou de priorité. diff --git a/docs/design-system/components/Kbd.md b/docs/design-system/components/Kbd.md new file mode 100644 index 0000000..c6f1384 --- /dev/null +++ b/docs/design-system/components/Kbd.md @@ -0,0 +1,5 @@ +# Kbd + +Une touche ou un raccourci clavier, en mono 11 px dans un filet `rule`. + +À utiliser dans le champ de recherche (`⌘K`), dans les items de la palette et dans les infobulles. On écrit les symboles Mac (`⌘`, `⌥`, `↵`) et on laisse l'application les traduire sur Windows et Linux (`Ctrl`). diff --git a/docs/design-system/components/Logo.md b/docs/design-system/components/Logo.md new file mode 100644 index 0000000..b4dd976 --- /dev/null +++ b/docs/design-system/components/Logo.md @@ -0,0 +1,10 @@ +# Logo + +Le symbole « T surligné » et le logotype « tracker », rendus avec les tokens pour suivre le thème. + +Utilisez `Logo` dans l'interface (barre latérale, écran de connexion). Pour un usage hors produit (README, slides, favicon), prenez les fichiers SVG du groupe d'assets Logos. + +- `size` règle la hauteur du symbole. Le logotype suit à 78 % de cette hauteur. +- `word={false}` affiche le symbole seul, par exemple dans une barre latérale repliée. +- Le carré utilise `ink`, le fût `surface` et la barre `signature`. En thème sombre, le carré devient clair automatiquement. +- Ne pas recolorer, ne pas poser sur `signature`, ne pas descendre sous 16 px. diff --git a/docs/design-system/components/MetricStrip.md b/docs/design-system/components/MetricStrip.md new file mode 100644 index 0000000..2a25fe6 --- /dev/null +++ b/docs/design-system/components/MetricStrip.md @@ -0,0 +1,10 @@ +# MetricStrip + +La bande de métriques, posée dans un creux `sunken` sous la phrase de synthèse. Elle compte trois à cinq valeurs. + +- Le libellé est en mono minuscule (`events / 24h`), la valeur en style `metric`, et une précision en `caption` peut la compléter (`3 of 5 finished`). +- `tone="failed"` ou `tone="warning"` ne s'applique que si la valeur elle-même est un problème, par exemple un nombre d'échecs supérieur à zéro. +- `tone="impact"` colore en magenta la métrique « users impacted », et seulement quand elle est supérieure à zéro. Elle a sa place sur chaque page qui résume une période. +- Un taux de succès reste neutre, même à 100 %. +- Pas de zéros de remplissage, pas d'icône décorative, pas de petite flèche de tendance colorée. +- **Comparaison avec la période précédente** : `delta` affiche l'écart en mono sous la valeur (« −3.2 pts vs prev. », « +7 min vs prev. »), et `trend` dit si c'est mieux ou moins bien. Seul `worse` prend une couleur (ambre, avec le glyphe warning). `better` reste neutre, comme tout ce qui va bien. Le signe du delta suit la valeur, pas le jugement : un taux d'échec qui baisse s'écrit « −3.2 pts » avec `trend: 'better'`. diff --git a/docs/design-system/components/SegmentedControl.md b/docs/design-system/components/SegmentedControl.md new file mode 100644 index 0000000..9da2bfd --- /dev/null +++ b/docs/design-system/components/SegmentedControl.md @@ -0,0 +1,5 @@ +# SegmentedControl + +Un choix exclusif parmi deux à cinq options courtes : période, densité, vue. + +Les options sont en mono, et l'option active se pose en `surface` dans une piste `sunken`. Pour plus de cinq options, ou des libellés longs, utilisez un menu. diff --git a/docs/design-system/components/Sidebar.md b/docs/design-system/components/Sidebar.md new file mode 100644 index 0000000..3a92139 --- /dev/null +++ b/docs/design-system/components/Sidebar.md @@ -0,0 +1,9 @@ +# Sidebar + +La navigation principale, en quatre groupes, avec des libellés de groupe en mono minuscule. + +- La page active est surlignée en `signature`, avec le texte et l'icône en `on-signature`. C'est le « vous êtes ici » de la marque. +- `count` n'affiche qu'un nombre qui appelle une action, par exemple les locks actifs ou les drifts ouverts. On ne met pas de compteur sur une page de consultation. +- La largeur vaut `sidebar-w`. Sous 960 px, la barre se replie en symbole seul, avec les icônes et des infobulles. +- **`collapsed`** donne un rail de 60 px : le symbole du logo, les icônes seules (le nom devient infobulle et nom accessible), les groupes séparés par un filet, les compteurs en petites pastilles. On l'utilise par défaut sur les pages plein écran (Architecture, Streamline en grand) et sous 960 px de large. +- `onToggle` affiche en bas un bouton pour replier ou déplier la barre. Le choix de l'utilisateur est mémorisé par page. diff --git a/docs/design-system/components/StatusMark.md b/docs/design-system/components/StatusMark.md new file mode 100644 index 0000000..222e138 --- /dev/null +++ b/docs/design-system/components/StatusMark.md @@ -0,0 +1,23 @@ +# StatusMark + +L'étiquette de statut : un glyphe à signe intérieur et un libellé mono, dans un contenant de 22 px. C'est le seul endroit où les couleurs `failed` et `warning` apparaissent. + +| status | glyphe (12 px dans l'étiquette) | étiquette | +| --- | --- | --- | +| failed | carré plein marqué d'un ✕ | fond `failed-wash`, texte `failed`, graisse 500 | +| warning | triangle plein marqué d'un ! | fond `warning-wash`, texte `warning`, graisse 500 | +| running | arc ouvert et point central, en rotation lente | fond `signature`, texte `on-signature`, graisse 500 | +| done | cercle et ✓ | filet plein `tag-line`, texte `muted`, graisse 400 | +| open | cercle vide | filet pointillé `tag-line`, texte `muted`, graisse 400 | + +**Pourquoi des étiquettes.** Dans une liste, les écarts doivent se repérer de loin, sans lire. Les deux étiquettes lavées (failed, warning) et le surligneur (running) ressortent seules. Les neutres (done, open) restent de simples filets gris, que l'œil saute. Le ✓ de done reste gris : il confirme, il ne célèbre pas. + +**Pourquoi un signe dans chaque glyphe.** À petite taille, un carré et un cercle se confondent. Le ✕, le !, le ✓ et l'arc ouvert rendent chaque statut identifiable en niveaux de gris ou pour un lecteur daltonien. Les glyphes ont des extrémités arrondies, ce qui les distingue du jeu d'icônes, dessiné en extrémités carrées. + +Règles d'usage : + +- Dans une ligne d'événement, l'étiquette se place à droite, alignée sur la première ligne du titre. Il n'y a pas de glyphe en tête de ligne. +- `label={false}` affiche le glyphe seul, à 14 px par défaut, coloré par son ton (`tk-tone--*`), avec le libellé comme nom accessible. On l'utilise là où l'étiquette serait trop lourde : historique du drawer, légendes de graphiques, cellules de calendrier. +- Une seule étiquette par ligne. Pas d'étiquette pour le type, l'environnement ou la priorité, qui restent dans la ligne de métadonnées. +- Le libellé ne se traduit pas et reste en minuscules : `failed`, `warning`, `running`, `done`, `open`. +- La rotation de running s'arrête avec `prefers-reduced-motion`. diff --git a/docs/design-system/components/TimeRuler.md b/docs/design-system/components/TimeRuler.md new file mode 100644 index 0000000..855d78f --- /dev/null +++ b/docs/design-system/components/TimeRuler.md @@ -0,0 +1,9 @@ +# TimeRuler + +La règle graduée, motif de la marque et axe du temps de Tracker. + +Elle se compose d'une trame millimétrée `grid` au pas de 8 px, de repères horaires en `ink` avec leur libellé mono, de marques d'événements colorées selon leur statut, et de l'aiguille `needle` surmontée d'un surligneur « now ». + +- On la trouve en tête de Timeline et de Streamline, et sous le graphique de vélocité. Les graduations doivent toujours correspondre aux données affichées dessous. +- `startHour` et `endHour` définissent la fenêtre en heures (`from` et `to` restent acceptés comme alias, mais `from` est réservé sur `x-import` : utilisez `start-hour` et `end-hour` dans un canevas). `now` place l'aiguille au format `HH:MM`, et `marks` place les événements. +- Ce n'est pas un élément décoratif : on ne l'utilise pas dans un en-tête sans données temporelles. diff --git a/docs/design-system/fonts/OFL-SplineSans.txt b/docs/design-system/fonts/OFL-SplineSans.txt new file mode 100644 index 0000000..ee2bacd --- /dev/null +++ b/docs/design-system/fonts/OFL-SplineSans.txt @@ -0,0 +1,93 @@ +Copyright 2021 The Spline Sans Project Authors (https://github.com/SorkinType/SplineSans) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/design-system/fonts/OFL-SplineSansMono.txt b/docs/design-system/fonts/OFL-SplineSansMono.txt new file mode 100644 index 0000000..ebcad12 --- /dev/null +++ b/docs/design-system/fonts/OFL-SplineSansMono.txt @@ -0,0 +1,93 @@ +Copyright 2022 The Spline Sans Mono Project Authors (https://github.com/SorkinType/SplineSansMono) + +This Font Software is licensed under the SIL Open Font License, Version 1.1. +This license is copied below, and is also available with a FAQ at: +https://scripts.sil.org/OFL + + +----------------------------------------------------------- +SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007 +----------------------------------------------------------- + +PREAMBLE +The goals of the Open Font License (OFL) are to stimulate worldwide +development of collaborative font projects, to support the font creation +efforts of academic and linguistic communities, and to provide a free and +open framework in which fonts may be shared and improved in partnership +with others. + +The OFL allows the licensed fonts to be used, studied, modified and +redistributed freely as long as they are not sold by themselves. The +fonts, including any derivative works, can be bundled, embedded, +redistributed and/or sold with any software provided that any reserved +names are not used by derivative works. The fonts and derivatives, +however, cannot be released under any other type of license. The +requirement for fonts to remain under this license does not apply +to any document created using the fonts or their derivatives. + +DEFINITIONS +"Font Software" refers to the set of files released by the Copyright +Holder(s) under this license and clearly marked as such. This may +include source files, build scripts and documentation. + +"Reserved Font Name" refers to any names specified as such after the +copyright statement(s). + +"Original Version" refers to the collection of Font Software components as +distributed by the Copyright Holder(s). + +"Modified Version" refers to any derivative made by adding to, deleting, +or substituting -- in part or in whole -- any of the components of the +Original Version, by changing formats or by porting the Font Software to a +new environment. + +"Author" refers to any designer, engineer, programmer, technical +writer or other person who contributed to the Font Software. + +PERMISSION & CONDITIONS +Permission is hereby granted, free of charge, to any person obtaining +a copy of the Font Software, to use, study, copy, merge, embed, modify, +redistribute, and sell modified and unmodified copies of the Font +Software, subject to the following conditions: + +1) Neither the Font Software nor any of its individual components, +in Original or Modified Versions, may be sold by itself. + +2) Original or Modified Versions of the Font Software may be bundled, +redistributed and/or sold with any software, provided that each copy +contains the above copyright notice and this license. These can be +included either as stand-alone text files, human-readable headers or +in the appropriate machine-readable metadata fields within text or +binary files as long as those fields can be easily viewed by the user. + +3) No Modified Version of the Font Software may use the Reserved Font +Name(s) unless explicit written permission is granted by the corresponding +Copyright Holder. This restriction only applies to the primary font name as +presented to the users. + +4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font +Software shall not be used to promote, endorse or advertise any +Modified Version, except to acknowledge the contribution(s) of the +Copyright Holder(s) and the Author(s) or with their explicit written +permission. + +5) The Font Software, modified or unmodified, in part or in whole, +must be distributed entirely under this license, and must not be +distributed under any other license. The requirement for fonts to +remain under this license does not apply to any document created +using the Font Software. + +TERMINATION +This license becomes null and void if any of the above conditions are +not met. + +DISCLAIMER +THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT +OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE +COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, +INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL +DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING +FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM +OTHER DEALINGS IN THE FONT SOFTWARE. diff --git a/docs/design-system/fonts/SplineSans.ttf b/docs/design-system/fonts/SplineSans.ttf new file mode 100644 index 0000000..00a7644 Binary files /dev/null and b/docs/design-system/fonts/SplineSans.ttf differ diff --git a/docs/design-system/fonts/SplineSansMono.ttf b/docs/design-system/fonts/SplineSansMono.ttf new file mode 100644 index 0000000..41f36d3 Binary files /dev/null and b/docs/design-system/fonts/SplineSansMono.ttf differ diff --git a/docs/design-system/mockups/README.md b/docs/design-system/mockups/README.md new file mode 100644 index 0000000..e2324f6 --- /dev/null +++ b/docs/design-system/mockups/README.md @@ -0,0 +1,21 @@ +# Maquettes de pages + +Ces fichiers sont les maquettes interactives validées, exportées du canevas de conception. Ce sont des **spécifications**, pas du code à réutiliser. + +Format d'un fichier `.dc.html` : + +- Le balisage HTML décrit la mise en page exacte : structure, espacements, tailles, ordre des éléments, textes de l'interface. +- `{{ … }}` est une valeur calculée. `` répète un bloc, `` l'affiche sous condition. +- `` monte un composant du design system avec ses props (en kebab-case dans l'attribut, en camelCase dans le composant). C'est la liste des composants à utiliser à cet endroit. +- Le ` + + + + + + + + + + +
+ +
+ +
+ +
+
+ + New +
+ +
+
+

Architecture

+ {{countLine}} +
+
+ + +
+
+ +
+ + {{fi.name}} + {{fi.hint}} +
+
+
No service matches “{{findText}}”
+
+
+
+
+ + + +
+ +
+ +
+
+ + +
+
+ + + + + +
+ + + + + + + + +
+
+
+
+
+ + +
+
+
+ #serviceownersladirectbreaks if down +
+ +
+ {{r.rank}} + {{r.name}}{{r.platform}} + {{r.owner}} + {{r.sla}} + {{r.direct}} + + + {{r.total}} · {{r.crit}} crit + + Simulate +
+
+
+
+
+
+ + +
+ + Select a service to simulate its failure + arrows point to what a service calls + + + down + breaks directly + breaks in cascade + unaffected + + + selected + needed directly + needed in cascade + +
+
+ − + {{zoomLabel}} + + + Fit +
+
+ F find + M switch mode + esc clear +
+
+
+ + + + +
+
+
+
+ + + diff --git a/docs/design-system/mockups/compiled-tokens.css b/docs/design-system/mockups/compiled-tokens.css new file mode 100644 index 0000000..c4f3b39 --- /dev/null +++ b/docs/design-system/mockups/compiled-tokens.css @@ -0,0 +1,37 @@ +/* Tracker — CSS variables compiled from the Tracker design system tokens.json (light first, dark under data-theme). */ +:root, [data-theme="light"] { + --paper: #F3F1EC; --surface: #FAF8F3; --sunken: #F1EEE7; --overlay: #FCFBF8; --scrim: rgba(20, 19, 17, 0.28); + --rule: #DCD8CF; --rule-soft: #E7E3DA; --rule-strong: #8C867A; --grid: #E9E5DC; + --ink: #141311; --ink-hover: #2E2C28; --on-ink: #FAF8F3; --muted: #615C52; + --signature: #D4F53C; --on-signature: #141311; --signature-wash: #EEF5CC; --needle: #141311; --focus-ring: #141311; + --failed: #C2261C; --failed-wash: #FBE4DF; --warning: #8F5A00; --warning-wash: #F7EBD3; --on-status: #FAF8F3; --tag-line: #B5AFA3; + --impact: #B5126F; --on-impact: #FAF8F3; --impact-wash: #FBE3F0; + --chart-done: #8C867A; --chart-open: #D3CEC4; + --data-1: #141311; --data-2: #5C574D; --data-3: #9C968A; --data-4: #CFCAC0; + --type-deployment: #3355D6; --type-operation: #0E8A80; --type-drift: #7A4CC9; --type-rpa: #5F7186; + --shadow-overlay: 0 1px 0 rgba(20, 19, 17, 0.04), 0 16px 40px rgba(20, 19, 17, 0.14); + /* canvas-only extension, candidate for the system: overlap hatch */ + --overlap-hatch: rgba(143, 90, 0, 0.16); + --lock-hatch: rgba(20, 19, 17, 0.07); +} +[data-theme="dark"] { + --paper: #131210; --surface: #1B1A17; --sunken: #22211D; --overlay: #211F1C; --scrim: rgba(0, 0, 0, 0.55); + --rule: #2E2C27; --rule-soft: #262420; --rule-strong: #6E695F; --grid: #25231F; + --ink: #EDEAE3; --ink-hover: #D2CEC5; --on-ink: #141311; --muted: #A39E93; + --signature: #D4F53C; --on-signature: #141311; --signature-wash: #2A2F16; --needle: #D4F53C; --focus-ring: #D4F53C; + --failed: #FF7A6B; --failed-wash: #3D1D18; --warning: #F0B44A; --warning-wash: #382A12; --on-status: #141311; --tag-line: #55514A; + --impact: #FF5CB0; --on-impact: #141311; --impact-wash: #3A1529; + --chart-done: #6E695F; --chart-open: #3A3732; + --data-1: #EDEAE3; --data-2: #A39E93; --data-3: #6E695F; --data-4: #45423B; + --type-deployment: #7F9BFF; --type-operation: #3FC7BA; --type-drift: #B39BFF; --type-rpa: #9DB0C4; + --shadow-overlay: 0 16px 40px rgba(0, 0, 0, 0.55); + --overlap-hatch: rgba(240, 180, 74, 0.16); + --lock-hatch: rgba(237, 234, 227, 0.07); +} +:root { + --font-sans: "Spline Sans", "Helvetica Neue", Arial, sans-serif; + --font-mono: "Spline Sans Mono", ui-monospace, "SF Mono", Menlo, Consolas, monospace; + --space-1: 4px; --space-2: 8px; --space-3: 12px; --space-4: 16px; --space-5: 20px; --space-6: 24px; --space-8: 32px; --space-16: 64px; + --radius-xs: 3px; --radius-sm: 5px; --radius-md: 6px; --radius-lg: 8px; + --control-md: 38px; --control-sm: 30px; --row-pad-comfy: 12px; --row-pad-compact: 6px; --sidebar-w: 232px; --drawer-w: 480px; +} diff --git a/docs/design-system/mockups/dashboard.dc.html b/docs/design-system/mockups/dashboard.dc.html new file mode 100644 index 0000000..963dc8b --- /dev/null +++ b/docs/design-system/mockups/dashboard.dc.html @@ -0,0 +1,374 @@ + + + + +Tracker — Dashboard + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ + New +
+ +
+
+ +
+
+

Dashboard

+ + + live · updated 15:31 · tue 07 apr 2026 + +
+
+ + +
+
+ + +

2 deployments failed and need attention. 1 deployment is running on production. 2 events are impacting users. 2 scheduling overlaps ahead, the first at 16:30. 3 events completed.

+
+ +

Nothing failed for payments-team today. 1 deployment is running on production and is impacting users. 1 overlap ahead at 16:30 on checkout.

+
+ + + +
+
+
+
+

Needs attention

+ {{attentionCount}} +
+ Open in Timeline → +
+ +
+ + + +
+ + + {{n.title}} + + {{n.meta}} +
+ {{n.action}} +
+
+ +

Nothing needs attention for this scope.

+
+
+ +
+
+
+

Up next

+ today +
+ Open in Streamline → +
+ +
+ {{u.range}} +
+ + + {{u.title}} + + {{u.meta}} +
+ + + + +
+
+
+
+ +
+
+
+

Activity

+ {{shownCount}} of {{scopeCount}} events · last 24h +
+ Open in Timeline with these filters → +
+ +
+
+ + + +
+ + hour{{hourLabel}} + + + + +
+ +
+
+ + + +
+
+ + {{h.label}} + +
+
+ +
+ +
+ +
+ No events match these filters. + Clear filters +
+
+
+
+ + + + +
+
+ + +
+ +
+ +
+
+
+
+
+ + + diff --git a/docs/design-system/mockups/insights.dc.html b/docs/design-system/mockups/insights.dc.html new file mode 100644 index 0000000..f79e192 --- /dev/null +++ b/docs/design-system/mockups/insights.dc.html @@ -0,0 +1,321 @@ + + + + +Tracker — Insights + + + + + + + + + + + +
+ +
+ +
+ +
+
+ + New +
+ +
+ +
+
+

Insights

+ 09 mar – 07 apr 2026 · compared with 07 feb – 08 mar +
+
+
+ + All environments, teams and services + Filter +
+ +
+
+ + + +
+
+
+
+

Activity

+ changes per day · {{activityTotal}} +
+
+ + + +
+
+
+
+ +
+ + + +
+
+
+ +
+ + {{d.label}} + +
+
+
+ previous 30 days, same weekday + weekend +
+
+ +
+
+

By type

+ vs previous 30 days +
+ +
+ +
+ {{ty.label}} + +
+ {{ty.count}} + {{ty.delta}} +
+
+
+
+ +
+
+
+

Reliability and impact by service

+ top 11 of 17 services · sorted by {{sortLabel}} +
+ +
+
+
+
+ servicechangesfailure rateincidentsrestoreuser impact +
+ +
+ {{sv.name}}{{sv.owner}} + {{sv.changes}} · {{sv.deploys}} dep + + + {{sv.rate}} + + {{sv.incidents}} {{sv.incDelta}} + {{sv.restore}} + + + + {{sv.impact}} + + + Timeline + · + Simulate + +
+
+
+
+
failure rate = failed deployments ÷ deployments · restore = median incident duration · user impact = time spent by events flagged impact
+
+ +
+
+
+
+

12-month trend

+ may 2025 – apr 2026 +
+ {{trendSentence}} +
+ +
+
+
+ +
+ {{tr.value}} + +
+
+
+
+ + {{tr.month}} + +
+
+
+
+
+
+
+ + + diff --git a/docs/design-system/mockups/search-and-filters.dc.html b/docs/design-system/mockups/search-and-filters.dc.html new file mode 100644 index 0000000..05b25a8 --- /dev/null +++ b/docs/design-system/mockups/search-and-filters.dc.html @@ -0,0 +1,132 @@ + + + + +Tracker — Recherche et filtres + + + + + + + + + + + +
+ +
+

filtrer ce que je regarde · barre de filtre de page

+ +
+ 1 · focus, champ vide +
+ + Filter by env, status, type, team, service — or type to search + / +
+
+
+
recent
+
status:failed env:productionyesterday
+
owner:infra-team -status:donelast week
+
filter by
+
env:production, pré-production…
+
status:failed, running, open…
+
owner:payments-team…
+
impact:true3 events touch users
+
+
+
+ +
+ 2 · une clé tapée, les valeurs proposées avec leur nombre +
+ + status:f + / +
+
+
+
status
+
failed2 in view↵
+
+ +
+
+ +
+ 3 · filtres appliqués, résultat et vue enregistrable +
+ + envproduction, pré-production + -status≠ done + checkout + Clear +
+
+ 1 event · 1 lane · filtered · synced to timeline, calendar + Save as view +
+
+ +
+ 4 · aucun résultat, on propose quoi retirer +
+ No scheduled work matches service:checkout status:failed between 12:00 and 20:00. +
+ try without + status:failed · 1 result + service:checkout · 2 results + Clear all +
+
+
+
+ +
+

aller quelque part · recherche globale ⌘K

+
+ « check » trouve un service, un événement, une page +
+ +
+
+ +
+ syntaxe et raccourcis, communs à toutes les pages +
+
env:production
one value
+
env:production,mco
any of these values
+
-status:done
exclude a value
+
impact:true
events that touch users, whatever their status
+
"schema migration"
exact phrase in titles
+
/
focus the page filter
+
⌘K
search and jump anywhere
+
⌫ on empty field
remove the last filter
+
G then D, T, S
go to dashboard, timeline, streamline
+
+
+
+
+
+ + + diff --git a/docs/design-system/mockups/streamline.dc.html b/docs/design-system/mockups/streamline.dc.html new file mode 100644 index 0000000..91099f3 --- /dev/null +++ b/docs/design-system/mockups/streamline.dc.html @@ -0,0 +1,650 @@ + + + + +Tracker — Streamline + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ + New +
+ +
+
+ +
+
+

Streamline

+ scheduled work across services · tue 07 apr 2026 +
+
+ Today + ← + → + {{rangeLabel}} + +
+
+ + + +
+
+
+ + + {{t.key}}{{t.text}} + + + + Clear + + / +
+ +
+
+ +
+ + +
+ + + {{it.label}} + {{it.hint}} +
+
+
+
+
+ +
+
+
+ + +
+ +
+ {{resultLine}} + filters synced · timeline, streamline, calendar +
+ + +
+ + {{ovHeadline}} + {{ovDetail}} + + + P + Previous + + + Next overlap + N + +
+
+ +
+
+ + + + + +
+
0006121824
+
+ +
+
+
+
+
{{groupHeading}}
+
+ + + + +
+
+ + +
+
+
+ {{sec.name}} + {{sec.count}} +
+
+
+ +
+
+ {{ln.name}} + {{ln.sub}} +
+
+ + + {{lk.label}} + + + + + + + + + + + + {{b.label}} + + + + +
+
+
+
+
+ +
+ No scheduled work matches these filters in this period. + Clear filters +
+
+
+
+
+ +
+ users impacted + overlap + + ←→ pan + −+ zoom + T today + NP overlaps + / filter + GD dashboard +
+
+ + + + + + + + +
+
+ + +
+ +
+ +
+
+
+
+
+ + + diff --git a/docs/design-system/reference/bundle.css b/docs/design-system/reference/bundle.css new file mode 100644 index 0000000..c8077ab --- /dev/null +++ b/docs/design-system/reference/bundle.css @@ -0,0 +1,197 @@ +/* Tracker — component styles. Every value comes from tokens.css. */ +body { margin: 0; font-family: var(--font-sans); color: var(--ink); background: var(--paper); -webkit-font-smoothing: antialiased; } +.tk-mono { font-family: var(--font-mono); font-variant-numeric: tabular-nums; } +.tk-stack { display: flex; flex-direction: column; gap: var(--space-4); padding: var(--space-5); } +.tk-inline { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-3); padding: var(--space-5); } +.tk-panel { background: var(--surface); border: 1px solid var(--rule); border-radius: var(--radius-md); } + +/* Icon */ +.tk-icon { display: inline-block; flex: none; vertical-align: middle; } + +/* Button */ +.tk-btn { display: inline-flex; align-items: center; justify-content: center; gap: var(--space-2); height: var(--control-md); padding: 0 14px; border: 1px solid transparent; border-radius: var(--radius-md); font: inherit; font-size: 14px; font-weight: 500; line-height: 1; white-space: nowrap; cursor: pointer; transition: background-color 120ms cubic-bezier(0.2, 0, 0, 1), border-color 120ms cubic-bezier(0.2, 0, 0, 1); } +.tk-btn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +.tk-btn--primary { background: var(--ink); color: var(--on-ink); } +.tk-btn--primary:hover { background: var(--ink-hover); } +.tk-btn--secondary { background: var(--surface); color: var(--ink); border-color: var(--rule-strong); } +.tk-btn--secondary:hover { background: var(--sunken); } +.tk-btn--ghost { background: transparent; color: var(--ink); } +.tk-btn--ghost:hover { background: var(--sunken); } +.tk-btn--sm { height: var(--control-sm); padding: 0 10px; font-size: 13px; } +.tk-btn--icon { width: var(--control-md); padding: 0; } +.tk-btn--icon.tk-btn--sm { width: var(--control-sm); } +.tk-btn[disabled] { opacity: 0.45; cursor: not-allowed; } + +/* Kbd */ +.tk-kbd { display: inline-block; font-family: var(--font-mono); font-size: 11px; line-height: 16px; padding: 0 5px; border: 1px solid var(--rule); border-radius: var(--radius-xs); background: var(--surface); color: var(--muted); } +.tk-btn--primary .tk-kbd { background: transparent; border-color: currentColor; color: inherit; opacity: 0.7; } + +/* Highlight */ +.tk-hl { background: var(--signature); color: var(--on-signature); padding: 1px 5px; border-radius: var(--radius-xs); -webkit-box-decoration-break: clone; box-decoration-break: clone; } +.tk-hl--data { font-family: var(--font-mono); font-size: 11px; line-height: 16px; font-weight: 500; padding: 2px 6px; display: inline-block; } + +/* ImpactMark — magenta marker, same 22 px height as the status tag */ +.tk-impact-mark { display: inline-flex; align-items: center; gap: 4px; height: 22px; box-sizing: border-box; padding: 0 7px 0 5px; border-radius: 4px; background: var(--impact-wash); color: var(--impact); font-family: var(--font-mono); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; } +.tk-impact-mark--compact { width: 22px; padding: 0; justify-content: center; } + +/* StatusMark — tag: glyph 12 px + mono label, 22 px tall */ +.tk-status { display: inline-flex; align-items: center; gap: 5px; height: 22px; box-sizing: border-box; padding: 0 8px 0 6px; border: 1px solid transparent; border-radius: 4px; font-family: var(--font-mono); font-size: 11px; line-height: 16px; font-weight: 500; white-space: nowrap; } +.tk-status--failed { background: var(--failed-wash); color: var(--failed); } +.tk-status--warning { background: var(--warning-wash); color: var(--warning); } +.tk-status--running { background: var(--signature); color: var(--on-signature); } +.tk-status--done { border-color: var(--tag-line); color: var(--muted); font-weight: 400; } +.tk-status--open { border-color: var(--tag-line); border-style: dashed; color: var(--muted); font-weight: 400; } +/* Glyph alone (history, legends, ruler): colour by tone */ +.tk-status-glyph { display: inline-flex; align-items: center; } +.tk-tone--failed { color: var(--failed); } +.tk-tone--warning { color: var(--warning); } +.tk-tone--running { color: var(--needle); } +.tk-tone--done, .tk-tone--open { color: var(--muted); } +.tk-glyph { flex: none; display: block; } +.tk-glyph__mark { stroke: var(--on-status); } +.tk-glyph--running { animation: tk-spin 1.6s linear infinite; transform-origin: 50% 50%; } +@keyframes tk-spin { to { transform: rotate(360deg); } } +@media (prefers-reduced-motion: reduce) { .tk-glyph--running { animation: none; } } + +/* Meta line */ +.tk-meta { font-family: var(--font-mono); font-size: 11px; line-height: 16px; color: var(--muted); min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } + +/* EventRow */ +.tk-list { display: flex; flex-direction: column; } +.tk-row { display: grid; grid-template-columns: 44px minmax(0, 1fr) auto; column-gap: var(--space-3); align-items: start; width: 100%; box-sizing: border-box; padding: var(--row-pad-comfy) var(--space-3); margin: 0; border: 0; border-top: 1px solid var(--rule-soft); background: transparent; color: inherit; font: inherit; text-align: left; cursor: pointer; transition: background-color 120ms cubic-bezier(0.2, 0, 0, 1); } +.tk-row:hover { background: var(--sunken); } +.tk-row:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; } +.tk-row[aria-selected="true"] { background: var(--signature-wash); } +.tk-row__time { font-family: var(--font-mono); font-size: 12px; line-height: 19px; color: var(--muted); font-variant-numeric: tabular-nums; } +.tk-row__body { min-width: 0; display: flex; flex-direction: column; gap: var(--space-1); } +.tk-row__title { font-size: 14px; line-height: 19px; font-weight: 500; color: var(--ink); min-width: 0; overflow: hidden; text-overflow: ellipsis; } +.tk-row__status { justify-self: end; display: flex; align-items: center; gap: 6px; height: 19px; } +.tk-row--compact { padding-top: var(--row-pad-compact); padding-bottom: var(--row-pad-compact); } +.tk-row--compact .tk-row__body { flex-direction: row; align-items: baseline; gap: var(--space-3); } +.tk-row--compact .tk-row__title { white-space: nowrap; flex: 0 1 auto; } +.tk-row--compact .tk-meta { flex: 1 1 0; } + +/* MetricStrip */ +.tk-metrics { display: grid; grid-template-columns: repeat(auto-fit, minmax(160px, 1fr)); background: var(--sunken); border-radius: var(--radius-md); } +.tk-metric { padding: 14px var(--space-4); display: flex; flex-direction: column; gap: var(--space-1); min-width: 0; } +.tk-metric + .tk-metric { border-left: 1px solid var(--rule); } +.tk-metric__label { font-family: var(--font-mono); font-size: 11px; line-height: 16px; color: var(--muted); } +.tk-metric__value { font-family: var(--font-mono); font-size: 30px; line-height: 34px; font-weight: 500; letter-spacing: -0.04em; font-variant-numeric: tabular-nums; color: var(--ink); } +.tk-metric__value--failed { color: var(--failed); } +.tk-metric__value--warning { color: var(--warning); } +.tk-metric__value--impact { color: var(--impact); } +.tk-metric__delta { display: inline-flex; align-items: center; gap: 5px; font-family: var(--font-mono); font-size: 11px; line-height: 16px; color: var(--muted); } +.tk-metric__delta--worse { color: var(--warning); font-weight: 500; } +.tk-metric__sub { font-size: 12px; line-height: 16px; color: var(--muted); } + +/* SegmentedControl */ +.tk-seg { display: inline-flex; padding: 3px; gap: 2px; border-radius: 7px; background: var(--sunken); } +.tk-seg__item { height: var(--control-sm); padding: 0 12px; border: 1px solid transparent; border-radius: var(--radius-sm); background: transparent; color: var(--muted); font-family: var(--font-mono); font-size: 12px; cursor: pointer; } +.tk-seg__item:hover { color: var(--ink); } +.tk-seg__item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 1px; } +.tk-seg__item[aria-pressed="true"] { background: var(--surface); border-color: var(--rule); color: var(--ink); font-weight: 500; } + +/* FilterBar */ +.tk-filters { display: flex; flex-wrap: wrap; align-items: center; gap: var(--space-2); } +.tk-filters__spacer { flex: 1 1 auto; } +.tk-filters__scope { font-family: var(--font-mono); font-size: 11px; color: var(--muted); } +.tk-token { display: inline-flex; align-items: center; gap: 6px; height: 28px; padding: 0 3px 0 10px; border: 1px solid var(--rule-strong); border-radius: var(--radius-sm); background: var(--surface); font-family: var(--font-mono); font-size: 12px; color: var(--ink); } +.tk-token__key { color: var(--muted); } +.tk-token__remove { display: inline-flex; align-items: center; justify-content: center; width: 22px; height: 22px; padding: 0; border: 0; border-radius: var(--radius-xs); background: transparent; color: var(--muted); cursor: pointer; } +.tk-token__remove:hover { background: var(--sunken); color: var(--ink); } +.tk-token__remove:focus-visible { outline: 2px solid var(--focus-ring); } + +/* Nav */ +.tk-sidebar { width: var(--sidebar-w); box-sizing: border-box; background: var(--surface); border-right: 1px solid var(--rule); padding: 18px 14px 24px; display: flex; flex-direction: column; gap: 22px; } +.tk-nav { display: flex; flex-direction: column; gap: 18px; } +.tk-nav__group { display: flex; flex-direction: column; gap: 2px; } +.tk-nav__label { font-family: var(--font-mono); font-size: 11px; line-height: 16px; color: var(--muted); padding: 0 10px 6px; } +.tk-nav__item { display: flex; align-items: center; gap: 10px; padding: 7px 10px; border-radius: var(--radius-sm); font-size: 14px; line-height: 20px; color: var(--ink); text-decoration: none; } +.tk-nav__item .tk-icon { color: var(--muted); } +.tk-nav__item:hover { background: var(--sunken); } +.tk-nav__item:hover .tk-icon { color: var(--ink); } +.tk-nav__item:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: -2px; } +.tk-nav__item[aria-current="page"] { background: var(--signature); color: var(--on-signature); font-weight: 600; } +.tk-nav__item[aria-current="page"] .tk-icon { color: var(--on-signature); } +.tk-nav__count { margin-left: auto; font-family: var(--font-mono); font-size: 11px; color: inherit; opacity: 0.75; } +.tk-sidebar__foot { margin-top: auto; display: flex; } +.tk-sidebar--collapsed { width: 60px; padding: 16px 10px 16px; align-items: center; gap: 18px; } +.tk-sidebar--collapsed .tk-nav { gap: 10px; align-items: center; } +.tk-sidebar--collapsed .tk-nav__label { display: none; } +.tk-sidebar--collapsed .tk-nav__group { align-items: center; } +.tk-sidebar--collapsed .tk-nav__group + .tk-nav__group { border-top: 1px solid var(--rule); padding-top: 10px; } +.tk-sidebar--collapsed .tk-nav__item { position: relative; justify-content: center; width: 40px; height: 36px; padding: 0; box-sizing: border-box; } +.tk-sidebar--collapsed .tk-nav__count { position: absolute; top: 3px; right: 3px; margin: 0; min-width: 14px; height: 14px; padding: 0 3px; box-sizing: border-box; border-radius: 7px; background: var(--ink); color: var(--on-ink); font-size: 9px; line-height: 14px; text-align: center; opacity: 1; } +.tk-sidebar--collapsed .tk-sidebar__foot { justify-content: center; } + +/* TimeRuler */ +.tk-ruler { display: block; width: 100%; height: 40px; overflow: visible; } +.tk-ruler__grid { stroke: var(--grid); } +.tk-ruler__major { stroke: var(--ink); } +.tk-ruler__label { fill: var(--muted); font-family: var(--font-mono); font-size: 9.5px; } +.tk-ruler__mark--failed { fill: var(--failed); } +.tk-ruler__mark--warning { fill: var(--warning); } +.tk-ruler__mark--running { fill: var(--needle); } +.tk-ruler__mark--done { fill: none; stroke: var(--muted); stroke-width: 1.2; } +.tk-ruler__mark--open { fill: none; stroke: var(--muted); stroke-width: 1.2; stroke-dasharray: 2 1.5; } +.tk-ruler__needle { stroke: var(--needle); stroke-width: 1.5; } +.tk-ruler__chip { fill: var(--signature); } +.tk-ruler__chip-text { fill: var(--on-signature); font-family: var(--font-mono); font-size: 9.5px; font-weight: 500; } + +/* CommandPalette */ +.tk-palette { width: 100%; max-width: 640px; box-sizing: border-box; background: var(--overlay); border: 1px solid var(--rule); border-radius: var(--radius-lg); box-shadow: var(--shadow-overlay); overflow: hidden; } +.tk-palette__search { display: flex; align-items: center; gap: 10px; height: 52px; padding: 0 var(--space-4); border-bottom: 1px solid var(--rule); color: var(--muted); } +.tk-palette__input { flex: 1; min-width: 0; border: 0; outline: 0; background: transparent; font: inherit; font-size: 16px; color: var(--ink); } +.tk-palette__input::placeholder { color: var(--muted); } +.tk-palette__list { padding: 6px 0; max-height: 360px; overflow-y: auto; } +.tk-palette__group { font-family: var(--font-mono); font-size: 11px; line-height: 16px; color: var(--muted); padding: 10px var(--space-4) 4px; } +.tk-palette__item { display: flex; align-items: center; gap: 10px; padding: 8px var(--space-4); font-size: 14px; color: var(--ink); cursor: pointer; } +.tk-palette__item .tk-icon { color: var(--muted); } +.tk-palette__item[aria-selected="true"] { background: var(--signature-wash); } +.tk-palette__item[aria-selected="true"] .tk-icon { color: var(--ink); } +.tk-palette__label { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.tk-palette__hint { font-family: var(--font-mono); font-size: 11px; color: var(--muted); white-space: nowrap; } +.tk-palette__footer { display: flex; flex-wrap: wrap; gap: var(--space-4); padding: 10px var(--space-4); border-top: 1px solid var(--rule); font-family: var(--font-mono); font-size: 11px; color: var(--muted); } + +/* EventDrawer */ +.tk-drawer { width: var(--drawer-w); max-width: 100%; box-sizing: border-box; background: var(--overlay); border-left: 1px solid var(--rule); box-shadow: var(--shadow-overlay); display: flex; flex-direction: column; } +.tk-drawer__head { padding: var(--space-5) var(--space-6) var(--space-4); border-bottom: 1px solid var(--rule); display: flex; flex-direction: column; gap: var(--space-2); } +.tk-drawer__bar { display: flex; align-items: center; gap: var(--space-3); } +.tk-drawer__bar .tk-btn { margin-left: auto; } +.tk-drawer__title { margin: 0; font-size: 19px; line-height: 26px; font-weight: 600; letter-spacing: -0.02em; } +.tk-drawer__head--band { background: var(--sunken); gap: var(--space-3); padding-bottom: var(--space-4); } +.tk-type { display: inline-flex; align-items: center; gap: 6px; height: 22px; box-sizing: border-box; padding: 0 8px 0 6px; border: 1px solid var(--rule-strong); border-radius: 4px; background: var(--surface); font-family: var(--font-mono); font-size: 11px; font-weight: 500; color: var(--ink); white-space: nowrap; } +.tk-chips { display: flex; flex-wrap: wrap; gap: 6px; } +.tk-chip { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; height: 28px; box-sizing: border-box; padding: 0 10px; border: 1px solid var(--rule-strong); border-radius: var(--radius-sm); background: var(--surface); color: var(--ink); font: inherit; font-size: 13px; font-weight: 500; cursor: pointer; transition: border-color 120ms cubic-bezier(0.2, 0, 0, 1); } +.tk-chip .tk-icon { color: var(--muted); } +.tk-chip:hover { border-color: var(--ink); } +.tk-chip:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +.tk-chip__text { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } +.tk-chip--quiet { border-color: var(--rule); background: transparent; color: var(--muted); font-family: var(--font-mono); font-size: 12px; font-weight: 400; cursor: default; } +.tk-chip--quiet:hover { border-color: var(--rule); } +.tk-links { display: flex; flex-wrap: wrap; gap: var(--space-2); } +.tk-linkbtn { display: inline-flex; align-items: center; gap: 6px; max-width: 100%; height: 30px; box-sizing: border-box; padding: 0 10px; border: 1px solid var(--rule-strong); border-radius: var(--radius-md); background: var(--surface); color: var(--ink); font-family: var(--font-mono); font-size: 12px; font-weight: 500; text-decoration: none; white-space: nowrap; transition: border-color 120ms cubic-bezier(0.2, 0, 0, 1); } +.tk-linkbtn:hover { border-color: var(--ink); } +.tk-linkbtn:focus-visible { outline: 2px solid var(--focus-ring); outline-offset: 2px; } +.tk-linkbtn__ext { display: inline-flex; color: var(--muted); } +.tk-linkbtn--empty { border-style: dashed; border-color: var(--tag-line); background: transparent; color: var(--muted); font-weight: 400; padding-right: 2px; } +.tk-linkbtn--empty:hover { border-color: var(--tag-line); } +.tk-linkbtn--empty .tk-btn { height: 24px; width: 24px; } +.tk-impact { display: flex; flex-wrap: wrap; align-items: center; gap: 10px; padding: 12px var(--space-6); background: var(--impact); color: var(--on-impact); } +.tk-impact__title { font-size: 14px; line-height: 20px; font-weight: 600; } +.tk-impact__note { font-family: var(--font-mono); font-size: 11px; line-height: 16px; opacity: 0.85; } +.tk-impact--none { gap: 8px; padding: 10px var(--space-6); background: transparent; color: var(--muted); font-size: 13px; border-bottom: 1px solid var(--rule-soft); } +.tk-drawer__body { padding: var(--space-4) var(--space-6) var(--space-6); display: flex; flex-direction: column; gap: var(--space-6); overflow-y: auto; } +.tk-drawer__section-label { font-family: var(--font-mono); font-size: 11px; color: var(--muted); margin: 0 0 var(--space-2); font-weight: 400; } +.tk-fields { display: grid; grid-template-columns: 112px minmax(0, 1fr); margin: 0; } +.tk-fields dt { font-family: var(--font-mono); font-size: 11px; line-height: 20px; color: var(--muted); padding: 6px 0; border-top: 1px solid var(--rule-soft); } +.tk-fields dd { margin: 0; font-family: var(--font-mono); font-size: 12px; line-height: 20px; color: var(--ink); padding: 6px 0; border-top: 1px solid var(--rule-soft); min-width: 0; overflow-wrap: anywhere; } +.tk-fields a { color: var(--ink); text-underline-offset: 3px; } +.tk-history { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; } +.tk-history li { display: grid; grid-template-columns: 64px 12px minmax(0, 1fr); column-gap: var(--space-3); align-items: center; padding: 6px 0; font-size: 13px; line-height: 19px; } +.tk-history time { font-family: var(--font-mono); font-size: 11px; color: var(--muted); font-variant-numeric: tabular-nums; } +.tk-drawer__foot { margin-top: auto; padding: var(--space-4) var(--space-6); border-top: 1px solid var(--rule); display: flex; flex-wrap: wrap; justify-content: flex-end; gap: var(--space-2); } + +/* Logo */ +.tk-logo { display: inline-flex; align-items: center; gap: 10px; color: var(--ink); text-decoration: none; } +.tk-logo__word { font-family: var(--font-sans); font-weight: 600; letter-spacing: -0.035em; } diff --git a/docs/design-system/reference/bundle.js b/docs/design-system/reference/bundle.js new file mode 100644 index 0000000..ed899ef --- /dev/null +++ b/docs/design-system/reference/bundle.js @@ -0,0 +1,433 @@ +/* @ds-bundle: {"format":4,"namespace":"Tracker","components":[{"name":"Logo"},{"name":"Icon"},{"name":"Button"},{"name":"Kbd"},{"name":"Highlight"},{"name":"ImpactMark"},{"name":"StatusMark"},{"name":"EventRow"},{"name":"MetricStrip"},{"name":"SegmentedControl"},{"name":"FilterBar"},{"name":"Sidebar"},{"name":"TimeRuler"},{"name":"CommandPalette"},{"name":"EventDrawer"}]} */ +(function () { + var React = window.React; + var h = React.createElement; + var Fragment = React.Fragment; + + function cx() { + var out = []; + for (var i = 0; i < arguments.length; i++) { if (arguments[i]) out.push(arguments[i]); } + return out.join(' '); + } + function omit(obj, keys) { + var out = {}; + for (var k in obj) { if (Object.prototype.hasOwnProperty.call(obj, k) && keys.indexOf(k) === -1) out[k] = obj[k]; } + return out; + } + var idCounter = 0; + function useStableId(prefix) { + var ref = React.useRef(null); + if (ref.current === null) { idCounter += 1; ref.current = prefix + idCounter; } + return ref.current; + } + + /* ---------- Icons: 16 px grid, 1.5 px stroke, square caps, mitre joins ---------- */ + var icons = { + dashboard: ['M2.75 11.75 A5.25 5.25 0 0 1 13.25 11.75', 'M8 11.75 L10.75 7', 'M1.75 13.75 H14.25'], + timeline: ['M4 2.75 V13.25', 'M4 4.75 H6', 'M4 8 H6', 'M4 11.25 H6', 'M8.5 4.75 H13.25', 'M8.5 8 H12', 'M8.5 11.25 H13.25'], + streamline: ['M2.75 4.25 H8.25', 'M5.25 8 H12.75', 'M3.75 11.75 H9.75', 'M11 2 V14'], + calendar: ['M2.75 3.75 H13.25 V13.25 H2.75 Z', 'M2.75 6.75 H13.25', 'M5.5 2 V5', 'M10.5 2 V5', 'M5.25 9.75 H7.25'], + overlaps: ['M2.75 6 H10.25', 'M5.75 10 H13.25', 'M5.75 3.75 V12.25', 'M10.25 3.75 V12.25'], + insights: ['M2.75 13.25 H13.25', 'M4.75 10.5 V13.25', 'M8 7.5 V13.25', 'M11.25 4.5 V13.25'], + catalog: ['M2.75 2.75 H13.25 V13.25 H2.75 Z', 'M2.75 6.25 H13.25', 'M2.75 9.75 H13.25', 'M7 4.5 H9', 'M7 8 H9', 'M7 11.5 H9'], + architecture: ['M2.75 2.75 H6.25 V6.25 H2.75 Z', 'M9.75 2.75 H13.25 V6.25 H9.75 Z', 'M6.25 9.75 H9.75 V13.25 H6.25 Z', 'M4.5 6.25 V8 H11.5 V6.25', 'M8 8 V9.75'], + compliance: ['M3.75 2.75 H12.25 V13.25 H3.75 Z', 'M5.75 8.25 L7.25 9.75 L10.25 6.25'], + drifts: ['M1.75 11 H5.5 L10.5 5 H14.25', { d: 'M10.5 11 H14.25', dash: '1.5 1.5', cap: 'butt' }], + rpa: ['M12.25 9.5 A4.5 4.5 0 1 1 11.6 4.9', 'M12 2.25 V5.25 H9'], + locks: ['M4.25 7.25 H11.75 V13.25 H4.25 Z', 'M5.75 7.25 V5 A2.25 2.25 0 0 1 10.25 5 V7.25', 'M8 9.5 V11'], + links: ['M7 9 L13.25 2.75', 'M9 2.75 H13.25 V7', 'M11.25 9.5 V13.25 H2.75 V4.75 H6.5'], + docs: ['M3.75 2.75 H9.5 L12.25 5.5 V13.25 H3.75 Z', 'M9.5 2.75 V5.5 H12.25', 'M5.75 8.25 H10.25', 'M5.75 10.75 H10.25'], + deployment: ['M8 10.75 V2.75', 'M4.75 6 L8 2.75 L11.25 6', 'M2.75 13.25 H13.25'], + operation: ['M2.75 5 H13.25', 'M2.75 11 H13.25', 'M6 3.25 V6.75', 'M10 9.25 V12.75'], + incident: ['M1.75 8.5 H4.75 L6.5 3.75 L9.5 12.25 L11.25 8.5 H14.25'], + service: ['M2.75 3.25 H13.25 V7.25 H2.75 Z', 'M2.75 8.75 H13.25 V12.75 H2.75 Z', 'M5 5.25 H6', 'M5 10.75 H6'], + now: ['M8 1.75 V14.25', 'M5.75 1.75 H10.25'], + rows: ['M2.75 3.5 H13.25', 'M2.75 6.5 H13.25', 'M2.75 9.5 H13.25', 'M2.75 12.5 H13.25'], + search: ['M11.25 7 A4.25 4.25 0 1 1 2.75 7 A4.25 4.25 0 1 1 11.25 7 Z', 'M10.25 10.25 L13.5 13.5'], + filter: ['M2.75 4.5 H13.25', 'M4.75 8 H11.25', 'M6.75 11.5 H9.25'], + plus: ['M8 3 V13', 'M3 8 H13'], + close: ['M4 4 L12 12', 'M12 4 L4 12'], + 'chevron-down': ['M4.5 6.25 L8 9.75 L11.5 6.25'], + 'arrow-right': ['M2.75 8 H13', 'M9.5 4.5 L13 8 L9.5 11.5'], + copy: ['M5.75 5.75 H13.25 V13.25 H5.75 Z', 'M10.25 5.75 V2.75 H2.75 V10.25 H5.75'], + environment: ['M2.75 5.25 L8 2.75 L13.25 5.25 L8 7.75 Z', 'M2.75 8.25 L8 10.75 L13.25 8.25', 'M2.75 11.25 L8 13.75 L13.25 11.25'], + owner: ['M10.25 5 A2.25 2.25 0 1 1 5.75 5 A2.25 2.25 0 1 1 10.25 5 Z', 'M3.25 13.25 V12.25 A3 3 0 0 1 6.25 9.25 H9.75 A3 3 0 0 1 12.75 12.25 V13.25'], + impact: ['M9.75 8 A1.75 1.75 0 1 1 6.25 8 A1.75 1.75 0 1 1 9.75 8 Z', 'M4 12 A5.66 5.66 0 0 1 4 4', 'M12 4 A5.66 5.66 0 0 1 12 12'], + 'pull-request': ['M6.25 3.5 A1.75 1.75 0 1 1 2.75 3.5 A1.75 1.75 0 1 1 6.25 3.5 Z', 'M4.5 5.25 V13.25', 'M13.25 12.5 A1.75 1.75 0 1 1 9.75 12.5 A1.75 1.75 0 1 1 13.25 12.5 Z', 'M11.5 10.75 V4.75 H7.75', 'M9.25 3.25 L7.75 4.75 L9.25 6.25'], + ticket: ['M2.75 4.25 H13.25 V6.5 A1.5 1.5 0 0 0 13.25 9.5 V11.75 H2.75 V9.5 A1.5 1.5 0 0 0 2.75 6.5 Z', 'M9.75 5.75 V6.75 M9.75 7.75 V8.25 M9.75 9.25 V10.25'] + }; + + function Icon(props) { + var size = props.size || 16; + var paths = icons[props.name] || []; + var a11y = props.title ? { role: 'img', 'aria-label': props.title } : { 'aria-hidden': 'true' }; + return h('svg', Object.assign({ className: cx('tk-icon', props.className), width: size, height: size, viewBox: '0 0 16 16', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'square', strokeLinejoin: 'miter' }, a11y), + paths.map(function (p, i) { + if (typeof p === 'string') return h('path', { key: i, d: p }); + return h('path', { key: i, d: p.d, strokeDasharray: p.dash, strokeLinecap: p.cap || 'square' }); + })); + } + + /* ---------- Logo ---------- */ + function Logo(props) { + var size = props.size || 24; + var showWord = props.word !== false; + return h('span', { className: 'tk-logo', role: 'img', 'aria-label': 'Tracker' }, + h('svg', { width: size, height: size, viewBox: '0 0 44 44', 'aria-hidden': 'true' }, + h('rect', { x: 0, y: 0, width: 44, height: 44, rx: 8, style: { fill: 'var(--ink)' } }), + h('rect', { x: 19, y: 12, width: 6, height: 22, rx: 1, style: { fill: 'var(--surface)' } }), + h('rect', { x: 9, y: 11, width: 26, height: 8, rx: 1.5, style: { fill: 'var(--signature)' } })), + showWord ? h('span', { className: 'tk-logo__word', 'aria-hidden': 'true', style: { fontSize: Math.round(size * 0.78) + 'px' } }, 'tracker') : null); + } + + /* ---------- Button, Kbd, Highlight ---------- */ + function Button(props) { + var variant = props.variant || 'secondary'; + var size = props.size || 'md'; + var iconOnly = props.icon && !props.children; + var rest = omit(props, ['variant', 'size', 'icon', 'iconRight', 'className', 'children', 'type']); + return h('button', Object.assign({ type: props.type || 'button', className: cx('tk-btn', 'tk-btn--' + variant, size === 'sm' && 'tk-btn--sm', iconOnly && 'tk-btn--icon', props.className) }, rest), + props.icon ? h(Icon, { name: props.icon }) : null, + props.children, + props.iconRight ? h(Icon, { name: props.iconRight, size: 12 }) : null); + } + + function Kbd(props) { + return h('kbd', { className: 'tk-kbd' }, props.children); + } + + /* ImpactMark: the event touches users (attribute impact = true). Magenta, independent of status. */ + function ImpactMark(props) { + var label = props.title || 'Users impacted'; + if (props.compact) { + return h('span', { className: cx('tk-impact-mark', 'tk-impact-mark--compact', props.className), role: 'img', 'aria-label': label, title: label }, + h(Icon, { name: 'impact', size: 12 })); + } + return h('span', { className: cx('tk-impact-mark', props.className), title: label }, + h(Icon, { name: 'impact', size: 12 }), + h('span', null, props.text || 'impact')); + } + + function Highlight(props) { + return h('span', { className: cx('tk-hl', props.data && 'tk-hl--data', props.className) }, props.children); + } + + /* ---------- StatusMark ---------- */ + /* Status glyphs: 16 px grid, round caps (distinct from the square-capped icon set). + failed = square + ✕, warning = triangle + !, running = open arc + dot, done = circle + ✓, open = empty circle. */ + var STATUSES = ['failed', 'warning', 'running', 'done', 'open']; + function Glyph(props) { + var s = props.status; + var size = props.size || 12; + var common = { className: cx('tk-glyph', s === 'running' && 'tk-glyph--running'), width: size, height: size, viewBox: '0 0 16 16', 'aria-hidden': 'true' }; + var mark = { fill: 'none', className: 'tk-glyph__mark', strokeWidth: 1.75, strokeLinecap: 'round' }; + if (s === 'failed') return h('svg', common, + h('rect', { x: 1, y: 1, width: 14, height: 14, rx: 3, fill: 'currentColor' }), + h('path', Object.assign({ d: 'M5.6 5.6 L10.4 10.4 M10.4 5.6 L5.6 10.4' }, mark))); + if (s === 'warning') return h('svg', common, + h('path', { d: 'M8 1.2 L15.1 14.4 L0.9 14.4 Z', fill: 'currentColor', stroke: 'currentColor', strokeWidth: 0.8, strokeLinejoin: 'round' }), + h('path', Object.assign({ d: 'M8 6.3 V9.4 M8 11.9 V12' }, mark))); + if (s === 'running') return h('svg', common, + h('path', { d: 'M8 1.75 A6.25 6.25 0 1 1 1.75 8', fill: 'none', stroke: 'currentColor', strokeWidth: 1.75, strokeLinecap: 'round' }), + h('circle', { cx: 8, cy: 8, r: 2.1, fill: 'currentColor' })); + if (s === 'open') return h('svg', common, + h('circle', { cx: 8, cy: 8, r: 6.25, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5 })); + return h('svg', common, + h('circle', { cx: 8, cy: 8, r: 6.25, fill: 'none', stroke: 'currentColor', strokeWidth: 1.5 }), + h('path', { d: 'M5.1 8.2 L7.1 10.2 L10.9 6.1', fill: 'none', stroke: 'currentColor', strokeWidth: 1.5, strokeLinecap: 'round', strokeLinejoin: 'round' })); + } + + function StatusMark(props) { + var s = STATUSES.indexOf(props.status) === -1 ? 'done' : props.status; + var text = props.text || s; + if (props.label === false) { + return h('span', { className: cx('tk-status-glyph', 'tk-tone--' + s, props.className), role: 'img', 'aria-label': text }, + h(Glyph, { status: s, size: props.size || 14 })); + } + return h('span', { className: cx('tk-status', 'tk-status--' + s, props.className) }, + h(Glyph, { status: s, size: 12 }), + h('span', null, text)); + } + + /* ---------- EventRow ---------- */ + function metaText(meta) { + if (!meta) return ''; + return Array.isArray(meta) ? meta.filter(Boolean).join(' · ') : meta; + } + + function EventRow(props) { + var compact = props.density === 'compact'; + var s = props.status || 'done'; + function onKey(e) { + if ((e.key === 'Enter' || e.key === ' ') && props.onSelect) { e.preventDefault(); props.onSelect(); } + } + return h('div', { role: 'option', tabIndex: 0, 'aria-selected': props.selected ? 'true' : 'false', className: cx('tk-row', compact && 'tk-row--compact'), onClick: props.onSelect, onKeyDown: onKey }, + h('span', { className: 'tk-row__time' }, props.time), + h('span', { className: 'tk-row__body' }, + h('span', { className: 'tk-row__title' }, props.title), + h('span', { className: 'tk-meta' }, metaText(props.meta))), + h('span', { className: 'tk-row__status' }, + props.impact ? h(ImpactMark, { compact: true }) : null, + h(StatusMark, { status: s }))); + } + + function EventList(props) { + var events = props.events || []; + return h('div', { role: 'listbox', 'aria-label': props.label || 'Events', className: 'tk-list' }, + events.map(function (ev, i) { + return h(EventRow, Object.assign({ key: ev.id || i, density: props.density, selected: props.selectedId != null && ev.id === props.selectedId, onSelect: props.onSelect ? function () { props.onSelect(ev); } : undefined }, ev)); + })); + } + + /* ---------- MetricStrip ---------- */ + function MetricStrip(props) { + var items = props.items || []; + return h('div', { className: 'tk-metrics', role: 'group', 'aria-label': props.label || 'Metrics' }, + items.map(function (m, i) { + return h('div', { key: i, className: 'tk-metric' }, + h('span', { className: 'tk-metric__label' }, m.label), + h('span', { className: cx('tk-metric__value', m.tone && 'tk-metric__value--' + m.tone) }, m.value), + m.sub ? h('span', { className: 'tk-metric__sub' }, m.sub) : null, + m.delta ? h('span', { className: cx('tk-metric__delta', 'tk-metric__delta--' + (m.trend || 'flat')), title: m.trend === 'worse' ? 'Worse than the previous period' : (m.trend === 'better' ? 'Better than the previous period' : 'Compared with the previous period') }, + m.trend === 'worse' ? h(Glyph, { status: 'warning', size: 10 }) : null, + h('span', null, m.delta)) : null); + })); + } + + /* ---------- SegmentedControl ---------- */ + function SegmentedControl(props) { + var options = props.options || []; + var controlled = props.value !== undefined; + var st = React.useState(props.defaultValue || options[0]); + var value = controlled ? props.value : st[0]; + function pick(o) { if (!controlled) st[1](o); if (props.onChange) props.onChange(o); } + return h('div', { className: 'tk-seg', role: 'group', 'aria-label': props.label || 'Options' }, + options.map(function (o) { + return h('button', { key: o, type: 'button', className: 'tk-seg__item', 'aria-pressed': o === value ? 'true' : 'false', onClick: function () { pick(o); } }, o); + })); + } + + /* ---------- FilterBar ---------- */ + function FilterBar(props) { + var init = props.filters || []; + var st = React.useState(init); + var filters = props.onRemove ? init : st[0]; + function remove(i) { + if (props.onRemove) props.onRemove(filters[i], i); + else st[1](filters.filter(function (_, j) { return j !== i; })); + } + return h('div', { className: 'tk-filters', role: 'toolbar', 'aria-label': 'Filters' }, + h(SegmentedControl, { label: 'Period', options: props.periods || ['24h', '7d', '30d'], defaultValue: props.period || (props.periods || ['24h', '7d', '30d'])[1], onChange: props.onPeriodChange }), + filters.map(function (f, i) { + return h('span', { key: f.key + ':' + f.value, className: 'tk-token' }, + h('span', { className: 'tk-token__key' }, f.key), + h('span', null, f.value), + h('button', { type: 'button', className: 'tk-token__remove', 'aria-label': 'Remove filter ' + f.key + ' ' + f.value, onClick: function () { remove(i); } }, h(Icon, { name: 'close', size: 12 }))); + }), + h(Button, { variant: 'ghost', size: 'sm', icon: 'plus', onClick: props.onAdd }, 'Filter'), + h('span', { className: 'tk-filters__spacer' }), + props.scope ? h('span', { className: 'tk-filters__scope' }, props.scope) : null); + } + + /* ---------- Sidebar ---------- */ + function Sidebar(props) { + var groups = props.groups || []; + var collapsed = !!props.collapsed; + return h('aside', { className: cx('tk-sidebar', collapsed && 'tk-sidebar--collapsed') }, + props.logo === false ? null : h('a', { href: props.homeHref || '#', className: 'tk-logo', 'aria-label': 'Tracker home', style: { padding: collapsed ? '0' : '2px 6px' } }, h(Logo, { size: collapsed ? 28 : 26, word: !collapsed })), + h('nav', { className: 'tk-nav', 'aria-label': 'Main' }, + groups.map(function (g) { + return h('div', { key: g.label, className: 'tk-nav__group', role: collapsed ? 'group' : undefined, 'aria-label': collapsed ? g.label : undefined }, + h('span', { className: 'tk-nav__label' }, g.label), + g.items.map(function (it) { + var current = it.name === props.active; + var hint = it.count != null ? it.name + ' (' + it.count + ')' : it.name; + return h('a', { key: it.name, href: it.href || '#', className: 'tk-nav__item', 'aria-current': current ? 'page' : undefined, 'aria-label': collapsed ? hint : undefined, title: collapsed ? hint : undefined }, + it.icon ? h(Icon, { name: it.icon }) : null, + collapsed ? null : h('span', null, it.name), + it.count != null ? h('span', { className: 'tk-nav__count' }, it.count) : null); + })); + })), + props.onToggle ? h('div', { className: 'tk-sidebar__foot' }, + h(Button, { variant: 'ghost', size: 'sm', icon: collapsed ? 'arrow-right' : 'rows', 'aria-label': collapsed ? 'Expand navigation' : 'Collapse navigation', onClick: props.onToggle }, collapsed ? null : 'Collapse')) : null); + } + + /* ---------- TimeRuler ---------- */ + function toHours(t) { + if (typeof t === 'number') return t; + var p = String(t).split(':'); + return parseInt(p[0], 10) + (parseInt(p[1] || '0', 10) / 60); + } + function TimeRuler(props) { + /* startHour / endHour are the canonical props; from / to stay as aliases (from is reserved on x-import). */ + var from = props.startHour != null ? Number(props.startHour) : (props.from != null ? Number(props.from) : 12); + var to = props.endHour != null ? Number(props.endHour) : (props.to != null ? Number(props.to) : 18); + var W = props.width || 600; + var H = 40; + var pph = W / (to - from); + var gid = useStableId('tk-ruler-grid-'); + function x(t) { return (toHours(t) - from) * pph; } + var majors = []; + for (var hr = from; hr <= to; hr++) majors.push(hr); + var marks = props.marks || []; + var nowX = props.now ? x(props.now) : null; + var nowLabel = props.now ? 'now ' + props.now : ''; + var chipW = nowLabel.length * 6.2 + 12; + var chipX = nowX != null ? Math.min(nowX, W - chipW) : 0; + return h('svg', { className: 'tk-ruler', viewBox: '0 0 ' + W + ' ' + H, preserveAspectRatio: 'none', role: 'img', 'aria-label': props.label || ('Time axis from ' + from + ':00 to ' + to + ':00' + (props.now ? ', now ' + props.now : '')) }, + h('defs', null, h('pattern', { id: gid, width: 8, height: 8, patternUnits: 'userSpaceOnUse' }, h('path', { d: 'M8 0.5 H0.5 V8', fill: 'none', className: 'tk-ruler__grid', strokeWidth: 1 }))), + h('rect', { x: 0, y: 0, width: W, height: H, rx: 4, fill: 'url(#' + gid + ')' }), + majors.map(function (m, i) { + var mx = (m - from) * pph + 0.5; + return h(Fragment, { key: m }, + h('line', { x1: mx, y1: 26, x2: mx, y2: H, className: 'tk-ruler__major', strokeWidth: 1 }), + i < majors.length - 1 ? h('text', { x: mx + 4.5, y: 34, className: 'tk-ruler__label' }, (m < 10 ? '0' : '') + m + ':00') : null); + }), + marks.map(function (mk, i) { + return h('circle', { key: i, cx: x(mk.at), cy: 14, r: 3, className: 'tk-ruler__mark--' + (mk.status || 'done') }); + }), + nowX != null ? h(Fragment, null, + h('rect', { x: chipX, y: 4, width: chipW, height: 16, rx: 3, className: 'tk-ruler__chip' }), + h('text', { x: chipX + 6, y: 15.5, className: 'tk-ruler__chip-text' }, nowLabel), + h('line', { x1: nowX, y1: 4, x2: nowX, y2: H, className: 'tk-ruler__needle' })) : null); + } + + /* ---------- CommandPalette ---------- */ + function CommandPalette(props) { + var qs = React.useState(props.defaultQuery || ''); + var as = React.useState(0); + var q = qs[0].trim().toLowerCase(); + var groups = (props.groups || []).map(function (g) { + return { label: g.label, items: g.items.filter(function (it) { return !q || it.label.toLowerCase().indexOf(q) !== -1 || (it.hint || '').toLowerCase().indexOf(q) !== -1; }) }; + }).filter(function (g) { return g.items.length; }); + var flat = []; + groups.forEach(function (g) { g.items.forEach(function (it) { flat.push(it); }); }); + var active = Math.min(as[0], Math.max(flat.length - 1, 0)); + var listId = useStableId('tk-palette-list-'); + function onKey(e) { + if (e.key === 'ArrowDown') { e.preventDefault(); as[1](Math.min(active + 1, flat.length - 1)); } + else if (e.key === 'ArrowUp') { e.preventDefault(); as[1](Math.max(active - 1, 0)); } + else if (e.key === 'Enter' && flat[active] && props.onSelect) { e.preventDefault(); props.onSelect(flat[active]); } + else if (e.key === 'Escape' && props.onClose) { props.onClose(); } + } + var idx = -1; + return h('div', { className: 'tk-palette', role: 'dialog', 'aria-label': 'Command palette' }, + h('div', { className: 'tk-palette__search' }, + h(Icon, { name: 'search' }), + h('input', { className: 'tk-palette__input', type: 'text', role: 'combobox', 'aria-expanded': 'true', 'aria-controls': listId, 'aria-activedescendant': flat.length ? listId + '-' + active : undefined, 'aria-label': 'Search or run a command', placeholder: props.placeholder || 'Search events, services, teams — or type a command', value: qs[0], onChange: function (e) { qs[1](e.target.value); as[1](0); }, onKeyDown: onKey, autoFocus: props.autoFocus }), + h(Kbd, null, 'esc')), + h('div', { className: 'tk-palette__list', role: 'listbox', id: listId }, + groups.length === 0 ? h('div', { className: 'tk-palette__group' }, 'No results for “' + qs[0] + '”') : null, + groups.map(function (g) { + return h('div', { key: g.label, role: 'group', 'aria-label': g.label }, + h('div', { className: 'tk-palette__group', 'aria-hidden': 'true' }, g.label), + g.items.map(function (it) { + idx += 1; + var my = idx; + return h('div', { key: it.label, id: listId + '-' + my, role: 'option', 'aria-selected': my === active ? 'true' : 'false', className: 'tk-palette__item', onMouseEnter: function () { as[1](my); }, onClick: function () { if (props.onSelect) props.onSelect(it); } }, + it.icon ? h(Icon, { name: it.icon }) : null, + h('span', { className: 'tk-palette__label' }, it.label), + it.hint ? h('span', { className: 'tk-palette__hint' }, it.hint) : null, + it.kbd ? h(Kbd, null, it.kbd) : null); + })); + })), + h('div', { className: 'tk-palette__footer', 'aria-hidden': 'true' }, + h('span', null, '↑ ↓ move'), h('span', null, '↵ open'), h('span', null, 'esc close'))); + } + + /* ---------- EventDrawer ---------- */ + /* Bandeau layout: type, status, title, then the facts people act on (service, environment, owner, + pull request, ticket) in the header band; user impact as its own full-width band in --impact. */ + var TYPE_ICON = { deployment: 'deployment', operation: 'operation', drift: 'drifts', incident: 'incident', rpa_usage: 'rpa', rpa: 'rpa' }; + function Chip(props) { + return h('button', { type: 'button', className: 'tk-chip', title: props.hint, 'aria-label': props.hint, onClick: props.onClick }, + h(Icon, { name: props.icon, size: 14 }), + h('span', { className: 'tk-chip__text' }, props.children)); + } + function LinkButton(props) { + if (!props.link) { + return h('span', { className: 'tk-linkbtn tk-linkbtn--empty' }, + h(Icon, { name: props.icon, size: 14 }), + h('span', null, props.emptyLabel), + props.onAdd ? h(Button, { variant: 'ghost', size: 'sm', icon: 'plus', 'aria-label': props.addLabel, onClick: props.onAdd }) : null); + } + return h('a', { className: 'tk-linkbtn', href: props.link.href || '#', title: props.link.title, target: props.link.href ? '_blank' : undefined, rel: props.link.href ? 'noopener' : undefined }, + h(Icon, { name: props.icon, size: 14 }), + h('span', null, (props.prefix || '') + props.link.label), + h('span', { className: 'tk-linkbtn__ext' }, h(Icon, { name: 'links', size: 12 }))); + } + + function EventDrawer(props) { + var ev = props.event || {}; + var fields = ev.fields || []; + var history = ev.history || []; + var hasFacts = ev.service || ev.environment || ev.owner; + var hasLinks = ev.pullRequest !== undefined || ev.ticket !== undefined; + function filter(key, value) { return props.onFilter ? function () { props.onFilter(key, value); } : undefined; } + var stake = (ev.stakeholders || []).length ? 'Owner ' + ev.owner + ', stakeholders: ' + ev.stakeholders.join(', ') : 'Filter by owner ' + ev.owner; + return h('section', { className: 'tk-drawer', role: 'dialog', 'aria-modal': 'false', 'aria-label': ev.title || 'Event detail', style: props.style }, + h('header', { className: cx('tk-drawer__head', hasFacts && 'tk-drawer__head--band') }, + h('div', { className: 'tk-drawer__bar' }, + ev.type ? h('span', { className: 'tk-type' }, h(Icon, { name: TYPE_ICON[ev.type] || 'operation', size: 12 }), String(ev.type).replace('_', ' ')) : null, + h(StatusMark, { status: ev.status }), + h('span', { className: 'tk-meta' }, ev.time), + h(Button, { variant: 'ghost', size: 'sm', icon: 'close', 'aria-label': 'Close detail', onClick: props.onClose })), + h('h2', { className: 'tk-drawer__title' }, ev.title), + hasFacts ? h('div', { className: 'tk-chips' }, + ev.service ? h(Chip, { icon: 'service', hint: 'Filter by service ' + ev.service, onClick: filter('service', ev.service) }, ev.service) : null, + ev.environment ? h(Chip, { icon: 'environment', hint: 'Filter by environment ' + ev.environment, onClick: filter('env', ev.environment) }, ev.environment) : null, + ev.owner ? h(Chip, { icon: 'owner', hint: stake, onClick: filter('owner', ev.owner) }, ev.owner) : null, + ev.priority ? h('span', { className: 'tk-chip tk-chip--quiet', title: 'Priority' }, ev.priority) : null) + : (ev.meta ? h('span', { className: 'tk-meta' }, metaText(ev.meta)) : null), + hasLinks ? h('div', { className: 'tk-links' }, + h(LinkButton, { icon: 'pull-request', link: ev.pullRequest, prefix: 'PR ', emptyLabel: 'no pull request', addLabel: 'Link a pull request', onAdd: props.onLinkPullRequest }), + h(LinkButton, { icon: 'ticket', link: ev.ticket, emptyLabel: 'no ticket', addLabel: 'Link a ticket', onAdd: props.onLinkTicket })) : null), + ev.impact === true ? h('div', { className: 'tk-impact', role: 'note' }, + h(Icon, { name: 'impact', size: 16 }), + h('span', { className: 'tk-impact__title' }, 'Users impacted'), + ev.impactNote ? h('span', { className: 'tk-impact__note' }, ev.impactNote) : null) : null, + ev.impact === false ? h('div', { className: 'tk-impact tk-impact--none' }, + h(Icon, { name: 'impact', size: 14 }), + h('span', null, 'No user impact declared')) : null, + h('div', { className: 'tk-drawer__body' }, + ev.summary ? h('p', { style: { margin: 0, fontSize: '14px', lineHeight: '21px' } }, ev.summary) : null, + fields.length ? h('div', null, + h('h3', { className: 'tk-drawer__section-label' }, 'details'), + h('dl', { className: 'tk-fields' }, fields.map(function (f) { + return h(Fragment, { key: f[0] }, h('dt', null, f[0]), h('dd', null, f[2] ? h('a', { href: f[2] }, f[1]) : f[1])); + }))) : null, + history.length ? h('div', null, + h('h3', { className: 'tk-drawer__section-label' }, 'history'), + h('ol', { className: 'tk-history' }, history.map(function (it, i) { + return h('li', { key: i }, + h('time', null, it.time), + h('span', { className: 'tk-history__glyph tk-tone--' + (it.status || 'done') }, h(Glyph, { status: it.status || 'done', size: 12 })), + h('span', null, it.text)); + }))) : null), + props.actions === false ? null : h('footer', { className: 'tk-drawer__foot' }, + h(Button, { variant: 'ghost', icon: 'copy' }, 'Copy link'), + h(Button, { variant: 'secondary', icon: 'locks' }, 'Lock service'), + h(Button, { variant: 'primary' }, 'Edit event'))); + } + + var api = { + icons: icons, + Icon: Icon, + Logo: Logo, + Button: Button, + Kbd: Kbd, + Highlight: Highlight, + ImpactMark: ImpactMark, + StatusMark: StatusMark, + EventRow: EventRow, + EventList: EventList, + MetricStrip: MetricStrip, + SegmentedControl: SegmentedControl, + FilterBar: FilterBar, + Sidebar: Sidebar, + TimeRuler: TimeRuler, + CommandPalette: CommandPalette, + EventDrawer: EventDrawer + }; + window.Tracker = Object.assign(window.Tracker || {}, api); +})(); diff --git a/docs/design-system/reference/index.d.ts b/docs/design-system/reference/index.d.ts new file mode 100644 index 0000000..7e182ed --- /dev/null +++ b/docs/design-system/reference/index.d.ts @@ -0,0 +1,59 @@ +// Tracker — component props (documentation). Global: window.Tracker +import * as React from 'react'; + +export type Status = 'failed' | 'warning' | 'running' | 'done' | 'open'; +export type IconName = 'dashboard' | 'timeline' | 'streamline' | 'calendar' | 'overlaps' | 'insights' | 'catalog' | 'architecture' | 'compliance' | 'drifts' | 'rpa' | 'locks' | 'links' | 'docs' | 'deployment' | 'operation' | 'incident' | 'service' | 'now' | 'rows' | 'search' | 'filter' | 'plus' | 'close' | 'chevron-down' | 'arrow-right' | 'copy' | 'environment' | 'owner' | 'impact' | 'pull-request' | 'ticket'; + +export interface LogoProps { /** Symbol height in px. Default 24. */ size?: number; /** Show the "tracker" wordmark. Default true. */ word?: boolean; } +export interface IconProps { name: IconName; /** px, default 16 (drawn on a 16 px grid). */ size?: number; /** Accessible name; omit for decorative icons. */ title?: string; className?: string; } +export interface ButtonProps extends React.ButtonHTMLAttributes { variant?: 'primary' | 'secondary' | 'ghost'; size?: 'md' | 'sm'; icon?: IconName; iconRight?: IconName; } +export interface KbdProps { children: React.ReactNode; } +export interface HighlightProps { children: React.ReactNode; /** Mono 11 px chip for data ("running", "now 15:31"). */ data?: boolean; } +export interface StatusMarkProps { status: Status; /** Override the label text. */ text?: string; /** false renders the glyph alone, coloured by tone, with the label as its accessible name. Default true (tag). */ label?: boolean; /** Glyph size in px when label is false. Default 14. */ size?: number; } +export interface ImpactMarkProps { /** Icon only, 22 × 22. Use in rows and dense places. */ compact?: boolean; /** Label, default "impact". */ text?: string; /** Tooltip and accessible name, default "Users impacted". */ title?: string; } +export interface EventRowProps { id?: string | number; time: string; status: Status; title: string; /** Shows a compact ImpactMark before the status tag. */ impact?: boolean; meta?: string | string[]; density?: 'comfortable' | 'compact'; selected?: boolean; onSelect?: () => void; } +export interface EventListProps { events: EventRowProps[]; density?: 'comfortable' | 'compact'; selectedId?: string | number; onSelect?: (event: EventRowProps) => void; label?: string; } +export interface Metric { label: string; value: string; sub?: string; tone?: 'failed' | 'warning' | 'impact'; /** Comparison with the previous period, e.g. "−3.2 pts vs prev." */ delta?: string; /** worse: amber with a warning glyph; better and flat stay neutral. */ trend?: 'better' | 'worse' | 'flat'; } +export interface MetricStripProps { items: Metric[]; label?: string; } +export interface SegmentedControlProps { options: string[]; value?: string; defaultValue?: string; onChange?: (value: string) => void; label?: string; } +export interface Filter { key: string; value: string; } +export interface FilterBarProps { filters?: Filter[]; periods?: string[]; period?: string; scope?: string; onRemove?: (f: Filter, index: number) => void; onAdd?: () => void; onPeriodChange?: (p: string) => void; } +export interface NavItem { name: string; icon?: IconName; href?: string; count?: number | string; } +export interface SidebarProps { groups: { label: string; items: NavItem[] }[]; active?: string; logo?: boolean; homeHref?: string; /** Icon rail, 60 px wide: names become tooltips and accessible names, counts become badges. */ collapsed?: boolean; /** Shows a collapse / expand button at the bottom. */ onToggle?: () => void; } +export interface TimeRulerProps { /** First hour shown. Default 12. */ startHour?: number; /** Last hour. Default 18. */ endHour?: number; /** Alias of startHour (reserved on x-import: use start-hour). */ from?: number; /** Alias of endHour. */ to?: number; /** "HH:MM" */ now?: string; marks?: { at: string; status: Status }[]; width?: number; label?: string; } +export interface PaletteItem { label: string; icon?: IconName; hint?: string; kbd?: string; } +export interface CommandPaletteProps { groups: { label: string; items: PaletteItem[] }[]; defaultQuery?: string; placeholder?: string; autoFocus?: boolean; onSelect?: (item: PaletteItem) => void; onClose?: () => void; } +export type EventType = 'deployment' | 'operation' | 'drift' | 'incident' | 'rpa_usage'; +export interface EventLink { /** "#1287", "PAY-342" */ label: string; href?: string; /** PR or ticket title, shown as tooltip */ title?: string; } +export interface DrawerEvent { + status: Status; time: string; title: string; + type?: EventType; priority?: string; + /** Header chips; each one filters the time views on click (onFilter). */ + service?: string; environment?: string; owner?: string; stakeholders?: string[]; + /** true: full-width impact band in --impact; false: quiet "No user impact declared"; undefined: nothing. */ + impact?: boolean; impactNote?: string; + /** null shows a dashed empty slot with a + button; undefined hides the link row. */ + pullRequest?: EventLink | null; ticket?: EventLink | null; + /** Legacy meta line, shown only when service / environment / owner are absent. */ + meta?: string | string[]; + summary?: string; /** Secondary details: [label, value, href?] */ fields?: [string, string, string?][]; history?: { time: string; status: Status; text: string }[]; +} +export interface EventDrawerProps { event: DrawerEvent; onClose?: () => void; /** key is 'service' | 'env' | 'owner' */ onFilter?: (key: string, value: string) => void; onLinkTicket?: () => void; onLinkPullRequest?: () => void; actions?: boolean; style?: React.CSSProperties; } + +export declare const Logo: React.FC; +export declare const Icon: React.FC; +export declare const Button: React.FC; +export declare const Kbd: React.FC; +export declare const Highlight: React.FC; +export declare const ImpactMark: React.FC; +export declare const StatusMark: React.FC; +export declare const EventRow: React.FC; +export declare const EventList: React.FC; +export declare const MetricStrip: React.FC; +export declare const SegmentedControl: React.FC; +export declare const FilterBar: React.FC; +export declare const Sidebar: React.FC; +export declare const TimeRuler: React.FC; +export declare const CommandPalette: React.FC; +export declare const EventDrawer: React.FC; +export declare const icons: Record; diff --git a/docs/design-system/tokens.json b/docs/design-system/tokens.json new file mode 100644 index 0000000..20ad8e5 --- /dev/null +++ b/docs/design-system/tokens.json @@ -0,0 +1,138 @@ +{ + "name": "Tracker", + "version": 1, + "color": { + "themes": [ + { "id": "light", "name": "Clair" }, + { "id": "dark", "name": "Sombre" } + ], + "tokens": [ + { "name": "paper", "value": { "light": "#F3F1EC", "dark": "#131210" }, "usage": "Fond de page. Papier chaud le jour, carbone la nuit. Jamais de blanc pur ni de noir pur." }, + { "name": "surface", "value": { "light": "#FAF8F3", "dark": "#1B1A17" }, "usage": "Panneaux, cartes, barre latérale, barre du haut. Posé sur paper, délimité par un filet rule, sans ombre." }, + { "name": "sunken", "value": { "light": "#F1EEE7", "dark": "#22211D" }, "usage": "Creux : bande de métriques, piste du segmented control, survol d'une ligne ou d'un item de navigation." }, + { "name": "overlay", "value": { "light": "#FCFBF8", "dark": "#211F1C" }, "usage": "Surfaces flottantes uniquement : drawer, palette ⌘K, menus. Toujours avec shadow-overlay." }, + { "name": "scrim", "value": { "light": "rgba(20, 19, 17, 0.28)", "dark": "rgba(0, 0, 0, 0.55)" }, "usage": "Voile derrière la palette ⌘K. Le drawer n'en a pas : la liste reste lisible à côté." }, + + { "name": "rule", "value": { "light": "#DCD8CF", "dark": "#2E2C27" }, "usage": "Filet de 1 px : bords de panneaux, séparateurs de métriques. Décoratif, jamais seul pour délimiter un contrôle." }, + { "name": "rule-soft", "value": { "light": "#E7E3DA", "dark": "#262420" }, "usage": "Séparateurs entre lignes d'événements et entre lignes de tableau." }, + { "name": "rule-strong", "value": { "light": "#8C867A", "dark": "#6E695F" }, "usage": "Bord des contrôles (input, bouton secondary, filtre). 3.4:1 sur surface en clair, 3.2:1 en sombre." }, + { "name": "grid", "value": { "light": "#E9E5DC", "dark": "#25231F" }, "usage": "Trame millimétrée de la règle temporelle, au pas de 8 px. Ne jamais l'utiliser derrière du texte courant." }, + + { "name": "ink", "value": { "light": "#141311", "dark": "#EDEAE3" }, "usage": "Texte principal, titres, chiffres. 16:1 sur surface en clair, 14:1 en sombre." }, + { "name": "ink-hover", "value": { "light": "#2E2C28", "dark": "#D2CEC5" }, "usage": "Survol du bouton primary." }, + { "name": "on-ink", "value": { "light": "#FAF8F3", "dark": "#141311" }, "usage": "Texte et icônes posés sur un fond ink (bouton primary)." }, + { "name": "muted", "value": { "light": "#615C52", "dark": "#A39E93" }, "usage": "Métadonnées, heures, labels, statuts neutres (done, open). 6.3:1 sur surface en clair, 6.9:1 en sombre." }, + + { "name": "signature", "value": { "light": "#D4F53C", "dark": "#D4F53C" }, "usage": "Le surligneur. Marque le présent : maintenant, en cours, page active. Toujours en aplat sous un texte on-signature, jamais en couleur de texte sur paper." }, + { "name": "on-signature", "value": { "light": "#141311", "dark": "#141311" }, "usage": "Texte et icônes sur signature, dans les deux thèmes." }, + { "name": "signature-wash", "value": { "light": "#EEF5CC", "dark": "#2A2F16" }, "usage": "Ligne sélectionnée, item actif de la palette ⌘K. Le texte reste ink." }, + { "name": "needle", "value": { "light": "#141311", "dark": "#D4F53C" }, "usage": "Aiguille « now » de la règle temporelle et des graphiques. Encre le jour, chartreuse la nuit." }, + { "name": "focus-ring", "value": { "light": "#141311", "dark": "#D4F53C" }, "usage": "Anneau de focus clavier, 2 px, décalé de 2 px." }, + + { "name": "failed", "value": { "light": "#C2261C", "dark": "#FF7A6B" }, "usage": "Statut failed et seulement lui : échecs, incidents ouverts. 5.6:1 en clair, 6.8:1 en sombre. Toujours accompagné du glyphe carré marqué d'un ✕." }, + { "name": "failed-wash", "value": { "light": "#FBE4DF", "dark": "#3D1D18" }, "usage": "Fond de l'étiquette failed, et rien d'autre. Le texte failed y atteint 4.6:1 en clair, 6:1 en sombre." }, + { "name": "warning", "value": { "light": "#8F5A00", "dark": "#F0B44A" }, "usage": "Statut warning : dégradé, à surveiller, conflit potentiel. 5.6:1 en clair. Toujours accompagné du glyphe triangle marqué d'un !." }, + { "name": "warning-wash", "value": { "light": "#F7EBD3", "dark": "#382A12" }, "usage": "Fond de l'étiquette warning, et rien d'autre. Le texte warning y atteint 4.9:1 en clair, 7.5:1 en sombre." }, + { "name": "on-status", "value": { "light": "#FAF8F3", "dark": "#141311" }, "usage": "Le signe intérieur (✕, !) des glyphes pleins failed et warning." }, + { "name": "tag-line", "value": { "light": "#B5AFA3", "dark": "#55514A" }, "usage": "Filet des étiquettes neutres : plein pour done, pointillé pour open. Décoratif : le libellé muted porte l'information." }, + { "name": "impact", "value": { "light": "#B5126F", "dark": "#FF5CB0" }, "usage": "Impact utilisateur (attribut impact = true), et rien d'autre : bande du drawer, marqueur d'impact. Indépendant du statut : un déploiement réussi peut avoir un impact." }, + { "name": "on-impact", "value": { "light": "#FAF8F3", "dark": "#141311" }, "usage": "Texte et icônes posés sur impact. 6.1:1 en clair, 6.4:1 en sombre." }, + { "name": "impact-wash", "value": { "light": "#FBE3F0", "dark": "#3A1529" }, "usage": "Fond discret d'un marqueur d'impact dans une liste ou une cellule. Le texte y reste ink." }, + { "name": "chart-done", "value": { "light": "#8C867A", "dark": "#6E695F" }, "usage": "Segments « done » dans les graphiques. Neutre par principe : ce qui va bien ne réclame pas l'attention." }, + { "name": "chart-open", "value": { "light": "#D3CEC4", "dark": "#3A3732" }, "usage": "Segments « open » dans les graphiques : en attente, sans gravité." }, + + { "name": "type-deployment", "value": { "light": "#3355D6", "dark": "#7F9BFF" }, "usage": "Série « deployment » dans les graphiques par type d'événement. 5.8:1 sur surface en clair." }, + { "name": "type-operation", "value": { "light": "#0E8A80", "dark": "#3FC7BA" }, "usage": "Série « operation » dans les graphiques par type. 4:1 sur surface en clair." }, + { "name": "type-drift", "value": { "light": "#7A4CC9", "dark": "#B39BFF" }, "usage": "Série « drift » dans les graphiques par type. 5.2:1 sur surface en clair." }, + { "name": "type-rpa", "value": { "light": "#5F7186", "dark": "#9DB0C4" }, "usage": "Série « rpa usage » dans les graphiques par type. Les incidents restent en ink : ils se lisent par leur statut, pas par une couleur de série." }, + { "name": "data-1", "value": { "light": "#141311", "dark": "#EDEAE3" }, "usage": "Série catégorielle 1 (ex. production). Les catégories utilisent la rampe data, jamais les couleurs de statut." }, + { "name": "data-2", "value": { "light": "#5C574D", "dark": "#A39E93" }, "usage": "Série catégorielle 2 (ex. pré-production)." }, + { "name": "data-3", "value": { "light": "#9C968A", "dark": "#6E695F" }, "usage": "Série catégorielle 3 (ex. dev / integration)." }, + { "name": "data-4", "value": { "light": "#CFCAC0", "dark": "#45423B" }, "usage": "Série catégorielle 4 (ex. other). Au-delà de 4 séries, regrouper en « other »." } + ] + }, + "type": { + "fonts": [ + { "family": "Spline Sans", "file": "fonts/SplineSans.ttf", "weight": "300 700", "style": "normal" }, + { "family": "Spline Sans Mono", "file": "fonts/SplineSansMono.ttf", "weight": "300 700", "style": "normal" } + ], + "families": { + "sans": "\"Spline Sans\", \"Helvetica Neue\", Arial, sans-serif", + "mono": "\"Spline Sans Mono\", ui-monospace, \"SF Mono\", Menlo, Consolas, monospace" + }, + "groups": [ + { + "name": "Chiffres", + "family": "mono", + "styles": [ + { "name": "metric-xl", "fontSize": "36px", "lineHeight": "40px", "fontWeight": 500, "letterSpacing": "-0.04em", "sample": "236 events", "usage": "Chiffre héros d'une page (Insights). Un seul par écran." }, + { "name": "metric", "fontSize": "30px", "lineHeight": "34px", "fontWeight": 500, "letterSpacing": "-0.04em", "sample": "60.0%", "usage": "Valeurs de la bande de métriques. Chiffres tabulaires, jamais de zéros de remplissage (2, pas 02)." } + ] + }, + { + "name": "Titres", + "family": "sans", + "styles": [ + { "name": "title-lg", "fontSize": "28px", "lineHeight": "34px", "fontWeight": 600, "letterSpacing": "-0.035em", "sample": "Dashboard", "usage": "Titre de page, un par écran." }, + { "name": "title", "fontSize": "19px", "lineHeight": "26px", "fontWeight": 600, "letterSpacing": "-0.02em", "sample": "Deploy monitoring v2.5.5 → mco", "usage": "Titre du drawer, titres de sections longues." }, + { "name": "title-sm", "fontSize": "15px", "lineHeight": "22px", "fontWeight": 600, "letterSpacing": "-0.01em", "sample": "Event velocity", "usage": "En-tête de panneau ou de carte." } + ] + }, + { + "name": "Texte", + "family": "sans", + "styles": [ + { "name": "summary", "fontSize": "18px", "lineHeight": "28px", "fontWeight": 400, "letterSpacing": "-0.01em", "sample": "2 deployments failed and need attention.", "usage": "Phrase de synthèse en tête de page, lisible en trois secondes." }, + { "name": "body-strong", "fontSize": "14px", "lineHeight": "19px", "fontWeight": 500, "sample": "Terraform drift — firewall", "usage": "Titre d'une ligne d'événement, libellé d'item." }, + { "name": "body", "fontSize": "14px", "lineHeight": "21px", "fontWeight": 400, "sample": "Two deployments overlap on payment-service between 15:00 and 15:30.", "usage": "Texte courant, descriptions, contenu du drawer." }, + { "name": "caption", "fontSize": "12px", "lineHeight": "16px", "fontWeight": 400, "sample": "3 of 5 finished", "usage": "Précision sous une métrique, aide sous un champ." } + ] + }, + { + "name": "Données", + "family": "mono", + "styles": [ + { "name": "data", "fontSize": "12px", "lineHeight": "16px", "fontWeight": 400, "sample": "15:04:12", "usage": "Heures, durées, versions, identifiants, valeurs du drawer." }, + { "name": "data-sm", "fontSize": "11px", "lineHeight": "16px", "fontWeight": 400, "sample": "deployment · mco · P3 · infra-team", "usage": "Ligne de métadonnées, labels de métriques, étiquettes de groupes de navigation. En minuscules, séparées par « · »." }, + { "name": "data-strong", "fontSize": "11px", "lineHeight": "16px", "fontWeight": 500, "sample": "running", "usage": "Texte dans un surligneur, statut actif." } + ] + } + ] + }, + "spacing": { + "tokens": [ + { "name": "space-1", "value": "4px", "usage": "Écart entre glyphe et libellé serré, entre titre et métadonnées." }, + { "name": "space-2", "value": "8px", "usage": "Écart entre contrôles voisins, entre jetons de filtre." }, + { "name": "space-3", "value": "12px", "usage": "Gouttière des colonnes d'une ligne d'événement." }, + { "name": "space-4", "value": "16px", "usage": "Padding horizontal des métriques et des items de palette." }, + { "name": "space-5", "value": "20px", "usage": "Padding intérieur des panneaux." }, + { "name": "space-6", "value": "24px", "usage": "Écart entre panneaux, entre blocs d'une page." }, + { "name": "space-8", "value": "32px", "usage": "Marge de page." }, + { "name": "space-16", "value": "64px", "usage": "Grands dégagements (page vide, onboarding)." } + ] + }, + "radius": { + "tokens": [ + { "name": "radius-xs", "value": "3px", "usage": "Surligneur, touches clavier, segments de graphique." }, + { "name": "radius-sm", "value": "5px", "usage": "Items de navigation, segments du segmented control, jetons de filtre." }, + { "name": "radius-md", "value": "6px", "usage": "Boutons, champs, panneaux, cartes." }, + { "name": "radius-lg", "value": "8px", "usage": "Palette ⌘K, symbole du logo." } + ] + }, + "size": { + "tokens": [ + { "name": "control-md", "value": "38px", "usage": "Hauteur des boutons et champs par défaut (cible ≥ 38 px, 44 px sur tactile)." }, + { "name": "control-sm", "value": "30px", "usage": "Hauteur des contrôles secondaires et du segmented control." }, + { "name": "row-pad-comfy", "value": "12px", "usage": "Padding vertical d'une ligne en densité confort (≈ 62 px par ligne)." }, + { "name": "row-pad-compact", "value": "6px", "usage": "Padding vertical d'une ligne en densité compacte (≈ 32 px, une seule ligne de texte)." }, + { "name": "sidebar-w", "value": "232px", "usage": "Largeur de la barre latérale." }, + { "name": "drawer-w", "value": "480px", "usage": "Largeur du drawer de détail." } + ] + }, + "shadow": { + "note": "Filets, pas d'ombres. Une seule ombre existe, réservée à ce qui flotte au-dessus de la page.", + "tokens": [ + { "name": "shadow-overlay", "value": { "light": "0 1px 0 rgba(20, 19, 17, 0.04), 0 16px 40px rgba(20, 19, 17, 0.14)", "dark": "0 16px 40px rgba(0, 0, 0, 0.55)" }, "usage": "Drawer, palette ⌘K, menus déroulants. Jamais sur une carte ou un panneau posé." } + ] + } +} diff --git a/web/index.html b/web/index.html index ea55d91..37dabb2 100644 --- a/web/index.html +++ b/web/index.html @@ -12,10 +12,6 @@ - - - -
diff --git a/web/package-lock.json b/web/package-lock.json index b6e5c51..1743c57 100644 --- a/web/package-lock.json +++ b/web/package-lock.json @@ -8,6 +8,8 @@ "name": "tracker-web", "version": "1.0.0", "dependencies": { + "@fontsource-variable/spline-sans": "^5.3.0", + "@fontsource-variable/spline-sans-mono": "^5.3.0", "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", @@ -746,6 +748,24 @@ } } }, + "node_modules/@fontsource-variable/spline-sans": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/spline-sans/-/spline-sans-5.3.0.tgz", + "integrity": "sha512-a0EVY1u6qvI5uk66ImOKbh3QD8y3LtMpuqMMyN4OB9eJwivKPXNTn8rJUB+YLrJ5LxS85vqSmG1xeYG/mF8MDw==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, + "node_modules/@fontsource-variable/spline-sans-mono": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@fontsource-variable/spline-sans-mono/-/spline-sans-mono-5.3.0.tgz", + "integrity": "sha512-dH/qBj/MCE4ObiuCwlA9X1o3AftMO9dwfFgoRVYjy6VCPYkpWAlEtK/RjeyICgsR8rW763IAQARskVgShBY5JA==", + "license": "OFL-1.1", + "funding": { + "url": "https://github.com/sponsors/ayuhito" + } + }, "node_modules/@fortawesome/fontawesome-common-types": { "version": "6.7.2", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.7.2.tgz", diff --git a/web/package.json b/web/package.json index 1c81cbf..af723cd 100644 --- a/web/package.json +++ b/web/package.json @@ -4,9 +4,15 @@ "private": true, "type": "module", "scripts": { + "tokens": "node scripts/build-tokens.mjs", + "tokens:check": "node scripts/build-tokens.mjs --check", + "predev": "npm run tokens", "dev": "vite", + "prebuild": "npm run tokens", "build": "vite build", + "prebuild:static": "npm run tokens", "build:static": "vite build --mode static", + "prebuild:check": "npm run tokens", "build:check": "tsc && vite build", "preview": "vite preview", "lint": "eslint . --report-unused-disable-directives --max-warnings 0", @@ -14,6 +20,8 @@ "test:watch": "vitest" }, "dependencies": { + "@fontsource-variable/spline-sans": "^5.3.0", + "@fontsource-variable/spline-sans-mono": "^5.3.0", "@fortawesome/fontawesome-svg-core": "^6.7.2", "@fortawesome/free-brands-svg-icons": "^6.7.2", "@fortawesome/free-solid-svg-icons": "^6.7.2", diff --git a/web/scripts/build-tokens.mjs b/web/scripts/build-tokens.mjs new file mode 100644 index 0000000..652bed7 --- /dev/null +++ b/web/scripts/build-tokens.mjs @@ -0,0 +1,110 @@ +#!/usr/bin/env node +// Generates src/design/tokens.css from src/design/tokens.json. +// +// node scripts/build-tokens.mjs write the file +// node scripts/build-tokens.mjs --check exit 1 if the file is out of date +// +// Light colours go under :root and .light, dark ones under .dark (ThemeContext +// sets one of the two classes on ; either can also scope a subtree). +// Every opaque colour also gets an "-rgb" channel variable (--paper-rgb: +// 243 241 236) for the rgb(var(--x) / α) form. Spacing, radii, sizes, font +// families and type styles go in the first block too. + +import { readFileSync, writeFileSync } from 'node:fs' +import { dirname, resolve } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = resolve(dirname(fileURLToPath(import.meta.url)), '..') +const SOURCE = resolve(root, 'src/design/tokens.json') +const TARGET = resolve(root, 'src/design/tokens.css') + +// Fontsource registers its variable fonts under " Variable". +// That name is put first, ahead of the family stack from tokens.json. +const FONTSOURCE_FAMILIES = { + sans: '"Spline Sans Variable"', + mono: '"Spline Sans Mono Variable"', +} + +function hexToChannels(value) { + const match = /^#([0-9a-f]{6})$/i.exec(value) + if (!match) return null + const n = parseInt(match[1], 16) + return `${(n >> 16) & 255} ${(n >> 8) & 255} ${n & 255}` +} + +function colorLines(tokens, theme) { + const lines = [] + for (const token of tokens) { + const value = token.value[theme] + lines.push(`--${token.name}: ${value};`) + const channels = hexToChannels(value) + if (channels) lines.push(`--${token.name}-rgb: ${channels};`) + } + return lines +} + +function typeLines(type) { + const lines = [] + for (const [name, stack] of Object.entries(type.families)) { + lines.push(`--font-${name}: ${FONTSOURCE_FAMILIES[name]}, ${stack};`) + } + for (const group of type.groups) { + for (const style of group.styles) { + const prefix = `--typo-${style.name}` + lines.push( + `${prefix}-family: var(--font-${group.family});`, + `${prefix}-size: ${style.fontSize};`, + `${prefix}-line-height: ${style.lineHeight};`, + `${prefix}-weight: ${style.fontWeight};`, + `${prefix}-tracking: ${style.letterSpacing ?? 'normal'};`, + ) + } + } + return lines +} + +function block(selector, lines) { + return `${selector} {\n${lines.map((l) => ` ${l}`).join('\n')}\n}\n` +} + +export function buildTokensCss(tokens) { + const flat = (family) => tokens[family].tokens.map((t) => `--${t.name}: ${t.value};`) + const shadows = (theme) => tokens.shadow.tokens.map((t) => `--${t.name}: ${t.value[theme]};`) + + return [ + '/*\n' + + ' * GENERATED FILE, DO NOT EDIT BY HAND.\n' + + ' * Source: src/design/tokens.json (copy of docs/design-system/tokens.json).\n' + + ' * Regenerate with: npm run tokens\n' + + ' */\n', + block(':root,\n.light', [ + ...colorLines(tokens.color.tokens, 'light'), + ...shadows('light'), + ...typeLines(tokens.type), + ...flat('spacing'), + ...flat('radius'), + ...flat('size'), + ]), + block('.dark', [...colorLines(tokens.color.tokens, 'dark'), ...shadows('dark')]), + ].join('\n') +} + +function main() { + const css = buildTokensCss(JSON.parse(readFileSync(SOURCE, 'utf8'))) + if (process.argv.includes('--check')) { + let current = '' + try { + current = readFileSync(TARGET, 'utf8') + } catch { + // missing file: reported below as out of date + } + if (current !== css) { + console.error('src/design/tokens.css is out of date: run npm run tokens') + process.exit(1) + } + return + } + writeFileSync(TARGET, css) +} + +main() diff --git a/web/src/components/DependencySelector.tsx b/web/src/components/DependencySelector.tsx index 7a716f7..0bc388e 100644 --- a/web/src/components/DependencySelector.tsx +++ b/web/src/components/DependencySelector.tsx @@ -187,7 +187,7 @@ export default function DependencySelector({ type="button" onClick={onAdd} disabled={!value.trim()} - className="px-3 py-2 bg-hud-primary text-white rounded-lg hover:bg-hud-primary-dim transition-colors disabled:opacity-50 disabled:cursor-not-allowed" + className="px-3 py-2 bg-hud-primary text-on-ink rounded-lg hover:bg-hud-primary-dim transition-colors disabled:opacity-50 disabled:cursor-not-allowed" > diff --git a/web/src/components/EventDetailsModal.tsx b/web/src/components/EventDetailsModal.tsx index 512cce3..479cfdd 100644 --- a/web/src/components/EventDetailsModal.tsx +++ b/web/src/components/EventDetailsModal.tsx @@ -506,10 +506,10 @@ export default function EventDetailsModal({ event, onClose }: EventDetailsModalP )} {isEditing ? ( - setEditedEvent({ ...editedEvent, title: e.target.value })} /> ) : (<> -

+

{editedEvent.title}

{editedEvent.attributes.service}

@@ -523,7 +523,7 @@ export default function EventDetailsModal({ event, onClose }: EventDetailsModalP @@ -666,7 +666,7 @@ export default function EventDetailsModal({ event, onClose }: EventDetailsModalP
Owner
- + {ownerInitials || '?'} {editedEvent.attributes.owner || 'Unassigned'} diff --git a/web/src/components/FormPanel.tsx b/web/src/components/FormPanel.tsx index b1ddb6f..0454c60 100644 --- a/web/src/components/FormPanel.tsx +++ b/web/src/components/FormPanel.tsx @@ -103,7 +103,7 @@ export default function FormPanel({ {icon}
-

+

{title}

{subtitle && ( diff --git a/web/src/components/Layout.tsx b/web/src/components/Layout.tsx index c3b9cf8..de2cdb1 100644 --- a/web/src/components/Layout.tsx +++ b/web/src/components/Layout.tsx @@ -251,7 +251,7 @@ export default function Layout() {
-

+

Global Dependencies

@@ -590,7 +590,7 @@ export default function CatalogDependencies() {

{label}

- {value} + {value} {icon}
@@ -610,7 +610,7 @@ export default function CatalogDependencies() { onClick={() => setLayoutType(lt)} className="px-3 py-1.5 text-xs font-medium rounded-lg transition-all hover:opacity-90 flex items-center gap-1" style={layoutType === lt - ? { background: T.primary, color: 'white' } + ? { background: T.primary, color: 'var(--on-ink)' } : { background: T.surfaceHigh, color: T.onSurfaceVar, border: `1px solid ${a('outline-var', 0.3)}` } } > @@ -630,7 +630,7 @@ export default function CatalogDependencies() { onClick={() => setNodeSize(size)} className="px-3 py-1.5 text-xs font-medium rounded-lg transition-all hover:opacity-90 flex items-center gap-1" style={nodeSize === size - ? { background: T.primary, color: 'white' } + ? { background: T.primary, color: 'var(--on-ink)' } : { background: T.surfaceHigh, color: T.onSurfaceVar, border: `1px solid ${a('outline-var', 0.3)}` } } > @@ -652,7 +652,7 @@ export default function CatalogDependencies() { onClick={() => setViewMode(mode)} className="px-3 py-1.5 text-xs font-medium rounded-lg transition-all hover:opacity-90 flex items-center gap-1" style={viewMode === mode - ? { background: T.primary, color: 'white' } + ? { background: T.primary, color: 'var(--on-ink)' } : { background: T.surfaceHigh, color: T.onSurfaceVar, border: `1px solid ${a('outline-var', 0.3)}` } } > @@ -702,7 +702,7 @@ export default function CatalogDependencies() { onClick={() => toggleFilter(type, selectedTypes, setSelectedTypes)} className="px-3 py-1 text-xs font-medium rounded-full transition-all hover:opacity-90" style={selectedTypes.includes(type) - ? { background: T.primary, color: 'white' } + ? { background: T.primary, color: 'var(--on-ink)' } : { background: T.surfaceHigh, color: T.onSurfaceVar, border: `1px solid ${a('outline-var', 0.3)}` } } > @@ -801,7 +801,7 @@ export default function CatalogDependencies() {
-

+

Dependency Graph

@@ -843,7 +843,7 @@ export default function CatalogDependencies() { ) : (

-

+

Dependencies List View

@@ -996,7 +996,7 @@ export default function CatalogDependencies() {

- + {totalDeps} {totalDeps > 0 && ( diff --git a/web/src/pages/CatalogDetail.tsx b/web/src/pages/CatalogDetail.tsx index 41454f7..c82c53a 100644 --- a/web/src/pages/CatalogDetail.tsx +++ b/web/src/pages/CatalogDetail.tsx @@ -879,7 +879,7 @@ export default function CatalogDetail({ serviceNameProp, onClose }: CatalogDetai
-

+

{service.name}

@@ -890,7 +890,7 @@ export default function CatalogDetail({ serviceNameProp, onClose }: CatalogDetai @@ -657,8 +657,7 @@ export default function CreateCatalog({ asPanel = false, onClose, onSuccess }: C className="flex items-center gap-2 px-10 py-3 rounded-xl font-bold shadow-lg transition-all hover:opacity-90 hover:scale-[1.02] active:scale-95 disabled:opacity-50" style={{ background: `linear-gradient(135deg, ${hud.primary}, ${ha('primary-dim', 1)})`, - color: 'white', - fontFamily: "'Space Grotesk', sans-serif", + color: 'var(--on-ink)', boxShadow: `0 4px 20px ${ha('primary', 0.25)}`, }} > diff --git a/web/src/pages/CreateDrift.tsx b/web/src/pages/CreateDrift.tsx index 04338b7..41d41db 100644 --- a/web/src/pages/CreateDrift.tsx +++ b/web/src/pages/CreateDrift.tsx @@ -93,7 +93,7 @@ export default function CreateDrift({ asPanel = false, onClose, onSuccess }: Cre const SectionHeader = ({ icon, title, color }: { icon: ReactNode; title: string; color?: string }) => (

{icon} -

{title}

+

{title}

) @@ -278,7 +278,7 @@ export default function CreateDrift({ asPanel = false, onClose, onSuccess }: Cre {/* Header */}
-

+

Create a Drift

@@ -302,15 +302,14 @@ export default function CreateDrift({ asPanel = false, onClose, onSuccess }: Cre

-

Recurrence

+

Recurrence