# nitaimaarek UI — complete reference Shared UI framework for every `*.nitaimaarek.com` site: frosted-glass components and a cursor-reactive canvas background. Assets: `/ui.css`, `/ui.js`, `/ui-bg.js` — all on `https://framework.nitaimaarek.com`. No build step, no dependencies, no web fonts. Rendered version of this file: `/`. ## Install ```html ``` `ui-bg.js` is fetched automatically the first time a background is requested; add `` yourself to avoid that second round trip. Put `ui.js` in `` without `defer` to avoid a theme flash. It is optional: every component degrades to working HTML without it. ## Rules 1. Bare HTML is already styled — add the `` and change nothing else. 2. Use `.ui-*` classes only for widgets plain elements don't cover. 3. Never write per-site CSS or a second palette; fix `ui.css` instead. 4. Restyle via tokens on ``, e.g. `style="--ui-hue:30"`. 5. Behaviour attaches through `data-ui-*` attributes, never through classes. ## Tokens Set on `:root` / ``. Everything else derives from them. | Token | Default | Drives | |---|---|---| | `--ui-hue` | 265 | Accent: links, focus, checked controls, background particles | | `--ui-sat` | 70% | Accent saturation | | `--ui-tint` | 240 | Neutral hue — surfaces, borders, text (240 cool ink, 40 warm) | | `--ui-tint-sat` | 6% | Grey lean; `0%` = pure neutral | | `--ui-radius` | 14px | **Every** corner in the framework. `-xs` ×.35, `-sm` ×.65, `-lg` ×1.35 derive from it, so `--ui-radius:0` squares the whole UI and a large value rounds all of it | | `--ui-density` | 1 | Multiplies the spacing scale `--ui-1`…`--ui-8` (4…64px) | | `--ui-fs` / `--ui-lh` | 15px / 1.6 | Root type size, line height | | `--ui-width` / `--ui-measure` | 1120px / 72ch | Content column; prose line length | | `--ui-font` / `--ui-mono` | system stacks | Typefaces | | `--ui-display` | `var(--ui-font)` | Face for h1–h4 and `.ui-display`; set to `var(--ui-mono)` for mono headings | | `--ui-solid` / `--ui-solid-ink` | ink / page | Filled surfaces — primary button, solid badge | | `--ui-blur` | 20px | Frosted-glass strength on panels; `0` makes them flat | | `--ui-lift` | -2px | How far buttons and cards rise on hover | | `--ui-glass` / `--ui-glass-2` | derived | Panel fills, translucent so the background reads through | | `--ui-glass-line` / `--ui-glass-hi` | derived | Hairline border and inset top highlight | | `--ui-bg` `--ui-bg-alt` `--ui-surface` `--ui-surface-2` | derived | Backgrounds, back to front | | `--ui-line` / `--ui-line-soft` | derived | Borders: visible / hairline | | `--ui-text` / `--ui-muted` / `--ui-faint` | derived | Text emphasis levels | | `--ui-ok` `--ui-warn` `--ui-bad` `--ui-info` | — | Status colours (+ `-wash` backgrounds) | | `--ui-fast` `--ui-mid` `--ui-slow` | 150/240/420ms | Motion (off under `prefers-reduced-motion`) | | `--ui-shadow-1..3` / `--ui-ring` | — | Elevation, focus ring | ```html ``` Dark is the default palette; light comes from `prefers-color-scheme` or `data-theme="light"`. `ui.js` persists theme and hue in a cookie scoped to `.nitaimaarek.com`, so the choice follows the user across all subdomains. ## Layer 1 — classless base Headings use `--ui-display` (the sans face) at heavy weights with tight tracking; `h5`/`h6` render as small uppercase mono eyebrows. Styled with no markup changes: `h1`–`h6`, `p`, `a`, `strong`, `em`, `small`, `mark`, `abbr`, `ul`, `ol`, `dl`, `hr`, `blockquote`, `code`, `pre`, `kbd`, `img`, `figure`, `table`/`thead`/ `tbody`/`th`/`td`/`caption`, `form`, `fieldset`, `legend`, `label`, `input` (text/search/email/url/password/number/tel/date/time/color/file/checkbox/radio/ range), `textarea`, `select`, `option`, `button`, `progress`, `details`, `summary`, `dialog`, scrollbars, `::selection`, `:focus-visible`. Body children are centred at `--ui-width`; add `class="ui-wide"` to `` to remove that cap. Gotcha: because direct children of `` get `margin-inline: auto`, a direct child you give a *narrower* `max-width` will centre itself instead of lining up with the headings. Add `margin-inline: 0` to it, or nest it inside a wrapper. ## Layer 2 — components ### Glass `.ui-glass` applies the frosted-panel recipe (translucent fill, hairline border, `backdrop-filter`) to any element. `.ui-card`, `.ui-menu`, `.ui-modal__box` and `.ui-toast` already use it. Browsers without `backdrop-filter` fall back to solid surfaces automatically. ### Display type `.ui-display` is the oversized hero face (`clamp(3rem, 1rem + 9vw, 8.5rem)`, weight 900, `-.06em` tracking). `.ui-dim` greys part of it. ```html

nitaimaarek/ui

``` ### Layout ```html
``` ### Buttons — `.ui-btn` Works on ` ``` ### Segmented — `.ui-segmented` ```html ``` ### Fields — `.ui-field` ```html
Host and port.
Not valid.
``` ### Toggles ```html ``` `input.indeterminate = true` renders a dash. ### Slider — `.ui-slider` `ui.js` paints the filled track (`--ui-fill`) and writes `.ui-slider__value`. ```html
6
116
``` Without the wrapper, an `` also receives the value. ### Searchable select — `data-ui-select` Upgrades a real ` ``` ### Dropdown — `.ui-dropdown` + `.ui-menu` Closes on outside click or Esc; flips to `.ui-menu--right` at the viewport edge. ```html
File
Open
``` ### Tabs — `data-ui-tabs` ```html
``` Arrow keys move between tabs; fires a bubbling `ui:tab` event. ### Card — `.ui-card` Modifiers: `--flat` (recessed) `--hover` (highlight on hover) `--accent` (left rule). ```html

media-studio

Downloader, port 8092.

Clickable card ``` ### Stat — `.ui-stat` ```html
18services
``` ### Badge / dot — `.ui-badge` `.ui-dot` Variants: `--accent --ok --warn --bad --info --solid --pill`. ```html running ``` ### Chip — `.ui-chip` `.ui-chip__x` removes the chip on click. `--pill` for a rounded one. ```html rust ``` ### Avatar — `.ui-avatar` `--round` `--lg`. Works on a `` with initials or an ``. ### Alert — `.ui-alert` Inline and persistent (use a toast for transient feedback). Variants: `--ok --warn --bad --info`. ```html
Deployed. All checks passed.
``` ### Toast — `UI.toast()` (JS only) ```js UI.toast("Saved to disk"); UI.toast("Service is back up", "ok"); // ok | warn | bad UI.toast("Connection lost", {type:"bad", title:"Error", duration:6000}); const t = UI.toast("Working…", {duration: 1e9}); t.close(); ``` ### Modal — `.ui-modal` / `UI.modal()` Native ``: focus trap and Esc come from the browser. Sizes `--sm --lg`. Clicking the backdrop closes it unless `dismissable:false`. ```html

Title

Body

``` ```js await UI.confirm("Restart?", {danger:true}); // → true | false await UI.modal({title:"From JS", html:"

Any HTML.

", buttons:[{label:"Cancel", value:false}, {label:"Go", variant:"primary", value:true}]}); ``` ### Tooltip — `data-ui-tip` Pure CSS, hover and keyboard focus. `data-ui-tip-pos="bottom|right"`. ### Progress / spinner / skeleton ```html
``` ### Pagination / breadcrumb ```html
  1. root
  2. media-studio
``` ### Utilities `.ui-muted .ui-faint .ui-accent .ui-mono .ui-small .ui-big .ui-bold .ui-center .ui-right .ui-truncate .ui-nowrap .ui-full .ui-hide .ui-hide-sm .ui-sr .ui-scroll-x .ui-reveal .ui-mt-0..3 .ui-mb-0..3`, plus `body.ui-wide`. ## Interactive background — `ui-bg.js` One fixed canvas behind the page (`.ui-bg-canvas`, `z-index:-1`, `pointer-events:none`). Five themes, all cursor-reactive. The choice persists in a cookie scoped to `.nitaimaarek.com`. | Theme | Looks like | Reacts to the cursor by | |---|---|---| | `stars` | Depth-layered star field, near stars brighter | Drifting toward it — near layers lead, far layers lag | | `mesh` | Linked constellation (the default) | Shouldering points aside as it passes | | `aurora` | Slow blurred colour fields | Parallax drift against the pointer | | `grid` | Dot lattice, slowly breathing | Dots swell and light up under the pointer | | `ripple` | Dot field | Swelling under the cursor; clicks send out waves | ```html data-ui-bg-speed=".6" data-ui-bg-opacity=".8" data-ui-bg-link="150" data-ui-bg-color="#7dd3c0" data-ui-bg-interactive="off"> ``` ```js UI.bg("aurora"); // switch, persists UI.bg(false); // stop and remove the canvas UI.bg("mesh", { density: 1.4, speed: .6, opacity: .8, link: 150 }); ``` Performance: counts scale to viewport area; neighbour links use a uniform spatial hash (O(n), not the usual O(n²)); segments are bucketed by alpha and drawn in five stroke calls; DPR capped at 1.75; the loop stops when the tab is hidden; particle alpha auto-dims on light themes. Under `prefers-reduced-motion` it paints one static frame and never animates. ## JS API ``` UI.toast(msg, "ok"|"warn"|"bad" | {type,title,duration}) → {close(), el} UI.modal({title, body|html, size, dismissable, buttons}) → Promise UI.confirm(msg, {danger, okLabel, cancelLabel}) → Promise UI.alert(msg, {title, okLabel}) → Promise UI.bg(name|false, opts) → interactive background UI.theme() → "dark" | "light" UI.theme("light"|"dark"|"auto") → sets + persists for *.nitaimaarek.com UI.hue(150) → recolor + persist UI.copy(text) → Promise UI.on(event, selector, fn) → delegated listener UI.init(scope) → rescan a subtree UI.$(sel) / UI.$$(sel) → querySelector / Array of matches ``` Auto-initialises on load and re-scans on DOM mutation (rAF-debounced), so injected markup is wired automatically. Emits `ui:theme` on `document` and `ui:tab` from a tab strip. ## Attributes | Attribute | On | Behaviour | |---|---|---| | `data-ui-bg="theme"` | `body` | Starts the background; `-density -speed -opacity -link -color -interactive` tune it | | `data-ui-bg-set="theme"` | any | Switches the background on click | | `data-ui-dropdown` | trigger | Toggles sibling `.ui-menu`, flips at viewport edge | | `data-ui-modal="#id"` | any | Opens that `` | | `data-ui-close` | any | Closes the enclosing dialog | | `data-ui-tabs` | strip | Wires the tabs inside | | `data-ui-tab="id"` | tab | Panel this tab shows | | `data-ui-select` | `select` | Searchable dropdown, stays a real select | | `data-ui-search="off"` | `select` | Suppresses the search box | | `data-ui-copy="text|#id"` | any | Copies to clipboard + toasts | | `data-ui-copy-msg` | any | Overrides the toast text | | `data-ui-copy="off"` | `pre` | No copy button on that code block | | `data-ui-confirm="msg"` | any | Confirm dialog before the click proceeds | | `data-ui-theme-toggle` | any | Cycles light / dark | | `data-ui-tip="text"` | any | Tooltip (`data-ui-tip-pos="bottom\|right"`) | | `data-ui-autosize` | `textarea` | Grows with content | | `data-ui-prefix` / `data-ui-suffix` | `range` | Units around the live value | | `data-ui-segmented` | segmented | Moves `aria-pressed` on click | | `aria-busy="true"` | `.ui-btn` | Spinner, hidden label, clicks blocked | | `data-theme` | `html` | `light` / `dark`; absent = follow the OS | | `data-wide` / `class="ui-wide"` | `body` | Removes the centred column cap | ## Operational notes - Files live at `/root/framework-site` on the host and are served by Caddy's `file_server`; there is no service to restart. - `ui.css` / `ui.js` are sent with `Cache-Control: max-age=300` and `Access-Control-Allow-Origin: *`, so editing `ui.css` rethemes every site that links it within about five minutes — no redeploys.