diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9e609d7..3b3160e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,9 @@ A tool **must**: load. Use system fonts and inline `data:` URIs for any assets; - **not** include analytics, trackers, or any phone-home behavior; - store any state in `localStorage` under a namespaced key (e.g. `my-tool:…`); +- follow [`Hillnote-Tools-Design-System.md`](./Hillnote-Tools-Design-System.md) — + see [Design system](#design-system) below; +- **support light and dark mode** via `prefers-color-scheme`; - look reasonable in the catalog's **800×500** preview frame. > Multi-file bundles are supported by the installer but not used today — keep it @@ -37,6 +40,70 @@ string-argument `setTimeout`/`setInterval`, and `atob`/`unescape`/`String.fromCh These scans are a first-pass filter and a review aid — the runtime CSP in the app is the actual sandbox — so a reviewer still reads every tool's script. +## Design system + +Every tool follows [`Hillnote-Tools-Design-System.md`](./Hillnote-Tools-Design-System.md). +The template already wires this up — copy it and you inherit all of it. The short +version: + +| | | +| --- | --- | +| Accent | Hillnote azure `#4d63ff`. One accent only — don't introduce a second. | +| Surfaces | `--canvas-soft` page → `--canvas` cards. Surface contrast *is* the elevation; no drop shadows. | +| Type | Manrope weight 900 for the headline, Inter for everything else. Both are fallback stacks — no web fonts (the CSP blocks them). | +| Radius | `24px` for cards and buttons. | + +### Two rules that are easy to get wrong + +**1. Never hard-code a colour.** Every colour goes through a token — including +`box-shadow`, `border`, SVG `fill`/`stroke`, and anything assigned from +JavaScript. A literal `#fff` is the single most common reason a tool fails to +flip to dark. If you need a colour the tokens don't cover, add it to *both* +`:root` and the dark block rather than inlining it. + +**2. `--primary` is a fill; `--primary-text` is for type.** Azure at `#4d63ff` +only reaches 4.05:1 on the page background, which fails WCAG AA below 18.66 px. +Use `--primary` for button fills and active states, `--primary-text` for +eyebrows, links, and any small azure text. + +### Dark mode + +Scheme follows the OS — there is no in-product toggle. Declare +`color-scheme: light dark` so native controls, scrollbars and form widgets come +along, then override the tokens: + +```css +@media (prefers-color-scheme: dark) { + :root { + --canvas: #20242e; --canvas-soft: #14161b; --line: #2f333d; + --ink: #eceff5; --body: #a9b0bf; --mute: #7d8494; + --primary: #8b9bff; --primary-text: #8b9bff; --on-primary: #111318; + } +} +``` + +Dark is **not** an inversion. The page stays the darkest surface with cards +lifted above it, the canvas is never pure `#000`, and the CTA flips polarity — +light azure fill with a near-black label. See the Dark Mode section of the +design system for the reasoning. + +Check both schemes before opening a PR: macOS System Settings → Appearance, or +Chrome DevTools → ⌘⇧P → *Emulate CSS prefers-color-scheme: dark*. + +### Illustrative colour + +The single-accent rule covers UI chrome. Genuinely illustrative content — a +game piece, chart series, a spinner's segments — may use other hues; prefer +`--accent-orange` / `--accent-cyan` or the semantic palette before inventing +one. + +The per-tool `color` in `tools.json` is a separate thing again. It tints the +**gallery card in the app**, not the tool, so it is deliberately *not* azure for +most tools — a varied gallery is easier to scan. It does not need to relate to +anything inside your tool. Pick a hue that is not already close to another +entry; if nothing distinct is left, brand azure `#4d63ff` is a fine default and +several tools already use it. + ## Add a tool — step by step 1. **Copy the template** into a new folder named with your tool's slug @@ -46,7 +113,8 @@ is the actual sandbox — so a reviewer still reads every tool's script. cp -r templates/tool-template unit-converter ``` - Edit `unit-converter/index.html` — title, accent color, and the actual tool. + Edit `unit-converter/index.html` — title, eyebrow, and the actual tool. Keep + the token block as-is; it is what makes light and dark mode work. 2. **Add a screenshot** at `screenshots/.webp` (one per tool, 1:1 by name). Generate it from your tool with the bundled helper: @@ -68,7 +136,8 @@ is the actual sandbox — so a reviewer still reads every tool's script. "name": "Unit Converter", // display name "description": "Convert between metric and imperial units.", // one line "glyph": "📐", // a single emoji - "color": "#6366f1", // #rrggbb — used for the card accent + "color": "#6366f1", // #rrggbb — tints the gallery card in the app, + // NOT the tool's own accent (that is always azure) "version": 1 // new tool = 1 } ``` @@ -93,8 +162,9 @@ is the actual sandbox — so a reviewer still reads every tool's script. ### Updating an existing tool -Edit its `index.html` → regenerate its screenshot → increment **that tool's** -`version` → bump `catalogVersion` → open a PR. +Edit its `index.html` → check it in **both light and dark** → regenerate its +screenshot → increment **that tool's** `version` → bump `catalogVersion` → open +a PR. ## Local commands diff --git a/Hillnote-Tools-Design-System.md b/Hillnote-Tools-Design-System.md index 19c8ac5..398af9c 100644 --- a/Hillnote-Tools-Design-System.md +++ b/Hillnote-Tools-Design-System.md @@ -1,11 +1,13 @@ --- version: alpha name: Hillnote-Tools-Design-System -description: The design language for Hillnote's tools surface — a heavy near-black display sans (Manrope at weight 800–900, 64–126 px) paired with a vivid azure brand accent, cool mist-tinted surface neutrals, and rounded white cards on a soft cool-grey canvas; the whole system reads more like a calm editorial knowledge workspace than a developer tool. +description: The design language for Hillnote's tools surface — a heavy near-black display sans (Manrope at weight 800–900, 64–126 px) paired with a vivid azure brand accent, cool mist-tinted surface neutrals, and rounded white cards on a soft cool-grey canvas; the whole system reads more like a calm editorial knowledge workspace than a developer tool. Dual-scheme — `colors` and `colors-dark` carry the same key set, so every token keeps its role and only its value flips at the `prefers-color-scheme` boundary. colors: primary: "#4d63ff" - on-primary: "#f4f6ff" + primary-text: "#3b50e0" + primary-on-ink: "#8b9bff" + on-primary: "#ffffff" primary-active: "#6f82ff" primary-neutral: "#b9c4ff" primary-pale: "#e6ebff" @@ -15,15 +17,53 @@ colors: mute: "#868a93" canvas: "#ffffff" canvas-soft: "#eef0f4" + line: "#dfe3ec" positive: "#22a559" positive-deep: "#0c5230" + positive-pale: "#e2f5ea" warning: "#f5b62a" warning-deep: "#9a5b00" warning-content: "#4a3a1c" + warning-pale: "#fdf1d8" negative: "#e0453f" negative-deep: "#b02a2a" negative-darkest: "#8f0f16" negative-bg: "#2e0a0a" + negative-pale: "#fbe3e2" + accent-orange: "#ff9e6b" + accent-cyan: "#38c9d6" + +# Dark mode. Same key set as `colors` — every token keeps its ROLE, only its +# value flips. `canvas` is always the card, `canvas-soft` is always the page, +# `ink` is always the highest-emphasis text. A consumer swaps the whole map at +# the `prefers-color-scheme: dark` boundary and needs no other changes. +colors-dark: + primary: "#8b9bff" + primary-text: "#8b9bff" + primary-on-ink: "#3b50e0" + on-primary: "#111318" + primary-active: "#a8b4ff" + primary-neutral: "#4d63ff" + primary-pale: "#232842" + ink: "#eceff5" + ink-deep: "#c9d3ff" + body: "#a9b0bf" + mute: "#7d8494" + canvas: "#20242e" + canvas-soft: "#14161b" + line: "#2f333d" + positive: "#3ddc84" + positive-deep: "#86efac" + positive-pale: "#14301f" + warning: "#fbbf24" + warning-deep: "#fcd34d" + warning-content: "#fde68a" + warning-pale: "#33280d" + negative: "#ff6b63" + negative-deep: "#ff9490" + negative-darkest: "#ffc4c1" + negative-bg: "#2e0a0a" + negative-pale: "#3a1412" accent-orange: "#ff9e6b" accent-cyan: "#38c9d6" @@ -157,7 +197,7 @@ components: text-input: backgroundColor: "{colors.canvas}" textColor: "{colors.ink}" - borderColor: "{colors.ink}" + borderColor: "{colors.line}" typography: "{typography.body-md}" rounded: "{rounded.md}" padding: "{spacing.md} {spacing.lg}" @@ -181,7 +221,7 @@ components: padding: "{spacing.xl}" card-feature-dark: backgroundColor: "{colors.ink}" - textColor: "{colors.primary}" + textColor: "{colors.primary-on-ink}" typography: "{typography.body-md}" rounded: "{rounded.xl}" padding: "{spacing.xl}" @@ -192,7 +232,7 @@ components: padding: "{spacing.3xl} {spacing.xl}" hero-band-dark: backgroundColor: "{colors.ink}" - textColor: "{colors.primary}" + textColor: "{colors.primary-on-ink}" typography: "{typography.display-mega}" padding: "{spacing.3xl} {spacing.xl}" content-band: @@ -203,7 +243,7 @@ components: capture-card: backgroundColor: "{colors.canvas}" textColor: "{colors.ink}" - borderColor: "{colors.ink}" + borderColor: "{colors.line}" typography: "{typography.body-md}" rounded: "{rounded.xl}" padding: "{spacing.xl}" @@ -214,8 +254,8 @@ components: rounded: "{rounded.pill}" padding: "{spacing.xs} {spacing.md}" badge-negative: - backgroundColor: "{colors.negative-bg}" - textColor: "{colors.on-primary}" + backgroundColor: "{colors.negative-pale}" + textColor: "{colors.negative-deep}" typography: "{typography.body-sm-strong}" rounded: "{rounded.pill}" padding: "{spacing.xs} {spacing.md}" @@ -297,11 +337,14 @@ Display typography is the second decisive voice. The `Manrope` family carries he Cards are universally pill-rounded — `{rounded.xl}` 24 px is the signature card radius. Buttons take the same 24 px pill-rectangle shape. The system never uses sharp corners on UI elements; the visual softness is part of the friendly, approachable voice. +The system is dual-scheme. Everything above describes light mode; `colors-dark` carries the same tokens for dark, where the mist canvas becomes a cool near-black `#14161b` and cards lift to `#20242e`. Surface contrast still carries elevation, and the azure lifts to `#8b9bff` so it survives a dark ground. See [Dark Mode](#dark-mode). + **Key Characteristics:** - A single azure CTA accent `{colors.primary}` (`#4d63ff`) — the universal primary action color. No second accent. - Two-face display typography — Manrope (open-source, weight 900, hero scale) + Inter (weight 600, sub-display scale). The contrast is the typographic story. - `{rounded.xl}` 24 px is the canonical card and button radius. Generous, friendly. - Cool mist canvas `{colors.canvas-soft}` (`#eef0f4`) is the hero surface; white `{colors.canvas}` is reserved for cards within the canvas band. +- Dual-scheme by OS preference — `colors` / `colors-dark` share one key set, so consumers swap the map and nothing else. No in-product theme toggle. - A full semantic palette: positive green family, warning amber family, negative red family — each documented with content / hover / active variants for in-product use. - Capture card on the hero — the signature interactive component, hosting a paste/drop input that turns any source into clean Markdown. @@ -309,6 +352,8 @@ Cards are universally pill-rounded — `{rounded.xl}` 24 px is the signature car ### Brand & Accent - **Hillnote Azure** (`{colors.primary}` — `#4d63ff`): The universal CTA color. Every primary button, every primary action pill, the logo accent. +- **Azure Text** (`{colors.primary-text}` — `#3b50e0`): The same azure darkened for *small text* on light surfaces — eyebrows, links, inline accents. `{colors.primary}` itself only clears 4.05:1 on the mist canvas, which fails AA below 18.66 px. Use `{colors.primary}` for fills, `{colors.primary-text}` for type. This is not a second accent — it is the same hue at an accessible tier. +- **Azure on Ink** (`{colors.primary-on-ink}` — `#8b9bff`): The azure for text sitting on an `{colors.ink}` band — the `card-feature-dark` / `hero-band-dark` moment. It is the *inverse* of `{colors.primary-text}`: because `{colors.ink}` flips polarity with the scheme, the azure on top of it has to flip the opposite way. Light `#8b9bff` on a near-black band, dark `#3b50e0` on a near-white one. Using plain `{colors.primary}` here reads 4.02:1 in light and collapses to 2.22:1 in dark. - **Azure Active** (`{colors.primary-active}` — `#6f82ff`): The lighter azure for active / hover state. - **Azure Neutral** (`{colors.primary-neutral}` — `#b9c4ff`): A mid-saturation azure used as a neutral active fill. - **Azure Pale** (`{colors.primary-pale}` — `#e6ebff`): The lightest azure for soft surface tints / badge backgrounds. @@ -316,6 +361,7 @@ Cards are universally pill-rounded — `{rounded.xl}` 24 px is the signature car ### Surface - **Canvas** (`{colors.canvas}` — `#ffffff`): Pure white for card interiors. - **Canvas Soft** (`{colors.canvas-soft}` — `#eef0f4`): The cool mist-tinted page background. Defining mood of the system. +- **Line** (`{colors.line}` — `#dfe3ec`): The hairline rule — input borders, card dividers, table row separators. A cool grey one step off the mist canvas, never full `{colors.ink}` except on the tertiary outline button. ### Text - **Ink** (`{colors.ink}` — `#111318`): Near-black with a faint cool cast — the default text and headings color. @@ -325,19 +371,86 @@ Cards are universally pill-rounded — `{rounded.xl}` 24 px is the signature car ### Semantic - **Positive** (`{colors.positive}` — `#22a559`): Success indicator. -- **Positive Deep** (`{colors.positive-deep}` — `#0c5230`): Pressed positive state. -- **Warning** (`{colors.warning}` — `#f5b62a`): Caution indicator. -- **Warning Deep** (`{colors.warning-deep}` — `#9a5b00`): Pressed warning. +- **Positive Deep** (`{colors.positive-deep}` — `#0c5230`): Pressed positive state, and text on positive tints. +- **Positive Pale** (`{colors.positive-pale}` — `#e2f5ea`): Soft green tint for success surfaces. +- **Warning** (`{colors.warning}` — `#f5b62a`): Caution indicator. Amber clears only 1.81:1 on white — treat it as a *fill or icon*, never as text. +- **Warning Deep** (`{colors.warning-deep}` — `#9a5b00`): Pressed warning, and the readable amber for text on white. - **Warning Content** (`{colors.warning-content}` — `#4a3a1c`): Text on warning surfaces. -- **Negative** (`{colors.negative}` — `#e0453f`): Destructive / error red. -- **Negative Deep** (`{colors.negative-deep}` — `#b02a2a`): Pressed destructive. +- **Warning Pale** (`{colors.warning-pale}` — `#fdf1d8`): Soft amber tint for caution surfaces. +- **Negative** (`{colors.negative}` — `#e0453f`): Destructive / error red. 4.12:1 on white — a fill and icon colour; use `{colors.negative-deep}` for error *text*. +- **Negative Deep** (`{colors.negative-deep}` — `#b02a2a`): Pressed destructive, and the readable red for text. - **Negative Darkest** (`{colors.negative-darkest}` — `#8f0f16`): Highest-emphasis destructive text. -- **Negative Bg** (`{colors.negative-bg}` — `#2e0a0a`): Dark maroon for destructive callout backgrounds. +- **Negative Bg** (`{colors.negative-bg}` — `#2e0a0a`): Dark maroon for destructive callout backgrounds. Already dark — unchanged across both schemes. +- **Negative Pale** (`{colors.negative-pale}` — `#fbe3e2`): Soft red tint for error surfaces. ### Brand Accent — Tertiary - **Accent Orange** (`{colors.accent-orange}` — `#ff9e6b`): Warm coral used inside illustrative content / pricing cards. - **Accent Cyan** (`{colors.accent-cyan}` — `#38c9d6`): Bright teal used as a tertiary illustration accent. +Both tertiary accents are mid-lightness and legible on either scheme, so they carry the same value in `colors-dark`. + +## Dark Mode + +The system is dual-scheme. `colors` is the light palette; `colors-dark` is its counterpart, carrying **the same key set**. Every token keeps its role and only its value flips, so a consumer swaps the whole map at the scheme boundary and changes nothing else: + +- `{colors.canvas}` is *always the card*. `{colors.canvas-soft}` is *always the page.* +- `{colors.ink}` is *always the highest-emphasis text*, `{colors.body}` secondary, `{colors.mute}` lowest. +- `{colors.primary}` is *always the CTA fill*, `{colors.on-primary}` *always the label on top of it.* + +### The polarity model + +Dark mode is **not an inversion** of the light palette. Three rules define it: + +1. **The page is the darkest surface; cards sit above it.** Light mode runs mist page → white card. Dark mode runs `#14161b` page → `#20242e` card. The direction of the lift is preserved, so surface contrast still carries elevation and no shadows are needed. +2. **Never pure black.** The dark canvas keeps the same faint cool cast as `{colors.ink}`. Pure `#000` reads harsh and destroys the card lift. +3. **The CTA flips polarity.** In light mode the pill is azure with a near-white label. In dark mode it becomes *light azure with a near-black label* — `primary` lifts to `#8b9bff` and `on-primary` becomes `#111318`. A saturated `#4d63ff` fill on a near-black page reads muddy and puts the label below AA. The lifted azure doubles as the accent-text colour, which is why `primary-text` and `primary` converge in dark mode. + +### Implementation contract + +Scheme follows the OS via `prefers-color-scheme` — there is no in-product theme toggle. Declare `color-scheme` so native form controls, scrollbars and spell-check underlines follow along: + +```css +:root { + color-scheme: light dark; + --canvas: #ffffff; --canvas-soft: #eef0f4; --line: #dfe3ec; + --ink: #111318; --body: #454852; --mute: #868a93; + --primary: #4d63ff; --primary-text: #3b50e0; --on-primary: #f4f6ff; +} + +@media (prefers-color-scheme: dark) { + :root { + --canvas: #20242e; --canvas-soft: #14161b; --line: #2f333d; + --ink: #eceff5; --body: #a9b0bf; --mute: #7d8494; + --primary: #8b9bff; --primary-text: #8b9bff; --on-primary: #111318; + } +} +``` + +Consume every colour through a token. A hard-coded `#fff` or `rgba(0,0,0,.3)` is the single most common way a surface fails to flip — including `box-shadow`, `border`, SVG `fill`/`stroke`, and any colour set from JavaScript. + +### Contrast & accessibility + +Measured WCAG 2.1 ratios for the pairings that carry text. Body copy targets 4.5:1; non-text indicators and `{colors.mute}` fine print target 3:1. + +| Pairing | Light | Dark | +|---|---|---| +| `ink` on `canvas` | 18.58:1 | 13.47:1 | +| `ink` on `canvas-soft` | 16.29:1 | 15.71:1 | +| `body` on `canvas` | 9.12:1 | 7.13:1 | +| `mute` on `canvas` | 3.46:1 | 4.14:1 | +| `primary-text` on `canvas-soft` | 5.40:1 | 7.09:1 | +| `primary-text` on `canvas` | 6.16:1 | 6.08:1 | +| `on-primary` on `primary` (CTA label) | 4.62:1 | 7.28:1 | +| `positive-deep` on `positive-pale` | 8.16:1 | 10.16:1 | +| `positive-deep` on `primary-pale` (badge) | 7.81:1 | 10.29:1 | +| `warning-content` on `warning-pale` | 9.80:1 | 11.63:1 | +| `negative-deep` on `negative-pale` | 5.35:1 | 7.67:1 | +| `canvas` vs `canvas-soft` (elevation) | 1.14:1 | 1.17:1 | + +**Resolved.** The light CTA label was `#f4f6ff` on `{colors.primary}`, which measured 4.29:1 and fell just short of AA for a 16 px label. `{colors.on-primary}` is now pure `#ffffff`, taking it to 4.62:1 — visually indistinguishable, and it leaves the brand fill `{colors.primary}` untouched. (Darkening the fill to `#4459f0` would have reached 4.95:1 but shifts the brand hue, so it was rejected.) + +**Remaining gap.** `{colors.mute}` is a deliberate 3:1 fine-print token and clears that comfortably on every surface in both schemes. It does not reach the stricter 4.5:1 body-text bar: 3.46:1 on `{colors.canvas}` and 3.03:1 on `{colors.canvas-soft}` in light, 4.14:1 and 4.83:1 in dark. Strict WCAG AA grants no exemption for small or low-priority text, so `{colors.mute}` set below 18.66 px is technically non-conforming — most visibly in light mode. Closing it would mean roughly `#686c77` in light and `#8a91a1` in dark, at the cost of visibly heavier fine print throughout. The 3:1 target is retained as an explicit, informed trade-off rather than an oversight. + ## Typography ### Font Family @@ -407,10 +520,12 @@ Photography is sparse; the system prefers illustrative SVGs and product mockups | Level | Treatment | Use | |---|---|---| | Level 0 — Flat | No shadow, no border. | Default. | -| Level 1 — Hairline on Dark | 1 px solid `{colors.ink}` border. | Tertiary outline buttons, form inputs. | -| Level 2 — Soft Card | Implicit Level 0 white card sitting on mist canvas — the surface contrast IS the elevation. | Cards on the mist hero band. | +| Level 1 — Hairline | 1 px solid `{colors.line}` border; `{colors.ink}` for the tertiary outline button only. | Form inputs, dividers, outline buttons. | +| Level 2 — Soft Card | Implicit Level 0 `{colors.canvas}` card sitting on `{colors.canvas-soft}` — the surface contrast IS the elevation. | Cards on the hero band. | + +The system uses surface contrast (`{colors.canvas-soft}` page vs `{colors.canvas}` cards) as the primary elevation cue, and this is scheme-independent: the card is lighter than the page in light mode and *also* lighter than the page in dark mode. The lift never inverts. -The system uses surface contrast (`{colors.canvas-soft}` background vs `{colors.canvas}` cards) as the primary elevation cue. +Because elevation is carried by surface rather than shadow, the model needs no dark-mode special case — which is the main reason drop shadows stay out of the system. A shadow tuned for a white page disappears against `#14161b`; a surface lift does not. Where a shadow is genuinely required (modals, toasts), tint it from `{colors.ink}` rather than pure black so it flips with the scheme. ## Shapes @@ -453,16 +568,16 @@ The system uses surface contrast (`{colors.canvas-soft}` background vs `{colors. **`card-feature-green`** — the soft-azure feature card. - Background `{colors.primary-pale}`, text `{colors.ink}`, padding `{spacing.xl}`, shape `{rounded.xl}`. -**`card-feature-dark`** — the polarity-flipped dark card with azure text. -- Background `{colors.ink}`, text `{colors.primary}` (Hillnote azure), padding `{spacing.xl}`, shape `{rounded.xl}`. Used for promotional moments. +**`card-feature-dark`** — the polarity-flipped card with azure text. +- Background `{colors.ink}`, text `{colors.primary-on-ink}`, padding `{spacing.xl}`, shape `{rounded.xl}`. Used for promotional moments. "Dark" names the *polarity*, not a fixed colour: because the surface is `{colors.ink}`, this card is near-black inside a light page and near-white inside a dark one. It always opposes the page it sits on. 7.28:1 light / 5.35:1 dark. **`capture-card`** — the system's signature interactive widget. -- Background `{colors.canvas}`, text `{colors.ink}`, 1 px solid `{colors.ink}` border, padding `{spacing.xl}`, shape `{rounded.xl}`. Hosts a paste/drop input that turns any source (link, note, clipboard) into clean Markdown. +- Background `{colors.canvas}`, text `{colors.ink}`, 1 px solid `{colors.line}` border, padding `{spacing.xl}`, shape `{rounded.xl}`. Hosts a paste/drop input that turns any source (link, note, clipboard) into clean Markdown. ### Inputs & Forms **`text-input`** — the canonical text input. -- Background `{colors.canvas}`, text `{colors.ink}`, 1 px solid `{colors.ink}` border, body in `{typography.body-md}`, padding `{spacing.md} {spacing.lg}`, shape `{rounded.md}`. +- Background `{colors.canvas}`, text `{colors.ink}`, 1 px solid `{colors.line}` border, body in `{typography.body-md}`, padding `{spacing.md} {spacing.lg}`, shape `{rounded.md}`. Focus takes a `{colors.primary}` border plus a 3 px `{colors.primary-pale}` ring. ### Navigation @@ -480,8 +595,8 @@ The system uses surface contrast (`{colors.canvas-soft}` background vs `{colors. **`hero-band`** — the mist-canvas hero band. - Background `{colors.canvas-soft}`, text `{colors.ink}`, padding `{spacing.3xl} {spacing.xl}`. Headline in `{typography.display-mega}` (Manrope weight 900). -**`hero-band-dark`** — the polarity-flipped dark hero. -- Background `{colors.ink}`, text `{colors.primary}` (Hillnote azure headline on near-black!), same padding / scale. +**`hero-band-dark`** — the polarity-flipped hero. +- Background `{colors.ink}`, text `{colors.primary-on-ink}`, same padding / scale. Azure headline on near-black in light mode; the same band inverts to azure-on-near-white in dark. Same polarity caveat as `card-feature-dark`. **`content-band`** — the white content band that follows hero. - Background `{colors.canvas}`, text `{colors.ink}`, padding `{spacing.3xl} {spacing.xl}`. Section headline in `{typography.display-md}`. @@ -490,7 +605,7 @@ The system uses surface contrast (`{colors.canvas-soft}` background vs `{colors. - Background `{colors.primary-pale}`, text `{colors.positive-deep}`, body in `{typography.body-sm-strong}`, padding `{spacing.xs} {spacing.md}`, shape `{rounded.pill}`. **`badge-negative`** — the negative status pill. -- Background `{colors.negative-bg}`, text white, body in `{typography.body-sm-strong}`, padding `{spacing.xs} {spacing.md}`, shape `{rounded.pill}`. +- Background `{colors.negative-pale}`, text `{colors.negative-deep}`, body in `{typography.body-sm-strong}`, padding `{spacing.xs} {spacing.md}`, shape `{rounded.pill}`. Mirrors `badge-positive`'s tint-plus-deep-text pattern, so it flips cleanly: 5.35:1 light / 7.67:1 dark. It previously paired `{colors.negative-bg}` with `{colors.on-primary}`, which measured 1.03:1 in dark — `negative-bg` is a scheme-stable dark surface, so a flipping text token cannot sit on it. Reserve `{colors.negative-bg}` for full destructive callouts with an explicitly light text colour. ### Examples (illustrative) @@ -533,12 +648,18 @@ The system uses surface contrast (`{colors.canvas-soft}` background vs `{colors. - Reserve `{colors.primary}` Hillnote azure for every primary CTA. The azure pill IS the system's conversion signature. - Set hero headlines in `{typography.display-mega}` / `{typography.display-xl}` Manrope weight 900. Never lighter. - Use `{rounded.xl}` 24 px for buttons and cards. The generous radius is the system's friendliness signature. -- Cycle page surfaces in `{colors.canvas-soft}` mist canvas → `{colors.canvas}` white cards. Surface contrast carries elevation. +- Cycle page surfaces `{colors.canvas-soft}` → `{colors.canvas}` cards. Surface contrast carries elevation in both schemes. - Use the full semantic palette (positive / warning / negative) for in-product status — never repurpose the azure CTA color as a success indicator since it IS the brand's primary action. +- Reach for `{colors.primary-text}` when azure is *type* and `{colors.primary}` when azure is a *fill*. +- Declare `color-scheme: light dark` so native controls, scrollbars and form widgets follow the scheme. ### Don't - Don't introduce a second brand accent. Hillnote azure is the sole identity colour. - Don't render the hero in weight 700 or lighter. The system's display weight is 900. - Don't render CTAs as sharp rectangles. The 24 px pill geometry is non-negotiable. -- Don't pair the azure CTA with an azure background. Always sit the azure on neutral surfaces (mist / white / ink). +- Don't pair the azure CTA with an azure background. Always sit the azure on neutral surfaces (canvas / ink). - Don't swap the heavy display face for a thin geometric sans — the weight-900 Manrope display IS the system's voice. +- Don't hard-code a colour anywhere — including `box-shadow`, `border`, SVG `fill`/`stroke`, and colours assigned from JavaScript. A literal `#fff` is what breaks dark mode. +- Don't use `#000` as the dark canvas, and don't simply invert the light palette. Follow the polarity model above. +- Don't carry `{colors.primary}` `#4d63ff` into dark mode as a fill or as accent text — it goes muddy on a near-black page. That is what `colors-dark` is for. +- Don't set `{colors.warning}` or `{colors.negative}` as text on `{colors.canvas}` — they measure 1.81:1 and 4.12:1. Use the `-deep` variants for type. diff --git a/ambient-sounds/index.html b/ambient-sounds/index.html index 39f6771..d88faf1 100644 --- a/ambient-sounds/index.html +++ b/ambient-sounds/index.html @@ -3,18 +3,41 @@ - + Ambient Sounds diff --git a/markdown-table/index.html b/markdown-table/index.html index 0ff4991..9241bbe 100644 --- a/markdown-table/index.html +++ b/markdown-table/index.html @@ -11,10 +11,11 @@ Palette + shapes follow the Hillnote design system: azure primary, cool-mist canvas, near-black ink, generous 24px card radius. */ :root { + color-scheme: light dark; --primary: #4d63ff; --primary-active: #6f82ff; --primary-pale: #e6ebff; - --on-primary: #f4f6ff; + --on-primary: #ffffff; --ink: #111318; --body: #454852; --mute: #868a93; @@ -28,6 +29,28 @@ --display: "Manrope", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --mono: ui-monospace, "SF Mono", "SFMono-Regular", Menlo, Consolas, monospace; + --negative: #e0453f; + --negative-pale: #fbe3e2; + --primary-text: #3b50e0; + } + + @media (prefers-color-scheme: dark) { + :root { + --primary: #8b9bff; + --primary-active: #a8b4ff; + --primary-pale: #232842; + --on-primary: #111318; + --ink: #eceff5; + --body: #a9b0bf; + --mute: #7d8494; + --canvas: #20242e; + --canvas-soft: #14161b; + --line: #2f333d; + --positive: #3ddc84; + --negative: #ff6b63; + --negative-pale: #3a1412; + --primary-text: #8b9bff; + } } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } @@ -51,7 +74,7 @@ font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; - color: var(--primary); + color: var(--primary-text); } header h1 { font-family: var(--display); @@ -61,12 +84,6 @@ line-height: 1.05; margin-top: 6px; } - header p { - color: var(--body); - font-size: 0.95rem; - line-height: 1.5; - margin-top: 8px; - } .layout { width: 100%; @@ -146,7 +163,7 @@ align-items: center; justify-content: center; } - .icon-btn:hover { border-color: var(--primary); color: var(--primary); } + .icon-btn:hover { border-color: var(--primary); color: var(--primary-text); } .icon-btn:disabled { opacity: 0.35; cursor: not-allowed; } .icon-btn:disabled:hover { border-color: var(--line); color: var(--ink); } @@ -200,7 +217,7 @@ align-items: center; justify-content: center; } - .row-remove:hover { color: #e0453f; background: #fff0f0; } + .row-remove:hover { color: var(--negative); background: var(--negative-pale); } /* Output */ .output { @@ -208,7 +225,7 @@ font-size: 0.82rem; line-height: 1.6; background: var(--ink); - color: #e7e9f2; + color: var(--canvas); border-radius: var(--r-lg); padding: 16px; white-space: pre; @@ -262,7 +279,6 @@
Writing · Markdown

Markdown Table Generator

-

Build a table visually, set per-column alignment, and copy clean, ready-to-paste Markdown.

diff --git a/metronome/index.html b/metronome/index.html index 90ebc3a..f6186a4 100644 --- a/metronome/index.html +++ b/metronome/index.html @@ -12,10 +12,11 @@ localStorage. Palette + shapes follow the Hillnote design system: azure primary, cool-mist canvas, near-black ink, generous 24px card radius. */ :root { + color-scheme: light dark; --primary: #4d63ff; --primary-active: #6f82ff; --primary-pale: #e6ebff; - --on-primary: #f4f6ff; + --on-primary: #ffffff; --ink: #111318; --body: #454852; --mute: #868a93; @@ -29,6 +30,25 @@ --r-xl: 24px; --display: "Manrope", "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; --sans: "Inter", system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; + --primary-text: #3b50e0; + } + + @media (prefers-color-scheme: dark) { + :root { + --primary: #8b9bff; + --primary-active: #a8b4ff; + --primary-pale: #232842; + --on-primary: #111318; + --ink: #eceff5; + --body: #a9b0bf; + --mute: #7d8494; + --canvas: #20242e; + --canvas-soft: #14161b; + --line: #2f333d; + --accent-amber: #fbbf24; + --negative: #ff6b63; + --primary-text: #8b9bff; + } } *, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; } @@ -53,7 +73,7 @@ font-weight: 600; letter-spacing: 0.14em; text-transform: uppercase; - color: var(--primary); + color: var(--primary-text); } header h1 { font-family: var(--display); @@ -63,12 +83,6 @@ line-height: 1.05; margin-top: 6px; } - header p { - color: var(--body); - font-size: 0.95rem; - line-height: 1.5; - margin-top: 8px; - } .layout { width: 100%; @@ -118,7 +132,7 @@ margin-top: 4px; font-size: 0.85rem; font-weight: 600; - color: var(--primary); + color: var(--primary-text); letter-spacing: 0.02em; } @@ -223,7 +237,7 @@ transition: background 0.12s, color 0.12s, border-color 0.12s; } .seg button:hover { background: var(--canvas-soft); } - .seg button.active { background: var(--primary-pale); color: var(--primary); border-color: var(--primary); } + .seg button.active { background: var(--primary-pale); color: var(--primary-text); border-color: var(--primary); } .tap { border: 1px dashed var(--line); @@ -253,7 +267,6 @@
Focus · Rhythm

Metronome

-

Keep perfect time. Set the tempo, choose a beat, and practise to an accurate click — no internet, no accounts.

@@ -262,14 +275,14 @@

Metronome

diff --git a/mood-tracker/index.html b/mood-tracker/index.html index 504a0c5..ad71ed3 100644 --- a/mood-tracker/index.html +++ b/mood-tracker/index.html @@ -3,18 +3,39 @@ - + Mood Tracker diff --git a/readability-score/index.html b/readability-score/index.html index 5230820..096fde6 100644 --- a/readability-score/index.html +++ b/readability-score/index.html @@ -3,18 +3,59 @@ - + Readability Score + The demo shows the conventions: offline, persists state locally, no + network, and every colour driven by a token so dark mode works for free. -->
-

🛠️ My Tool

+
Category · Subcategory
+

My Tool

A one-line description of what it does.

0
-
+