commit 23b8dd376db15ebb5e9fd51a4019dab3e280dee1 Author: Nexoniarz Date: Sat Oct 3 17:47:03 2026 +0200 Niveus: a tactile, adaptive interface language Bevelled controls with light falling across them, fields as wells, solid windows, and glass kept for the chrome that floats over everything else. Warm graphite in dark, soft paper in light, one brand color driving both. Ships its own fonts (Instrument Sans, JetBrains Mono; OFL). Co-Authored-By: Claude Opus 5.5 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c18dd8d --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +__pycache__/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..b34d044 --- /dev/null +++ b/LICENSE @@ -0,0 +1,201 @@ + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the + purposes of this License, Derivative Works shall not include works + that remain separable from, or merely link (or bind by name) to the + interfaces of, the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including the + original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing + the origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/README.md b/README.md new file mode 100644 index 0000000..2308620 --- /dev/null +++ b/README.md @@ -0,0 +1,465 @@ +# Niveus + +A tactile, adaptive interface language. Controls are bevelled keys with light +falling across them, fields are wells cut into the surface, windows are solid, +and glass is kept for the chrome that floats over everything else. Warm +graphite in dark, soft paper in light, one brand color driving both. + +Niveus is a **design-system layer**, not a component library bolted onto an +application. Applications consume it; they do not redefine it. The visual rules +live here and can change without touching a line of application logic. + +```html + +``` + +Niveus ships its own faces in `fonts/`: Instrument Sans for the interface and +JetBrains Mono for code and engraved labels, both under the SIL Open Font +License. Copy `fonts/` alongside `dist/` (or `niveus/`) and they load by +themselves. + +```html + +``` + +Open `index.html` for the full specimen: materials, the elevation scale, every +semantic color, the type scale, and each component in both appearances. +`examples/` holds four complete products built with it. + +**[REFERENCE.md](REFERENCE.md) is the complete API** — every class, token, +attribute and function, each listed beside the name the rest of the industry +uses for it. It is written to be read by a person *or* handed to a model on its +own: an assistant that has only that file can write correct Niveus markup +without seeing a line of the CSS. `tools/check.py` fails if a class exists in +the CSS and is missing from it. + +--- + +## Using it + +Reference the system, never restate it: + +```css +/* No */ +.thing { background: #ffffff; border-radius: 12px; padding: 10px 16px; } + +/* Yes */ +.thing { + background: var(--nv-color-surface-elevated); + border-radius: var(--nv-radius-md); + padding: var(--nv-space-2-5) var(--nv-space-4); + box-shadow: var(--nv-highlight-top), var(--nv-elevation-2); +} +``` + +Application-specific values are fine when they describe *your* content and +layout — a sidebar that happens to be 15rem wide, an aspect ratio, a grid +template. They are not fine when they restate the design language. + +Everything ships inside the `niveus` cascade layer, so unlayered application +styles always win. Overriding a Niveus component never requires `!important` +or a longer selector. + +## Structure + +``` +niveus/ +├── tokens/ the values, appearance-agnostic +│ ├── color.css palette + semantic roles +│ ├── typography.css families, scale, weights, composite roles +│ ├── spacing.css 4px rhythm + control sizing +│ ├── radius.css corner scale +│ ├── elevation.css the 0–5 depth scale, inset, highlight, focus ring +│ ├── motion.css durations, easings, displacement +│ └── materials.css blur, backdrop recipes, material definitions +│ +├── themes/ what those values become in each appearance +│ ├── light.css +│ └── dark.css +│ +├── base.css elements, before any component +├── continuity.css how one state becomes the next +├── components/ surface, button, input, card, dialog, navigation, +│ menu, disclosure, scroller, bed, badge, table +├── utilities.css the short list +├── niveus.css the entry point +└── niveus.js optional — continuity for changes CSS cannot animate + +examples/ four complete interfaces built from the system +├── ai-landing/ marketing page +├── ai-chat/ chat web app +├── it-shop/ hardware shop +└── dashboard/ monitoring dashboard +``` + +`niveus/niveus.css` is the authored entry point and works directly over HTTP. +`dist/niveus.css` is the same thing with the `@import` chain flattened into one +request — use it for anything shipped. + +## Theming + +A component never names a color. It names a **role**, and the appearance decides +what that role is made of: + +`background` · `surface` · `surface-elevated` · `surface-inset` · +`text-primary` · `text-secondary` · `text-tertiary` · `border` · `accent` · +`success` · `warning` · `danger` + +Each role resolves through `light-dark()`, pulling from `--nv-light-*` in +`themes/light.css` or `--nv-dark-*` in `themes/dark.css`. The appearance is +selected by `color-scheme`, which the theme files bind to `[data-theme]`: + +| `data-theme` | Result | +| ------------ | ----------------------------- | +| *(absent)* | follows the operating system | +| `light` | light, always | +| `dark` | dark, always | + +Dark is not light inverted. Surfaces get *lighter* as they rise, shadows deepen +rather than disappear, the accent moves up its ramp to stay legible, and the top +highlight thins to a faint rim. + +## Materials and depth + +**Material** answers what light does when it hits a surface. **Elevation** +answers how far that surface is from the canvas. They are independent. + +| Material | Use for | +| ---------- | ---------------------------------------------- | +| `solid` | content. The default. | +| `elevated` | surfaces that must be read before their context | +| `inset` | fields, wells, tracks — anything recessed | +| `frosted` | chrome that content passes under: bars, toolbars | +| `glass` | contextual layers where content behind must stay legible | + +| Elevation | Relationship | +| --------- | ------------------------------- | +| 0 | flush — painted on the canvas | +| 1 | resting — cards, rows, panels | +| 2 | raised — anything that reacts | +| 3 | floating — popovers, menus | +| 4 | lifted — dialogs, sheets | +| 5 | overlay — topmost, used sparingly | + +Applied as classes: `.nv-material-frosted`, `.nv-elevation-3`. A shadow that is +on neither scale is a shadow that means nothing. + +## Continuity + +Every interaction should read as a continuation of the state before it. When +something moves, grows, collapses or changes appearance, the change itself +should be perceptible — where it came from, where it went, and why. The goal is +not to animate everything. It is that nothing pops into a new state the user +could have watched it reach. + +| Change | How it stays continuous | +| ------ | ----------------------- | +| Selection moves between items | The marker itself travels. It is a pseudo-element with a shared `view-transition-name`, so the browser interpolates its old box into its new one. | +| Switching tabs | Panels cross-fade inside one box and slide with the direction of travel — forward one way, back the other. | +| Content expanding | `interpolate-size` lets `block-size: auto` animate, so content grows from the row that revealed it. Nothing is measured. | +| Menus and dialogs | A menu is anchored to its trigger and scales out of its edge; a dialog expands away from the control that opened it. | +| Theme change | The whole surface cross-fades. Surfaces also transition their own colors, so it glides even with no script. | +| Hover, press, focus, select | All have a duration. Marks grow from their centre, the switch thumb travels its track. | + +Most of this is plain CSS. The three cases CSS cannot do alone — a selection +moving *between two elements*, one panel replacing another, and the whole page +changing appearance — need a view transition, which needs one line of script: + +```js +nv.transition(() => { // or document.startViewTransition directly + previous.removeAttribute("aria-current"); + next.setAttribute("aria-current", "page"); +}); +``` + +`niveus.js` is optional and dependency-free. Without it every component still +works; changes simply arrive rather than travel. It also wires +`[data-nv-theme]`, `[data-nv-dialog]`, `[data-nv-tabs]` and `[data-nv-close]` +so the common cases need no code at all. + +### One rule worth knowing + +A `view-transition-name` must be unique among all rendered elements, or the +browser skips the transition entirely. Each selection group therefore needs its +own marker name: + +```html + + +``` + +Tab strips default to `nv-tabs-selection` and everything else to +`nv-selection`, which is enough for one of each per page. + +### Motion under a transition + +Transition layers always paint above the page, so a travelling marker cannot +stay behind the labels it passes the way it does at rest. Rather than let an +opaque marker wipe them, Niveus thins it while it is in flight: it stays +visible the whole way — it can still be followed — but reads as having lifted +off the surface to travel. + +`prefers-reduced-motion` collapses every duration at the token level and skips +view transitions entirely. The change still happens; it just arrives. + +## Color + +The whole color system has one input: + +```css +:root { --nv-brand: #d92e3f; } +``` + +From it the system derives the accent and its hover and press states, the text +that lands on it, the link color, the focus ring, the selection highlight, the +tint in the greys, and how far the status colors lean — for both appearances, +with nothing else to set. No component is involved: they all name roles, and +the roles re-derive. + +Three things make a single input actually work rather than just recolor a +button: + +**The label is derived, not chosen.** Every filled color computes its own +foreground by reading its lightness: below a threshold it resolves to +near-white, above it to near-black. A yellow brand gets dark labels and a navy +one light labels, and no one has to remember to update a second token. + +**The color is moved until its label is readable.** A mid-tone is the one place +where neither white nor black reaches 4.5:1, so a brand that lands there is +darkened until white works — or, if already light, lightened until black does. +Hue and chroma are untouched; only lightness moves, and only as far as it must. +This is why a mid-tone green arrives slightly deeper than the swatch you set. + +**The rest of the palette follows.** Status colors lean towards the brand by +`--nv-brand-harmony` (12%) — red still reads as danger, it just stops looking +borrowed from another system — and the greys carry `--nv-brand-tint` (4%) of +it, which is what stops an accent from looking stuck on. Set either to `0%` for +literal status colors and strictly neutral greys. + +| Input | Default | What it does | +| ----- | ------- | ------------ | +| `--nv-brand` | `#4f6cff` | the one color everything derives from | +| `--nv-brand-tint` | `4%` | how much brand bleeds into the greys | +| `--nv-brand-harmony` | `12%` | how far status colors lean towards it | +| `--nv-contrast-pivot` | `0.7` | where "too mid-toned to carry a label" begins | +| `--nv-contrast-dark` / `-light` | `0.53` / `0.78` | how far either side a color is pushed | + +### Branding a subtree + +`data-nv-brand` re-derives the whole set for one region, so a section can carry +its own color without a second theme: + +```html +
+ +
+``` + +The attribute is required, not decoration: `var()` inside a custom property is +substituted where that property is *declared*, so a `--nv-brand` set on a bare +element would never reach the roles. `[data-nv-brand]` is the selector that +re-runs the derivation. + +### Overriding a role outright + +Deriving is the default, not a requirement. Any role can be set directly, for +the whole page or for a subtree: + +```css +.danger-zone { --nv-color-accent: var(--nv-color-danger); } +``` + +Everything inside follows — buttons, focus rings, links, switches — because +none of them ever named a color. + +## Examples + +`examples/` holds four complete interfaces. Open `examples/index.html`, or go +straight to one: + +| | | | +| --- | --- | --- | +| **Lumen** | `examples/ai-landing/` | AI product landing page — hero, features, a scrolling row, pricing, FAQ | +| **Lumen Chat** | `examples/ai-chat/` | chat web app — sidebar, transcript, composer, settings dialog with tabs | +| **Northwire** | `examples/it-shop/` | hardware shop — filters, product grid, stock states, basket | +| **Northwire Ops** | `examples/dashboard/` | monitoring dashboard — stat tiles, a chart, a service table, incidents | + +They look like four different products. None of them overrides a component, +forks a token, or writes a color. What differs between them is one declaration: + +```css +:root { --nv-brand: #ea580c; } /* the shop */ +``` + +Each links exactly two files from the system — `dist/niveus.css` and the +optional `niveus/niveus.js` — and then its own `app.css`, which contains grids, +widths and breakpoints and **not a single hex value**. `tools/check.py` enforces +that: a color anywhere in an example, other than `--nv-brand` itself, fails the +build. An example that quietly reaches for a hex value is no longer +demonstrating the system; it is demonstrating how to work around one. + +Where an example needed something the system did not have, the answer was to +add it to the system: `.nv-badge` and `.nv-table` exist because these pages +asked for them. + +## Philosophy + +Niveus is not a folder of CSS. It is a set of decisions that have already been +made, so that building an interface stops being a series of small aesthetic +arguments. + +**Niveus is not about making interfaces look physical. It is about making +digital surfaces feel understandable.** Depth, material and motion are here to +answer questions the user is already asking — what is this, is it above or +below, where did it come from, did my click register — and for nothing else. An +effect that answers no question is decoration, and decoration is what makes an +interface tiring. + +### The principles, and what each one costs + +| | | +| --- | --- | +| **Clarity** | Every element has a purpose. Decoration never competes with content. | +| **Soft physicality** | Depth comes from shadow, highlight and inset — not from ornament. | +| **Material** | Surfaces are made of something. Solid, elevated, inset, frosted, glass. | +| **Restraint** | No effect exists because it is possible. Translucency and shadow are spent, not sprinkled. | +| **Consistency** | The same control behaves the same way everywhere, including under a pointer. | +| **Adaptability** | Nothing is defined in terms of an appearance or a color. | +| **Continuity** | Nothing pops into a new state the user could have watched it reach. | + +They constrain each other on purpose. Continuity says to animate the change; +restraint says most changes do not need it. Material says to use translucency; +restraint says only for layers that pass over content. When two pull in +different directions, restraint wins — a quiet interface with one thing moving +reads clearly, and a busy one does not. + +### Where the line runs + +Niveus decides **how the application looks and behaves**. The application +decides **what it does**. In practice: + +- A color, a shadow, a radius, a duration, a control height → the system. +- A sidebar that happens to be 15rem wide, an aspect ratio, a grid template → + the application. +- A value that is *about* your content is yours. A value that restates the + design language is the system's, and hard-coding it is how a system quietly + stops being one. + +### Adding to the system + +Three questions, in order: + +1. **Can it be expressed with what already exists?** Most new requirements are + an existing component with a modifier, or a pattern — a composition of + components that needs no new CSS at all. The patterns on the specimen page + introduce nothing. +2. **Is it a rule, or is it this once?** A component earns its place when it + can be stated as a reusable principle. "Selection emphasis travels" is a + rule. "This menu is 14px from its button" is not. +3. **Does it hold in both appearances, at every elevation, and under a + pointer?** If it only works on white, or only when nothing is behind it, it + is not finished. + +A new token follows the same test. If a value appears three times and means the +same thing each time, it is a token. If it appears three times and means +something different each time, three different things were given the same +number by accident. + +### Working on it + +Every rule in this repo is checked rather than trusted: + +- `tools/check.py` fails on a role defined for one appearance but not the + other, a token referenced but never defined, any hard-coded color in a + component *or an example*, and any class that exists in the CSS but is + missing from `REFERENCE.md`. +- Contrast is measured, not assumed — the derived label on every filled color + clears 4.5:1 for any brand. +- `tools/build.py --check` fails if `dist/` has drifted from the sources. + +If a change cannot be made without breaking one of those, the change is +probably not the problem the system has. + +## Components + +| Class | Notes | +| -------------- | --------------------------------------------------------- | +| `.nv-button` | `--primary` `--subtle` `--ghost` `--danger` `--frosted` `--float`, `--sm`/`--lg`, `--icon`, `--pill`, `--block`, `.nv-button-group` | +| `.nv-input` | with `.nv-field`, `.nv-input-group`, `.nv-select`, `.nv-checkbox`, `.nv-radio`, `.nv-switch`, `.nv-choice` | +| `.nv-card` | `--elevated` `--floating` `--inset` `--frosted` `--interactive`, `.nv-card-grid` | +| `.nv-dialog` | native ``; `--sheet` `--compact` `--wide` | +| `.nv-nav` | `--vertical` `--plain` `--inline`, plus `.nv-nav-tabs`, `.nv-tab-panel` and `.nv-breadcrumbs` | +| `.nv-menu` | anchored popover with `.nv-menu-trigger`, `__item`, `__separator`, `__label`, `__shortcut` | +| `.nv-disclosure` | native `
`; `.nv-disclosure-group`, plus `.nv-collapse` for other markup | +| `.nv-scroller` | snapping row inside `.nv-scroller-frame`, with edge fades and optional controls | +| `.nv-bed` | ground that is visibly not ordinary background; `--quiet` `--vivid` `--single` `--flush` | +| `.nv-badge` | state, not an action; `--accent` `--success` `--warning` `--danger`, `--solid` `--count` `--square` | +| `.nv-table` | inside `.nv-table-frame`; `__number` `__actions`, `--compact` `--interactive` | +| `.nv-surface` | materials and elevation as utilities | + +Variants are defined by reassigning private `--_*` properties. Interaction — +the lift on hover, the sink on press, the focus ring — is written once per +component, so a new variant cannot drift from how the rest of the system +responds to a pointer. + +## Accessibility + +- One focus treatment system-wide: a contrast ring under an accent ring, so it + stays visible on any surface including accent-filled controls. +- Controls shorter than 44px still get a 44px pointer target. +- `prefers-reduced-motion` collapses duration and displacement to zero at the + token level — no component opts in individually. +- `prefers-reduced-transparency`, and browsers without `backdrop-filter`, fall + back from frosted and glass to their solid equivalents. + +## Tools + +```bash +python3 tools/build.py # flatten the @import chain into dist/niveus.css +python3 tools/build.py --check # fail if dist/ is stale (for CI) +python3 tools/check.py # enforce the system's own rules +``` + +`tools/niveus_css.py` is not a command: it is an evaluator for the subset of +CSS the token layer is written in (`oklch(from …)`, `color-mix()`, +`light-dark()`, `calc()`), with CSS Color 4 gamut mapping, plus `read_rules()` +for the component layer (which role a button's background is in each state). +Every port that cannot read CSS — Android, Qt — imports it from here to +generate its tokens and control recipes, so no platform restates a value by +hand. + +`check.py` catches the failures that do not look broken: a role defined for one +appearance but not the other, a token referenced but never defined, and a +hard-coded color in a component. + +## Browser support + +Niveus uses `light-dark()`, cascade layers, `:has()`, `@starting-style` and +`allow-discrete` transitions — all baseline in current Chrome, Safari, Firefox +and Edge. `backdrop-filter` degrades to a solid surface where it is missing. + +The continuity layer builds on newer platform features, each of which degrades +to the state change simply happening rather than travelling: + +| Feature | Used for | Without it | +| ------- | -------- | ---------- | +| View transitions | travelling selection, tab panels, theme cross-fade | the change applies instantly | +| `interpolate-size` | disclosure height | content appears at full height (`.nv-collapse` still animates) | +| Anchor positioning | menus tied to their trigger | the menu centres itself | +| `popover` | menus, light dismiss | needs the application's own toggle | + +--- + +> Niveus borrows from physical things only what makes a surface easier to +> understand: a key looks pressable, a well looks like a place to put +> something, and glass means something is floating. + +--- + +## License + +Niveus is licensed under the [Apache License 2.0](LICENSE). The fonts in +`fonts/` keep their own license, the SIL Open Font License 1.1 +(`fonts/OFL-*.txt`), which allows bundling them with anything. + +Made by **Nexoniarz**. diff --git a/REFERENCE.md b/REFERENCE.md new file mode 100644 index 0000000..de91346 --- /dev/null +++ b/REFERENCE.md @@ -0,0 +1,430 @@ +# Niveus — reference + +Every class, token, attribute and function the system exposes, with the name +the rest of the industry uses for it. + +Two audiences: + +- **People.** Find the thing you mean by its usual name, then use the class + beside it. +- **Models.** This file is the whole API. Hand it over on its own and an + assistant can build correct Niveus markup without seeing the CSS: it does not + need to know *how* `.nv-nav-tabs` works, only that a tab list is spelled that + way here. Nothing below is aspirational — `tools/check.py` fails if a class + exists in the CSS and is missing from this file. + +Everything ships inside the `niveus` cascade layer, so unlayered application +styles win without `!important`. + +```html + + + + +``` + +--- + +## Button + +| Class | Known as | Does | +| --- | --- | --- | +| `.nv-button` | button, secondary button | Raised surface. The default; use it for anything that is not *the* action. | +| `.nv-button--primary` | filled / CTA button | The single affirmative action in a view. | +| `.nv-button--subtle` | tonal button | Accent-tinted fill, no elevation. Mid-emphasis. | +| `.nv-button--ghost` | text / quiet button | No material until hovered. Lowest emphasis. | +| `.nv-button--danger` | destructive button | Irreversible actions only. | +| `.nv-button--frosted` | glass button | For use over imagery or frosted chrome. | +| `.nv-button--float` | FAB, overlay control | Floats over content: grows on hover instead of lifting, and keeps its own positioning via `--nv-button-offset`. | +| `.nv-button--sm` `.nv-button--lg` | size variants | 28px and 44px; default is 36px. | +| `.nv-button--icon` | icon button | Square. Requires `aria-label`. | +| `.nv-button--pill` | pill button | Fully rounded. | +| `.nv-button--block` | full-width button | Fills its container. | +| `.nv-button__icon` | button icon slot | Sizes an inline SVG to the label. | +| `.nv-button-group` | segmented control, button group | Buttons sharing edges; only outer corners round. | + +States come from the element: `disabled`, `aria-disabled`, `aria-busy="true"` +(shows a spinner and keeps the width), `aria-pressed` for toggles. + +## Form controls + +| Class | Known as | Does | +| --- | --- | --- | +| `.nv-field` | form field, form group | Label + control + hint, correctly spaced. | +| `.nv-field__label` | field label | Add `data-optional="(optional)"` to append a muted suffix. | +| `.nv-field__hint` | helper text | Guidance below the control. | +| `.nv-field__error` | validation message | Pair with `aria-invalid="true"` + `aria-describedby`. | +| `.nv-input` | text field, form control | `input`, `textarea` and `select`. Inset material. | +| `.nv-input--sm` `.nv-input--lg` | size variants | Match the button sizes. | +| `.nv-input--color` | color well / swatch input | `input[type=color]` as a square swatch. | +| `.nv-input-group` | input with adornment | Wrapper for a field with a prefix or suffix. | +| `.nv-input-group__affix` | prefix / suffix adornment | Sits inside the field's recess. First child = leading, last = trailing. | +| `.nv-select` | select, dropdown | Wrapper that draws the chevron around a native `select.nv-input`. | +| `.nv-checkbox` | checkbox | Native input, custom mark. Supports `:indeterminate`. | +| `.nv-radio` | radio button | Native input, custom mark. | +| `.nv-switch` | toggle switch | Inset track, elevated thumb that travels. | +| `.nv-choice` | control with inline label | `