Compare commits
4 Commits
c2befb64db
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
| b965c1dfbd | |||
| 6457347dd6 | |||
| f0987778ec | |||
| b635fd1fdb |
@@ -0,0 +1 @@
|
|||||||
|
{"worktree": {"bgIsolation": "none"}}
|
||||||
@@ -26,7 +26,7 @@ KDCDesignSystem/
|
|||||||
├── components/ # Per-component specs and anatomy
|
├── components/ # Per-component specs and anatomy
|
||||||
├── themes/ # Light, dark, high-contrast theme overrides
|
├── themes/ # Light, dark, high-contrast theme overrides
|
||||||
├── templates/ # Web, mobile, email, print, presentation
|
├── templates/ # Web, mobile, email, print, presentation
|
||||||
├── docs/ # Getting started, principles, a11y, voice & tone
|
├── docs/ # Getting started, principles, patterns, a11y, voice & tone
|
||||||
├── examples/ # Reference implementations
|
├── examples/ # Reference implementations
|
||||||
└── scripts/ # Token build / export scripts
|
└── scripts/ # Token build / export scripts
|
||||||
```
|
```
|
||||||
@@ -36,6 +36,7 @@ KDCDesignSystem/
|
|||||||
1. Read [`docs/getting-started/README.md`](./docs/getting-started/README.md).
|
1. Read [`docs/getting-started/README.md`](./docs/getting-started/README.md).
|
||||||
2. Skim [`design.md`](./design.md) for the token vocabulary.
|
2. Skim [`design.md`](./design.md) for the token vocabulary.
|
||||||
3. Pick your platform under [`examples/`](./examples/).
|
3. Pick your platform under [`examples/`](./examples/).
|
||||||
|
4. Browse [`docs/patterns/README.md`](./docs/patterns/README.md) — a scrapbook of UI effects & interactions we like (and the bar each clears before shipping).
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
|
|||||||
@@ -16,7 +16,8 @@ For each: every distinct colour, font, radius, shadow, and reusable component wa
|
|||||||
|--------|-------|---------|-----|--------|------|-------|
|
|--------|-------|---------|-----|--------|------|-------|
|
||||||
| **Refined product** *(canonical)* | `kdcsurveyadd`, `kdcvault`, `kdc_void_planner` | `#6B35A7` | `#1A1530` | `#F7F4ED` (warm) | Manrope | Tailwind v4 + shadcn/ui + base-ui |
|
| **Refined product** *(canonical)* | `kdcsurveyadd`, `kdcvault`, `kdc_void_planner` | `#6B35A7` | `#1A1530` | `#F7F4ED` (warm) | Manrope | Tailwind v4 + shadcn/ui + base-ui |
|
||||||
| **Marketing dashboard** | `k-d-c-workspace` (dashboard) | `#6B35A7` | `#1a1530` | `#f7f6fb` (cool) | Manrope | Tailwind v4 + custom components |
|
| **Marketing dashboard** | `k-d-c-workspace` (dashboard) | `#6B35A7` | `#1a1530` | `#f7f6fb` (cool) | Manrope | Tailwind v4 + custom components |
|
||||||
| **Legacy / exploratory** *(drift)* | `kdcdiary`, `kdctracker`, `kdcdocs`, `kdcmapper` | varies | varies | varies | Geist or Inter+Outfit | varies |
|
| **Legacy / exploratory** *(drift)* | `kdcdiary`, `kdctracker`, `kdcmapper` | varies | varies | varies | Geist or Inter+Outfit | varies |
|
||||||
|
| **Archived** *(out of scope)* | `kdcdocs` | `#A37CD9` | dark | dark | Geist | Sunset — SOPs merged into `kdcsurveyadd /docs`. Read-only reference. |
|
||||||
|
|
||||||
The product family agrees almost line-for-line on tokens — including the bespoke gold accent `#C9892B`, the focus ring `0 0 0 3px rgba(107,53,167,0.18)`, the ink-tinted shadow stack, and a 4-step radius scale (`4 / 8 / 12 / 16`). v1.0.0 promotes that consensus into the canonical spec.
|
The product family agrees almost line-for-line on tokens — including the bespoke gold accent `#C9892B`, the focus ring `0 0 0 3px rgba(107,53,167,0.18)`, the ink-tinted shadow stack, and a 4-step radius scale (`4 / 8 / 12 / 16`). v1.0.0 promotes that consensus into the canonical spec.
|
||||||
|
|
||||||
@@ -28,15 +29,15 @@ The dashboard agrees on brand fundamentals (purple, Manrope, ink) but uses a **c
|
|||||||
|------|--------------|-----------|--------|
|
|------|--------------|-----------|--------|
|
||||||
| `kdcdiary` | `#6B4EFF` | Geist + JetBrains Mono | drift — different purple, different typeface |
|
| `kdcdiary` | `#6B4EFF` | Geist + JetBrains Mono | drift — different purple, different typeface |
|
||||||
| `kdctracker` | `#6B4EFF` | Geist + JetBrains Mono | drift — same as diary, looks like a shared template |
|
| `kdctracker` | `#6B4EFF` | Geist + JetBrains Mono | drift — same as diary, looks like a shared template |
|
||||||
| `kdcdocs` | `#A37CD9` | Geist + Geist Mono | drift — dark-themed Astro site, lifted purple |
|
|
||||||
| `kdcmapper` | `#5567E8` (indigo) | Inter + Outfit + JetBrains Mono | drift — indigo palette, no Manrope, glassmorphism style |
|
| `kdcmapper` | `#5567E8` (indigo) | Inter + Outfit + JetBrains Mono | drift — indigo palette, no Manrope, glassmorphism style |
|
||||||
|
|
||||||
|
`kdcdocs` was previously sunset (SOPs merged into `kdcsurveyadd /docs`) — excluded from migration.
|
||||||
|
|
||||||
**Not in scope for v1.0.0.** A follow-up project should migrate these to canonical — order of cheapest first:
|
**Not in scope for v1.0.0.** A follow-up project should migrate these to canonical — order of cheapest first:
|
||||||
|
|
||||||
1. **kdctracker** — single-file HTML, ~1 day to swap CSS vars.
|
1. **kdctracker** — single-file HTML, ~1 day to swap CSS vars.
|
||||||
2. **kdcdiary** — React app with inline styles, ~2 days.
|
2. **kdcdiary** — React app with inline styles, ~2 days.
|
||||||
3. **kdcdocs** — needs a deliberate "light vs dark canonical" decision before migration. Either we adopt a dark theme as canonical for marketing/docs, or we re-skin it onto the light product theme.
|
3. **kdcmapper** — biggest lift, fully bespoke Tailwind theme. Plan a dedicated re-skin sprint.
|
||||||
4. **kdcmapper** — biggest lift, fully bespoke Tailwind theme. Plan a dedicated re-skin sprint.
|
|
||||||
|
|
||||||
## What changed in v1.0.0
|
## What changed in v1.0.0
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
# Patterns We Like
|
||||||
|
|
||||||
|
A running scrapbook of UI effects, interactions and micro-patterns we've come across and want to keep — so a future app or project can reach for something proven instead of reinventing it.
|
||||||
|
|
||||||
|
This list sits slightly apart from the canonical [principles](../principles/README.md) on purpose. Those say *"clarity over cleverness"* and *"motion is feedback, never decorates."* The entries here are often **delight** effects that flirt with that line. That's the point of the doc: keep the tempting stuff in one place **and** record the bar each must clear before it earns a spot in a shipping product. A pattern we like is not a pattern we'll always use.
|
||||||
|
|
||||||
|
## The bar (when a delight effect earns its place)
|
||||||
|
|
||||||
|
Before any effect below ships in a KDC product, it has to clear all four:
|
||||||
|
|
||||||
|
- ✅ **It does a job.** It communicates state, affordance, continuity or confirmation — not just "looks nice." If it's purely decorative, it doesn't ship (per [Motion](../../foundations/motion/README.md)).
|
||||||
|
- ✅ **It respects `prefers-reduced-motion: reduce`** — collapses to a static/instant state, no exceptions.
|
||||||
|
- ✅ **It animates `transform` / `opacity` only** — never `width` / `height` / layout. Compositor-friendly or it doesn't go in.
|
||||||
|
- ✅ **It degrades gracefully** — works without it, and doesn't trap focus, block input, or break on keyboard/touch.
|
||||||
|
|
||||||
|
If an entry can't meet the bar in a given spot, that's a "no" for that spot — not a reason to drop the pattern from the list.
|
||||||
|
|
||||||
|
## How to add an entry
|
||||||
|
|
||||||
|
Keep it light — append a block in this shape. Don't turn it into a form.
|
||||||
|
|
||||||
|
```
|
||||||
|
### <name>
|
||||||
|
**Status:** idea | trialed | adopted-in-<app>
|
||||||
|
**What it is:** one line.
|
||||||
|
**Why we like it:** one or two lines.
|
||||||
|
**Snippet:** the smallest working version.
|
||||||
|
**Fits / avoid:** where it earns its place, and where it doesn't.
|
||||||
|
**Watch-outs:** the honest gotchas — performance, a11y, edge cases.
|
||||||
|
**Found:** where it came from (link / who / when).
|
||||||
|
```
|
||||||
|
|
||||||
|
**Status legend** — `idea` (caught our eye, untried) · `trialed` (prototyped in a branch) · `adopted-in-<app>` (shipped, name the app).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Entries
|
||||||
|
|
||||||
|
### Proximity-magnification dock
|
||||||
|
|
||||||
|
**Status:** idea
|
||||||
|
|
||||||
|
**What it is:** macOS-dock-style hover magnification — across a row (or column) of equal items, the one nearest the cursor scales up, neighbours scale less, tapering to nothing past a set radius. The bulge follows the pointer.
|
||||||
|
|
||||||
|
**Why we like it:** tiny amount of code, self-resetting (far items naturally land back at scale 1), and `scale` is compositor-cheap. Used in the right place it doubles as a targeting affordance — it literally points at the thing you're about to click.
|
||||||
|
|
||||||
|
**Snippet** — the whole trick is "distance → 0..1 closeness → size":
|
||||||
|
|
||||||
|
```js
|
||||||
|
// Vanilla, horizontal. 120 = reach in px; 0.5 = max growth (→ 1.5×).
|
||||||
|
onpointermove = e =>
|
||||||
|
document.querySelectorAll(".dock > *").forEach(el => {
|
||||||
|
const r = el.getBoundingClientRect();
|
||||||
|
const t = Math.max(0, 1 - Math.abs(e.clientX - (r.x + r.width / 2)) / 120);
|
||||||
|
el.style.scale = 1 + t * 0.5;
|
||||||
|
});
|
||||||
|
```
|
||||||
|
|
||||||
|
In our apps (all React 19) it belongs in an effect with cached rects + rAF + cleanup, and gated on reduced-motion:
|
||||||
|
|
||||||
|
```jsx
|
||||||
|
useEffect(() => {
|
||||||
|
if (matchMedia("(prefers-reduced-motion: reduce)").matches) return; // clears the bar
|
||||||
|
const items = [...dockRef.current.children];
|
||||||
|
let rects = items.map(el => el.getBoundingClientRect()); // cache — don't measure per-move
|
||||||
|
const remeasure = () => { rects = items.map(el => el.getBoundingClientRect()); };
|
||||||
|
const onMove = e => requestAnimationFrame(() => items.forEach((el, i) => {
|
||||||
|
const c = rects[i].x + rects[i].width / 2; // use clientY + rect.y for a vertical dock
|
||||||
|
const t = Math.max(0, 1 - Math.abs(e.clientX - c) / 120);
|
||||||
|
el.style.scale = 1 + t * 0.5;
|
||||||
|
}));
|
||||||
|
window.addEventListener("pointermove", onMove);
|
||||||
|
window.addEventListener("resize", remeasure);
|
||||||
|
return () => { window.removeEventListener("pointermove", onMove); window.removeEventListener("resize", remeasure); };
|
||||||
|
}, []);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fits / avoid:**
|
||||||
|
- ✅ A row/column of **≥ 5 equal icon targets you actually click** — tool palettes, icon rails, app launchers. (KDC candidate: the floor-plan tool palette in `kdc_void_planner` — `Palette.tsx`, a vertical rail of ~10 tool buttons. Vertical, so drive it off `clientY`.)
|
||||||
|
- ❌ **2–3 item navs** — the ripple is invisible with so few items.
|
||||||
|
- ❌ **Step indicators / status strips** — those communicate *progress/state*, not click targets. Magnifying them is semantically confusing and fails the bar (it does no job).
|
||||||
|
|
||||||
|
**Watch-outs:**
|
||||||
|
- **Layout thrash.** `getBoundingClientRect()` per `pointermove` forces synchronous layout. Cache the rects (re-measure on `resize`/`scroll`), and batch the writes in `requestAnimationFrame`.
|
||||||
|
- **Horizontal-only** as written (`clientX`). Swap to `clientY` + `rect.y` for a vertical rail.
|
||||||
|
- **Neighbours overlap rather than push apart.** `scale` doesn't reflow, so a growing item grows *over* its neighbours instead of displacing them (the real Dock shoves siblings outward). Looks fine with generous gaps/padding; less polished without.
|
||||||
|
- **`transform-origin`.** For a docked edge, set the origin to that edge (e.g. `transform-origin: bottom` / `left`) so items grow *out of* the dock, not in both directions.
|
||||||
|
- **Reduced motion + input parity.** Bail when `prefers-reduced-motion` is set; never let the scale affect hit-testing/keyboard order.
|
||||||
|
|
||||||
|
**Found:** Paul, May 2026 — a "proximity hack" snippet. Decoded + React-adapted in-session.
|
||||||
Reference in New Issue
Block a user