diff --git a/GUIDELINES.md b/GUIDELINES.md deleted file mode 100644 index 703d6da..0000000 --- a/GUIDELINES.md +++ /dev/null @@ -1,783 +0,0 @@ -# Niveus Design Guidelines - -**Status:** normative · **Version:** 1.0 · **Applies to:** every person and every -AI agent that designs with, builds on, or changes Niveus. - -This document says what Niveus is, what it decides, and what it forbids. It is -written to be followed, not admired. Where it conflicts with a habit, a -preference, or a model's prior, this document wins. - -The key words **MUST**, **MUST NOT**, **SHOULD**, **SHOULD NOT** and **MAY** are -used as in [RFC 2119](https://www.rfc-editor.org/rfc/rfc2119). A **MUST** is -enforced by a tool or is a defect when broken. A **SHOULD** needs a stated -reason to break. A **MAY** is a freedom, not a recommendation. - ---- - -## Contents - -1. [How to use this document](#1-how-to-use-this-document) -2. [Identity](#2-identity) -3. [Principles and their order](#3-principles-and-their-order) -4. [Materials](#4-materials) -5. [Glaze](#5-glaze) -6. [Elevation and light](#6-elevation-and-light) -7. [Color and appearance](#7-color-and-appearance) -8. [Type, space and shape](#8-type-space-and-shape) -9. [Motion and continuity](#9-motion-and-continuity) -10. [Interaction and state](#10-interaction-and-state) -11. [Accessibility](#11-accessibility) -12. [Components](#12-components) -13. [Customization contract](#13-customization-contract) -14. [Authoring rules](#14-authoring-rules) -15. [Protocol for AI agents](#15-protocol-for-ai-agents) -16. [Changing the system](#16-changing-the-system) -17. [Anti-patterns](#17-anti-patterns) -18. [Compliance checklist](#18-compliance-checklist) - ---- - -## 1. How to use this document - -### 1.1 Sources of truth, in order of authority - -When two sources disagree, the higher one is right and the lower one has a bug. - -| # | Source | Answers | -| - | ------ | ------- | -| 1 | `tools/check.py`, `tools/build.py --check` | What is *allowed*. Mechanical, not negotiable. | -| 2 | **This document** | What is *intended*, and why. | -| 3 | [`REFERENCE.md`](REFERENCE.md) | What exists: every class, token, attribute, function, event. | -| 4 | [`README.md`](README.md) | How to install and orient. | -| 5 | The CSS and JS | How it is currently built. | - -`REFERENCE.md` is the complete API. If a class, token or attribute is not in it, -**it does not exist** and MUST NOT be used, guessed, or invented. - -### 1.2 Who this is for - -- **Designers and engineers** use it to decide, and to review each other. -- **AI agents** use it as an operating contract. Section 15 is written for them - directly; sections 2–14 apply to them as to anyone. -- **Product authors** who ship Niveus to end users use section 13 to decide what - their users may customize. - -### 1.3 What this document is not - -It is not a style preference list. Every rule here exists because breaking it -produces a specific, nameable failure: an unreadable surface, a broken theme, a -control that behaves differently in two places, a port that has drifted. Rules -state their reason so they can be applied to cases the document did not foresee. - ---- - -## 2. Identity - -Niveus is a **design-system layer**, not a component library bolted onto an -application. Applications consume it; they do not redefine it. - -**It is:** tactile (controls are bevelled keys with light falling across them), -adaptive (one brand colour drives light and dark), and continuous (state -changes can be followed). Fields are wells cut into a surface. Windows are -solid. Glass is kept for what floats. - -**It is not:** a flat system, a material-for-its-own-sake system, or a -glassmorphism kit. Skeuomorphism here is **functional**: a surface imitates a -physical property only where that property answers a question the user is -already asking. - -> Niveus is not about making interfaces look physical. It is about making -> digital surfaces feel understandable. - -The questions a surface must answer are: **What is this? Is it above or below? -Where did it come from? Did my action register?** An effect that answers none -of them is decoration, and decoration makes an interface tiring. - -### 2.1 Naming - -Names in Niveus describe a thing's role, never another vendor's product. A -feature MUST NOT be named after a third party's trademark, and documentation -MUST NOT present a Niveus feature as a clone of one. (Niveus's adjustable-frost -glass is called **Glaze** for this reason.) - ---- - -## 3. Principles and their order - -| Principle | Rule | What it costs | -| --------- | ---- | ------------- | -| **Clarity** | Every element has a purpose. Decoration never competes with content. | Fewer flourishes. | -| **Soft physicality** | Depth comes from shadow, highlight and inset, not ornament. | No gratuitous textures. | -| **Material** | Surfaces are made of something: solid, elevated, inset, frosted, glass, glaze. | Every surface must pick one. | -| **Restraint** | No effect exists because it is possible. Translucency and shadow are spent, not sprinkled. | Saying no to good-looking things. | -| **Consistency** | The same control behaves the same way everywhere, including under a pointer. | No local variations. | -| **Adaptability** | Nothing is defined in terms of one appearance or one colour. | Every value is a role. | -| **Continuity** | Nothing pops into a state the user could have watched it reach. | Motion must be earned and bounded. | -| **Customizability** | Where the author allows it, the person using the product can tune it. | A bounded, documented set of dials. | - -### 3.1 Precedence - -When principles conflict, resolve in this order: - -1. **Accessibility** (section 11) overrides everything. A beautiful surface that - cannot be read or operated is broken. -2. **Clarity** overrides material and motion. -3. **Restraint** overrides every effect. A quiet interface with one thing moving - reads clearly; a busy one does not. -4. **Consistency** overrides local preference. -5. **Customizability** operates *inside* the limits above, never through them. - ---- - -## 4. Materials - -A material answers *what light does when it hits a surface*. Elevation answers -*how far the surface is from the canvas*. They are independent and combine -freely. - -| Material | Class | Use for | MUST NOT be used for | -| -------- | ----- | ------- | -------------------- | -| Solid | `.nv-material-solid` | Content. The default. | — | -| Elevated | `.nv-material-elevated` | Surfaces that must be read before their context. | — | -| Inset | `.nv-material-inset` | Fields, wells, tracks: anything recessed. | Anything that is pressed *toward* the user. | -| Frosted | `.nv-material-frosted` | Chrome that content passes under: bars, docks, menus, toolbars. | Containers of body copy or forms. | -| Glass | `.nv-material-glass` | Contextual layers where the content behind must stay legible. | Surfaces with no content behind them. | -| Glaze | `.nv-material-glaze` | Floating, interactive panes where the visitor's control over frost is a feature. See section 5. | Whole-page backgrounds; anything the user must read at length. | - -### 4.1 Rules - -0. **One dimensionality per interface.** Niveus is skeuomorphic *all the way - through*. A raised key, a recessed well and a lit lens are physical objects; - a flat colour block beside them reads as a different system. Every - interactive or selected element MUST take its shape from the depth - vocabulary — bevel, sheen, inset, ledge, outline, elevation — and MUST NOT be - a flat fill. If a state seems to need a flat wash (a hover, a selection band, - a pressed toggle), give it depth instead: press it *into* the surface, or - lay a lit lens over it. Mixing flat and physical is the single most common - way a Niveus interface starts to look unfinished. - -1. **Solid by default.** Anything that *holds content* — windows, cards, dialogs, - tables, forms — is solid. Translucency is reserved for what *floats over* - content. -2. **Translucency means "this floats".** Used everywhere, it stops meaning - anything, tints whatever is behind it, and forces the GPU to composite. - Spend it. -3. **Raised things are bevelled and sheened.** Lit along the top edge, shaded - along the bottom, with a contact outline. Compose with - `box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-2)` and - `background-image: var(--nv-sheen-raised)` over a role colour. -4. **Never invent a material.** If none fits, the right answer is a different - composition, or a proposal under section 16 — not a local `backdrop-filter`. -5. **Translucent materials MUST degrade.** `prefers-reduced-transparency` and - missing `backdrop-filter` fall back to the solid equivalent. A new - translucent surface without that fallback is incomplete. - ---- - -## 5. Glaze - -Glaze is glass with a dial. It exists to make Niveus feel *alive* — a surface -that catches light and answers to the person looking at it — without giving up -legibility. - -### 5.1 Anatomy - -| Part | Mechanism | Controlled by | -| ---- | --------- | ------------- | -| **Frost** | Backdrop blur, tint opacity and saturation, moved together | `--nv-glass-frost`, `0` (clear) … `1` (heavy frost); range set by `--nv-glaze-blur-max`, `--nv-glaze-tint-min`, `--nv-glaze-tint-max` | -| **Rim** | A 1.5px masked gradient border, bright on the side facing the light | `--nv-glaze-rim` (width), `--nv-color-glaze-rim-lit` / `-far` | -| **Specular** | A soft bloom under the pointer | `--nv-color-glaze-specular` | -| **Light** | Pointer position and angle written per surface | `niveus.js` → `--nv-light-x/-y/-angle` | - -Frost moves blur, tint and saturation **together** on purpose. A pane that -changed only one of them would stop looking like the same pane. - -### 5.2 Rules - -1. **Frost is read where the surface is drawn.** Set `--nv-glass-frost` on - `:root` for the site, or on any subtree to change just that region. Never - copy a frost value into another token; that breaks subtree overrides. -2. **Something colourful MUST be behind it.** Glaze over a flat canvas looks like - a grey film. Use it over imagery, gradients, or an `.nv-bed`. If nothing is - behind it, use `elevated`. -3. **Never place Glaze behind work.** Forms, tables and long reading belong on - solid surfaces. Glaze is for what the user passes *through*: pickers, - popovers, docks, notifications, hover cards, command surfaces. -4. **Text on Glaze MUST meet contrast at every frost the person can choose.** - Clear glass over a busy backdrop is the worst case. A pane carrying body text - SHOULD bound its slider (`min="0.2"` or higher) so the lowest setting is - still legible. Verify against the busiest content that can sit behind it. -5. **Do not stack Glaze.** Glaze over Glaze compounds blur and tint into mud. - One translucent layer between content and the eye. -6. **Do not animate the blur radius.** Blur is expensive; change frost by - discrete steps or on user input, not as ambient motion. -7. **No JavaScript, no problem.** Without `niveus.js` the light sits at its - default (top left) and the material is still correct. Light tracking MUST - NOT be required for legibility, and is skipped for touch and under reduced - motion. -8. **Position.** Glaze carries zero-specificity `position: relative`; a menu or - dialog using it keeps the positioning its own component gave it. - ---- - -## 6. Elevation and light - -Six levels, each describing one relationship between a surface and the canvas. -A shadow that is on neither scale is a shadow that means nothing. - -| Level | Class | Relationship | Typical use | -| ----- | ----- | ------------ | ----------- | -| 0 | `.nv-elevation-0` | flush | painted on the canvas | -| 1 | `.nv-elevation-1` | resting | cards, rows, panels | -| 2 | `.nv-elevation-2` | raised | anything that reacts | -| 3 | `.nv-elevation-3` | floating | popovers, menus | -| 4 | `.nv-elevation-4` | lifted | dialogs, sheets | -| 5 | `.nv-elevation-5` | overlay | topmost; used sparingly | - -Inset depth uses `.nv-elevation-inset-{sm,md,lg}`. - -1. **Light comes from above.** Highlights sit on top edges, shade on bottom - edges. A highlight on a lower edge is a bug. -2. **Dark is not light inverted.** Surfaces get *lighter* as they rise, shadows - deepen, and the top highlight thins. Elevation therefore carries a tint, not - only a shadow. -3. **Elevation tracks the user's mental model, not the visual taste.** Raise - what the user can act on or must notice; do not raise to decorate. -4. **A recess catches light on its lower lip** (`--nv-ledge`). Wells are cut into - the surface, not painted darker. - ---- - -## 7. Color and appearance - -### 7.1 The model - -The whole colour system has **one input**: `--nv-brand`. Every role, in both -appearances, is derived from it with relative colour syntax. There is no second -place to define a colour. - -- A component names a **role** (`--nv-color-*`); the appearance decides what the - role is made of, through `light-dark()`. -- `--nv-palette-*` is raw and does not adapt. **MUST NOT** be referenced from - application code. -- Appearance is `data-theme="light" | "dark"`, or absent to follow the OS. -- A subtree may rebrand with `data-nv-brand`. - -### 7.2 Rules - -1. **No literal colours** in components or examples (the sole exception is the - value of `--nv-brand`). `tools/check.py` fails on any. -2. **Every role exists in both appearances.** Adding `--nv-light-x` without - `--nv-dark-x` fails the parity check. -3. **Contrast is measured, not assumed.** The derived label on any filled colour - clears 4.5:1 for any brand. New roles that carry text MUST do the same. -4. **State is never colour alone.** Pair colour with a shape, icon, text or - attribute change (see 11). -5. **Semantic colours mean one thing.** `success`, `warning`, `danger` carry - meaning; they are not accents. Use `accent` for emphasis and brand. -6. **Every neutral carries the brand.** Surfaces, canvases, glass and shadows - are mixed with `--nv-brand` in *both* appearances, so changing one value - re-colours the whole interface and light and dark read as one material. A - neutral that ignores the brand — a literal grey or a bright white field — is - a defect. Light is a soft stone, not paper-white: a glaring field flattens - the bevels meant to shape it. -7. **No native colour survives.** Anything the browser draws itself (checkbox - ticks, range and progress fills, the caret, selection, date glyphs) takes the - brand through `accent-color`, `caret-color` and `::selection`. Do not leave a - control in its default blue. -8. **Dark mode is designed, not generated.** When a new surface is added, look at - it in dark. If it only works on white, it is not finished. - ---- - -## 8. Type, space and shape - -- **Type.** Instrument Sans for the interface, JetBrains Mono for code and - engraved labels, shipped in `fonts/`. Use the role classes — `.nv-display`, - `.nv-heading`, `.nv-title`, `.nv-body`, `.nv-label`, `.nv-caption`, - `.nv-overline` — not raw sizes. Sizes are in `rem` so the user's own font - size is respected. Tabular figures (`font-variant-numeric: tabular-nums`) for - any number that changes or aligns. -- **Space.** A 4px rhythm, expressed as `--nv-space-*`. Values are tokens; a - one-off `13px` is a defect. -- **Shape.** `--nv-radius-*`. Nested radii MUST be concentric: use - `--nv-radius-nested-*`, or the outer radius minus the gap. Pills - (`--nv-radius-full`) are for pills, avatars and switches. -- **Logical properties.** Use `inline`/`block` (`padding-inline`, - `inset-block-start`), never `left`/`right`, so the system works in RTL. - Where a property has no logical form (`translate`), flip it explicitly for - RTL. -- **Application-specific values** that describe *your content* — a 15rem sidebar, - an aspect ratio, a grid template — are yours. A value that restates the design - language is the system's, and hard-coding it is how a system quietly stops - being one. - ---- - -## 9. Motion and continuity - -Every interaction MUST read as a continuation of the state before it. The goal is -not to animate everything. It is that **nothing pops into a state the user could -have watched it reach.** - -### 9.1 Rules - -1. **Motion explains.** It shows where something came from, where it went, or - that an action registered. Motion with no such job is removed. -2. **Durations and easings are tokens.** `--nv-duration-*`, `--nv-ease-*`, - `--nv-transition-*`. A literal `200ms` is a defect, and it bypasses reduced - motion. -3. **Reduced motion collapses at the token level.** Components never opt in - individually. `prefers-reduced-motion` skips view transitions entirely; the - change still happens, it just arrives. -4. **Selection travels.** When emphasis moves between elements, the marker - itself moves (shared `view-transition-name`). When a panel replaces another, - it slides in the direction of travel. -5. **View transition names are unique per document.** A duplicate makes the - browser skip the transition. Give each selection group its own - `--nv-selection-name`. -6. **Origin matters.** Menus scale out of their trigger's edge; dialogs expand - away from the control that opened them. -7. **No JavaScript, no breakage.** Continuity enhances; it MUST NOT be required - for function. -8. **Edges give.** A scroller that reaches its end yields and springs back - (rubber-band) instead of stopping dead, the way a phone's list does. Use the - spring easing token, cap the stretch, and leave touch to the platform where - it already does this. The wheels in the time picker are the reference. -9. **Let scroll drive depth.** Where an effect is a function of scroll - position — a drum's rows fading and tilting away from the centre — express it - as a scroll-driven animation in CSS, not as script, so it tracks a finger - exactly and costs no frame budget. Provide a fallback where the feature is missing — either the plain state, or the same curve from script (as the wheels do for Firefox) — and test input differences between engines: wheel notches arrive in pixels in Chromium and in *lines* in Firefox, so normalise `deltaMode` before using `deltaY`. -10. **Local motion for local change.** Changing a month moves the month, not the - page. Reserve page-wide view transitions for changes that are about the - page: a route change, never appearance or selection — see 14.4.1. -11. **Restraint wins ties.** When continuity and restraint conflict, animate less. - ---- - -## 10. Interaction and state - -1. **One treatment per state, written once per component.** Hover lift, press - sink and focus ring are defined once; a new variant MUST NOT redefine them. - Variants are made by reassigning private `--_*` properties. -2. **Hover is guarded.** Hover styles live in `@media (hover: hover)`. Touch - devices never see a stuck hover. -3. **Pressed means pressed.** Raised controls sink on press. A control that does - not respond to a press does not look pressable. -4. **Targets are at least 44px**, including controls drawn smaller (the system - extends the pointer target). -5. **Every state has a visible form:** default, hover, focus-visible, active, - selected, disabled, invalid, loading. A component missing one is unfinished. -6. **Disabled is not hidden.** A disabled control keeps its place, dims, and - uses `not-allowed`. Prefer explaining *why* over disabling silently. -7. **Selection state lives in attributes with a platform meaning:** - `aria-selected`, `aria-current`, `aria-pressed`, `aria-checked`, - `aria-invalid`, `disabled`, `data-outside`. Not in ad-hoc classes. - ---- - -## 11. Accessibility - -Accessibility is a precondition, not a feature. Target **WCAG 2.2 level AA**. -Niveus provides the base; the author is responsible for the rest. - -**Provided by the system:** - -- One focus treatment: a contrast ring under an accent ring, visible on any - surface including accent-filled controls. -- A minimum 44px pointer target. -- `prefers-reduced-motion` and `prefers-reduced-transparency` honoured at the - token level. -- Filled colours with a derived on-colour that clears 4.5:1. - -**Required of the author:** - -1. **Icon-only buttons MUST have `aria-label`.** The system cannot name them. -2. **Use native elements** — ` - -
- - - -
- - A popup list is a menu whose items are options: the same frosted surface, - anchoring and light dismiss, with a selection that stays. A picker is its - trigger: a field-shaped button that shows the current choice and a chevron - that turns over while the list is open. - - niveus.js adds what a native . - ========================================================================== */ - - .nv-timepicker { - --_row: 2.25rem; - --_rows: 5; - - display: inline-flex; - flex-direction: column; - align-items: center; - gap: var(--nv-space-2); - padding: var(--nv-space-2) var(--nv-space-3); - border-radius: var(--nv-radius-xl); - color: var(--nv-color-text-primary); - } - - .nv-timepicker__wheels { - position: relative; - display: flex; - gap: var(--nv-space-1); - } - - /* The lens: a bar of tinted glass lying across the centre row. It is raised - like the keys elsewhere in the system — lit on top, shaded below, with a - contact line — not a flat band of colour, so it reads as a thing the wheels - pass under. */ - .nv-timepicker__wheels::before { - content: ""; - position: absolute; - inset-inline: calc(var(--nv-space-2) * -1); - inset-block-start: 50%; - block-size: var(--_row); - translate: 0 -50%; - background-color: var(--nv-color-selection); - background-image: var(--nv-sheen-raised); - border-radius: var(--nv-radius-md); - box-shadow: var(--nv-bevel), var(--nv-outline), var(--nv-elevation-1); - pointer-events: none; - } - - .nv-timepicker__column { - position: relative; - inline-size: 3.5rem; - block-size: calc(var(--_row) * var(--_rows)); - margin: 0; - padding: 0; - list-style: none; - cursor: grab; - user-select: none; - /* The wheel owns vertical gestures, whatever the pointer: niveus.js drags it - itself so a finger, a mouse and a desktop browser's phone mode behave - alike. Without the script the wheel still scrolls natively with a wheel - or keys; set touch-action back to pan-y if you ship it without. */ - touch-action: none; - overflow-y: auto; - overscroll-behavior: contain; - scroll-snap-type: y mandatory; - scrollbar-width: none; - mask-image: linear-gradient( - transparent, - currentcolor 28%, - currentcolor 72%, - transparent - ); - /* Depth for the drum effect below: rows turn away from the viewer as they - leave the centre. */ - perspective: 14rem; - } - - .nv-timepicker__column::-webkit-scrollbar { - display: none; - } - - /* Spacers, not padding: they let the first and last rows reach the centre. - Firefox leaves the bottom padding of a scroll container out of its - scrollable area, which stranded the last rows (and made a three-row wheel - unscrollable); real boxes always count. */ - .nv-timepicker__column::before, - .nv-timepicker__column::after { - content: ""; - display: block; - block-size: calc(var(--_row) * (var(--_rows) - 1) / 2); - } - - .nv-timepicker__column[data-nv-dragging] { - cursor: grabbing; - } - - .nv-timepicker__column:focus-visible { - outline: none; - border-radius: var(--nv-radius-md); - box-shadow: var(--nv-focus-ring); - } - - .nv-timepicker__option { - display: grid; - place-items: center; - block-size: var(--_row); - scroll-snap-align: center; - color: var(--nv-color-text-tertiary); - font: var(--nv-font-label); - font-size: var(--nv-font-size-lg); - font-variant-numeric: tabular-nums; - cursor: pointer; - /* --nv-pull is how far the wheel has been dragged past its end. Letting go - removes it, and the spring easing carries the rows back with a small - overshoot — the way a phone's list gives at its limit and settles. */ - translate: 0 var(--nv-pull, 0); - transition: - var(--nv-transition-colors), - translate var(--nv-duration-slower) var(--nv-ease-spring); - } - - .nv-timepicker__column[data-nv-pulling] .nv-timepicker__option { - transition: var(--nv-transition-colors); - } - - /* A drum: each row shrinks, fades and tilts away in proportion to how far it - is from the lens. Driven by the scroll position itself, so it needs no - script and tracks a finger exactly. Where scroll-driven animation is missing - the rows are simply flat, which is still correct. */ - @supports (animation-timeline: view()) { - @media (prefers-reduced-motion: no-preference) { - .nv-timepicker__option { - animation: nv-wheel-drum linear both; - animation-timeline: view(block); - } - } - } - - @keyframes nv-wheel-drum { - 0% { scale: 0.78; opacity: 0.25; rotate: x 52deg; } - 50% { scale: 1; opacity: 1; rotate: x 0deg; } - 100% { scale: 0.78; opacity: 0.25; rotate: x -52deg; } - } - - .nv-timepicker__option[aria-selected="true"] { - color: var(--nv-color-text-primary); - font-weight: var(--nv-font-weight-semibold); - } - - .nv-timepicker__separator { - display: grid; - place-items: center; - align-self: center; - color: var(--nv-color-text-tertiary); - font: var(--nv-font-label); - font-size: var(--nv-font-size-lg); - } -} @layer niveus.components { /* ========================================================================== Niveus — Disclosure @@ -4501,19 +3799,6 @@ --nv-button-offset: 0 -50%; } - /* The arrows float over the cards, so they are glass: frosted, with the content - passing blurred beneath them, rather than an opaque key hiding what it sits - on. This is the system's rule for anything that floats over content. */ - .nv-scroller-frame [data-nv-scroll] { - --_bg: var(--nv-material-frosted-bg); - --_bg-hover: var(--nv-material-frosted-bg); - --_bg-active: var(--nv-material-frosted-bg); - --_border: var(--nv-material-frosted-border); - --_sheen: none; - backdrop-filter: var(--nv-backdrop-frosted); - -webkit-backdrop-filter: var(--nv-backdrop-frosted); - } - /* Belt and braces: the frame knows these controls float, so their centring survives even if the markup forgets --float. */ .nv-scroller-frame [data-nv-scroll], diff --git a/examples/ai-chat/index.html b/examples/ai-chat/index.html index e5e7ca6..fe59588 100644 --- a/examples/ai-chat/index.html +++ b/examples/ai-chat/index.html @@ -404,7 +404,7 @@ .forEach((el) => el.removeAttribute("aria-current")); item.setAttribute("aria-current", "page"); render(item.dataset.thread); - }, { scope: list, content: transcript }); + }, { scope: list }); scroller.scrollTop = 0; }); }); diff --git a/examples/it-shop/index.html b/examples/it-shop/index.html index 77710c8..f3b9c2b 100644 --- a/examples/it-shop/index.html +++ b/examples/it-shop/index.html @@ -491,13 +491,9 @@ const count = document.getElementById("basket-count"); document.querySelectorAll("[data-add]").forEach((button) => { button.addEventListener("click", () => { - count.textContent = String(Number(count.textContent) + 1); - if (!nv.prefersReducedMotion()) { - count.animate([{ scale: 1.4 }, { scale: 1 }], { - duration: 280, - easing: getComputedStyle(document.documentElement).getPropertyValue("--nv-ease-spring"), - }); - } + nv.transition(() => { + count.textContent = String(Number(count.textContent) + 1); + }); button.textContent = "Added"; setTimeout(() => { button.textContent = "Add to basket"; }, 1200); }); diff --git a/index.html b/index.html index 7d681f2..3e6e30c 100644 --- a/index.html +++ b/index.html @@ -67,72 +67,6 @@ place-items: center; } - /* Glaze specimen: color behind the pane, so the frost has something to - act on. The orbs and the line of type are only there to be blurred. */ - .glaze-demo { - position: relative; - margin-block-start: var(--nv-space-6); - min-block-size: 22rem; - display: grid; - place-items: center; - overflow: hidden; - } - - .glaze-demo__scene { - position: absolute; - inset: 0; - pointer-events: none; - } - - .glaze-demo__orb { - position: absolute; - border-radius: var(--nv-radius-full); - inline-size: var(--_size); - block-size: var(--_size); - inset-inline-start: var(--_x); - inset-block-start: var(--_y); - background: var(--_fill); - } - - .glaze-demo__type { - position: absolute; - inset-inline: 0; - inset-block-end: var(--nv-space-4); - text-align: center; - font: var(--nv-font-display); - font-size: clamp(2.5rem, 9vw, 5.5rem); - font-weight: 700; - color: var(--nv-color-text-primary); - opacity: 0.85; - white-space: nowrap; - } - - .glaze-demo__panel { - position: relative; - inline-size: min(26rem, 100%); - padding: var(--nv-space-6); - border-radius: var(--nv-radius-xl); - display: flex; - flex-direction: column; - gap: var(--nv-space-4); - } - - .picker-demo { - display: flex; - flex-wrap: wrap; - align-items: flex-start; - justify-content: center; - gap: var(--nv-space-6); - margin-block-start: var(--nv-space-6); - } - - .picker-demo__column { - display: flex; - flex-direction: column; - gap: var(--nv-space-4); - inline-size: min(16rem, 100%); - } - .specimen__name { font: var(--nv-font-label); } @@ -363,84 +297,6 @@ - -
- - -
-
-

Glaze

-

- One dial controls how frosted this pane is. The rim brightens on - the side facing your pointer. -

-
-
- - - Clear glass on the left, heavy frost on the right. -
-
- - -
-
-
- - - -
-
-

Pickers

-

- A calendar, wheels for time, and a popup list. They hold content, so - they sit on solid surfaces; here the first two float on Glaze - over a bed, which is where a date field's popover would be. -

-
- -
-
-
- -

- -
-
-
- -
-
- 09:30 - -
- - -
- - - - - - - -
-
-
-
@@ -768,7 +624,7 @@ Weekly digest