site: rebuilt on Niveus, and the palette stops being a copy
Niveus is the interface language the project uses from here on -- this site
first, applications after it. Rebuilding the site on it is what proves the
system is one, rather than a folder of CSS with a README.
**`style.css` is gone.** 21 KB that transcribed include/theme.h by hand:
palette, type scale, shadows, spacing, every component. The intent was right --
"a site describing an operating system should look like the operating system"
-- and the mechanism was a copy, which means two files that have to be kept in
step by remembering to. What replaced it is `app.css`, which is layout and
nothing else, and one declaration in each page:
:root { --nv-brand: #7C5CFF; }
That is SCH_ACCENT. Niveus derives the accent and its states, the link colour,
the focus ring, the tint in the greys and how far the status colours lean, for
light and dark both. One number to keep true instead of forty.
**The site is now adaptive.** Dark by default, because the OS is dark, with a
Light/Dark/Auto group in the bar that is real. The choice is kept in
localStorage and re-applied by an inline script before first paint, so the page
never renders in one appearance and corrects itself while you watch.
**PSOGM keeps its one purple, and now says so structurally.** #4B1D9A is
spelled the way Niveus spells a subtree with its own colour -- `data-nv-brand`
with its own `--nv-brand` on that section -- so every role inside re-derives
from it rather than being painted on. Still exactly one place, which is what
branding/README.md asks for.
Content is unchanged; what changed is what it is made of. Cards are `.nv-card`,
tags are `.nv-badge`, the support table is `.nv-table`, the crash section is a
`.nv-bed`, and the docs reader's two rails are `.nv-nav--vertical` with the
current document marked by `aria-current` rather than a class of our own.
`docs.js` emits `.nv-table` and `.nv-code`, so a change to the system reaches
the documentation too.
Three things were found by looking at it in a browser rather than by reading
it, and all three are the same mistake: **a `.nv-card` carries no padding of
its own -- its parts do.** The stat tiles, the "One ISO, one stick" band and
the card headers were all built from bare children, so numbers ran to the card
edge, the USB mark hung off it, and a title and its badge sat at opposite ends
of a row meant for a title and a trailing slot. Where the component had a right
way to say it, the markup now says it that way; where it did not -- a tile has
no parts -- `app.css` supplies the padding and says why.
Checked: every nv- class used across the three pages and docs.js exists in the
CSS, every token referenced by app.css is defined, and app.css contains no hex
value at all. Niveus's own tools/check.py passes.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
9ab8a7a426
commit
6490e5c759
89 changed files with 20804 additions and 1363 deletions
234
docs/Website/niveus/examples/ai-chat/app.css
Normal file
234
docs/Website/niveus/examples/ai-chat/app.css
Normal file
|
|
@ -0,0 +1,234 @@
|
|||
/* ==========================================================================
|
||||
Lumen Chat — app layout
|
||||
--------------------------------------------------------------------------
|
||||
Layout only. The shell is a two-column grid pinned to the viewport, with
|
||||
exactly one scrolling region; everything visual comes from Niveus.
|
||||
|
||||
The avatars below are the one place this app extends the system rather than
|
||||
consuming it — and even then they are built from tokens, not from values
|
||||
invented here. That is the correct way to extend: new shapes, old language.
|
||||
========================================================================== */
|
||||
|
||||
html, body {
|
||||
block-size: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
overflow: hidden; /* the transcript scrolls, the page does not */
|
||||
}
|
||||
|
||||
.app {
|
||||
display: grid;
|
||||
grid-template-columns: 17rem 1fr;
|
||||
block-size: 100svh;
|
||||
}
|
||||
|
||||
@media (max-width: 52rem) {
|
||||
.app {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.app__sidebar {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
/* --- Sidebar ---------------------------------------------------------------- */
|
||||
|
||||
.app__sidebar {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-3);
|
||||
min-block-size: 0;
|
||||
padding: var(--nv-space-4) var(--nv-space-3);
|
||||
background-color: var(--nv-color-background-subtle);
|
||||
border-inline-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
|
||||
.app__threads {
|
||||
flex: 1;
|
||||
min-block-size: 0;
|
||||
overflow-y: auto;
|
||||
margin-inline: calc(var(--nv-space-1) * -1);
|
||||
padding-inline: var(--nv-space-1);
|
||||
}
|
||||
|
||||
.thread__title {
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.thread__meta {
|
||||
display: block;
|
||||
font: var(--nv-font-caption);
|
||||
color: var(--nv-color-text-tertiary);
|
||||
font-weight: var(--nv-font-weight-regular);
|
||||
}
|
||||
|
||||
/* This sidebar is a plain aside rather than .nv-nav--vertical, so the list
|
||||
keeps its default row direction until told otherwise. */
|
||||
.app__threads .nv-nav__list {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: var(--nv-space-0-5);
|
||||
}
|
||||
|
||||
/* A nav item holding two lines needs to stop centring them. */
|
||||
.app__threads .nv-nav__item {
|
||||
align-items: flex-start;
|
||||
flex-direction: column;
|
||||
gap: 0;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
/* --- Main ------------------------------------------------------------------- */
|
||||
|
||||
.app__main {
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
min-block-size: 0;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.app__transcript {
|
||||
overflow-y: auto;
|
||||
overscroll-behavior: contain;
|
||||
padding: var(--nv-space-6) var(--nv-layout-gutter);
|
||||
}
|
||||
|
||||
.transcript {
|
||||
max-inline-size: 48rem;
|
||||
margin-inline: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-6);
|
||||
}
|
||||
|
||||
/* --- Messages ---------------------------------------------------------------- */
|
||||
|
||||
.message {
|
||||
display: flex;
|
||||
gap: var(--nv-space-3);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.message--from-user {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
.message__body {
|
||||
max-inline-size: 42rem;
|
||||
min-inline-size: 0;
|
||||
}
|
||||
|
||||
.message--from-user .message__body {
|
||||
padding: var(--nv-space-3) var(--nv-space-4);
|
||||
background-color: var(--nv-color-accent-subtle);
|
||||
border: var(--nv-border-width) solid var(--nv-color-accent-border);
|
||||
border-radius: var(--nv-radius-lg);
|
||||
border-end-end-radius: var(--nv-radius-xs);
|
||||
color: var(--nv-color-text-primary);
|
||||
}
|
||||
|
||||
.message__body > * + * {
|
||||
margin-block-start: var(--nv-space-3);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
inline-size: var(--nv-space-8);
|
||||
block-size: var(--nv-space-8);
|
||||
flex: none;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--nv-radius-full);
|
||||
font: var(--nv-font-caption);
|
||||
font-weight: var(--nv-font-weight-semibold);
|
||||
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
box-shadow: var(--nv-highlight-top), var(--nv-elevation-1);
|
||||
}
|
||||
|
||||
.avatar--agent {
|
||||
background-color: var(--nv-color-accent);
|
||||
color: var(--nv-color-on-accent);
|
||||
border-color: transparent;
|
||||
}
|
||||
|
||||
.avatar--user {
|
||||
background-color: var(--nv-color-surface-elevated);
|
||||
color: var(--nv-color-text-secondary);
|
||||
}
|
||||
|
||||
.code-block {
|
||||
display: block;
|
||||
padding: var(--nv-space-4);
|
||||
border-radius: var(--nv-radius-md);
|
||||
background-color: var(--nv-color-surface-inset);
|
||||
border: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
box-shadow: var(--nv-elevation-inset-sm);
|
||||
overflow-x: auto;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
/* The three dots while an answer is still arriving. */
|
||||
.thinking {
|
||||
display: inline-flex;
|
||||
gap: var(--nv-space-1);
|
||||
align-items: center;
|
||||
padding-block: var(--nv-space-2);
|
||||
}
|
||||
|
||||
.thinking span {
|
||||
inline-size: var(--nv-space-1-5);
|
||||
block-size: var(--nv-space-1-5);
|
||||
border-radius: var(--nv-radius-full);
|
||||
background-color: var(--nv-color-text-tertiary);
|
||||
animation: thinking 1.2s var(--nv-ease-standard) infinite;
|
||||
}
|
||||
|
||||
.thinking span:nth-child(2) { animation-delay: 0.15s; }
|
||||
.thinking span:nth-child(3) { animation-delay: 0.3s; }
|
||||
|
||||
@keyframes thinking {
|
||||
0%, 60%, 100% { opacity: 0.25; translate: 0 0; }
|
||||
30% { opacity: 1; translate: 0 calc(var(--nv-travel-sm) * -0.5); }
|
||||
}
|
||||
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.thinking span { animation: none; opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* --- Composer ----------------------------------------------------------------- */
|
||||
|
||||
.composer {
|
||||
padding: var(--nv-space-4) var(--nv-layout-gutter) var(--nv-space-5);
|
||||
border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
background-color: var(--nv-color-background);
|
||||
}
|
||||
|
||||
.composer__inner {
|
||||
max-inline-size: 48rem;
|
||||
margin-inline: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-2-5);
|
||||
}
|
||||
|
||||
.composer__row {
|
||||
display: flex;
|
||||
align-items: flex-end;
|
||||
gap: var(--nv-space-2);
|
||||
}
|
||||
|
||||
.composer__row .nv-input {
|
||||
min-block-size: var(--nv-control-height-lg);
|
||||
max-block-size: 9rem;
|
||||
}
|
||||
|
||||
.composer__hints {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-4);
|
||||
}
|
||||
485
docs/Website/niveus/examples/ai-chat/index.html
Normal file
485
docs/Website/niveus/examples/ai-chat/index.html
Normal file
|
|
@ -0,0 +1,485 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Lumen Chat — an AI chat app built with Niveus</title>
|
||||
|
||||
<link rel="stylesheet" href="../../dist/niveus.css">
|
||||
<script src="../../niveus/niveus.js" defer></script>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
|
||||
<script>
|
||||
try {
|
||||
var saved = localStorage.getItem("lumen-theme");
|
||||
if (saved && saved !== "system") document.documentElement.setAttribute("data-theme", saved);
|
||||
} catch (error) {}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root { --nv-brand: #6d5efc; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="app">
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<aside class="app__sidebar" style="--nv-selection-name: nv-chat-threads">
|
||||
<a class="nv-nav__brand" href="#" style="padding-inline: var(--nv-space-2)">
|
||||
<svg class="nv-nav__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M12 3v18M3 12h18M6 6l12 12M18 6L6 18" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
Lumen
|
||||
</a>
|
||||
|
||||
<button class="nv-button nv-button--block" id="new-thread">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
New conversation
|
||||
</button>
|
||||
|
||||
<nav class="app__threads" aria-label="Conversations">
|
||||
<span class="nv-nav__section">Today</span>
|
||||
<ul class="nv-nav__list" id="threads" data-nv-travel>
|
||||
<li>
|
||||
<button class="nv-nav__item" data-thread="latency" aria-current="page">
|
||||
<span class="thread__title">Checkout latency after #4412</span>
|
||||
<span class="thread__meta">12 messages</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="nv-nav__item" data-thread="migration">
|
||||
<span class="thread__title">Postgres 15 migration plan</span>
|
||||
<span class="thread__meta">4 messages</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<span class="nv-nav__section">Earlier</span>
|
||||
<ul class="nv-nav__list">
|
||||
<li>
|
||||
<button class="nv-nav__item" data-thread="audit">
|
||||
<span class="thread__title">Dependency audit — Q3</span>
|
||||
<span class="thread__meta">31 messages</span>
|
||||
</button>
|
||||
</li>
|
||||
<li>
|
||||
<button class="nv-nav__item" data-thread="onboarding">
|
||||
<span class="thread__title">Why does billing own the cart?</span>
|
||||
<span class="thread__meta">8 messages</span>
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
<hr class="nv-divider" style="margin-block: var(--nv-space-2)">
|
||||
|
||||
<button class="nv-nav__item nv-menu-trigger" popovertarget="account-menu"
|
||||
style="--nv-anchor: --account">
|
||||
<span class="avatar avatar--user" aria-hidden="true">RK</span>
|
||||
<span class="thread__title">Robin Kade</span>
|
||||
</button>
|
||||
|
||||
<div class="nv-menu" popover id="account-menu" style="--nv-anchor: --account">
|
||||
<span class="nv-menu__label">Robin Kade</span>
|
||||
<button class="nv-menu__item" data-nv-dialog="settings">
|
||||
Settings<span class="nv-menu__shortcut">⌘,</span>
|
||||
</button>
|
||||
<button class="nv-menu__item">Keyboard shortcuts</button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Sign out</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<main class="app__main">
|
||||
|
||||
<nav class="nv-nav nv-nav--static" aria-label="Conversation">
|
||||
<h1 class="nv-title nv-truncate" id="thread-heading">Checkout latency after #4412</h1>
|
||||
|
||||
<span class="nv-nav__spacer"></span>
|
||||
|
||||
<button class="nv-button nv-button--sm nv-menu-trigger" popovertarget="model-menu"
|
||||
style="--nv-anchor: --model">
|
||||
<span id="model-label">Lumen 2</span>
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"
|
||||
style="margin-inline-start: var(--nv-space-1)"></span>
|
||||
</button>
|
||||
|
||||
<div class="nv-menu" popover id="model-menu" style="--nv-anchor: --model">
|
||||
<span class="nv-menu__label">Model</span>
|
||||
<button class="nv-menu__item" aria-checked="true" data-model="Lumen 2">
|
||||
Lumen 2<span class="nv-menu__shortcut">Balanced</span>
|
||||
</button>
|
||||
<button class="nv-menu__item" data-model="Lumen 2 Pro">
|
||||
Lumen 2 Pro<span class="nv-menu__shortcut">Deeper</span>
|
||||
</button>
|
||||
<button class="nv-menu__item" data-model="Lumen Mini">
|
||||
Lumen Mini<span class="nv-menu__shortcut">Fastest</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<button class="nv-button nv-button--sm nv-button--ghost">Share</button>
|
||||
<button class="nv-button nv-button--sm nv-button--ghost nv-button--icon"
|
||||
data-nv-dialog="settings" aria-label="Settings">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="12" cy="12" r="3.2" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M12 3v2.2M12 18.8V21M21 12h-2.2M5.2 12H3M18.4 5.6l-1.6 1.6M7.2 16.8l-1.6 1.6M18.4 18.4l-1.6-1.6M7.2 7.2L5.6 5.6"
|
||||
stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<div class="app__transcript" id="transcript-scroll">
|
||||
<div class="transcript nv-continuous" id="transcript">
|
||||
<!-- rendered from the thread data below -->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="composer">
|
||||
<form class="composer__inner" id="composer">
|
||||
<div class="composer__row">
|
||||
<button class="nv-button nv-button--lg nv-button--icon" type="button" aria-label="Attach">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M18 11l-6.5 6.5a4 4 0 01-5.6-5.6l7-7a2.8 2.8 0 013.9 4L9.6 16a1.6 1.6 0 01-2.3-2.3l6.2-6.2"
|
||||
stroke="currentColor" stroke-width="1.8" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
|
||||
<label class="nv-visually-hidden" for="prompt">Message Lumen</label>
|
||||
<textarea class="nv-input" id="prompt" rows="1"
|
||||
placeholder="Ask about this codebase…"></textarea>
|
||||
|
||||
<button class="nv-button nv-button--lg nv-button--primary nv-button--icon"
|
||||
type="submit" aria-label="Send">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M5 12h13M13 6l6 6-6 6" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="composer__hints">
|
||||
<label class="nv-choice nv-caption">
|
||||
<input class="nv-switch" type="checkbox" checked>
|
||||
Search the repository
|
||||
</label>
|
||||
<label class="nv-choice nv-caption">
|
||||
<input class="nv-switch" type="checkbox">
|
||||
Propose changes
|
||||
</label>
|
||||
<span class="nv-nav__spacer"></span>
|
||||
<span class="nv-caption">Enter to send · Shift + Enter for a new line</span>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<!-- ==================================================================== -->
|
||||
<dialog class="nv-dialog nv-dialog--wide" id="settings" aria-labelledby="settings-title">
|
||||
<div class="nv-dialog__header">
|
||||
<div>
|
||||
<h2 class="nv-dialog__title" id="settings-title">Settings</h2>
|
||||
<p class="nv-dialog__description">Applies to this workspace.</p>
|
||||
</div>
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-dialog__close"
|
||||
aria-label="Close" data-nv-close>
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="nv-dialog__body">
|
||||
<div class="nv-nav-tabs" role="tablist" data-nv-tabs aria-label="Settings sections"
|
||||
style="--nv-selection-name: nv-chat-settings-tabs">
|
||||
<button class="nv-nav__item" role="tab" id="tab-general"
|
||||
aria-controls="panel-general" aria-selected="true">General</button>
|
||||
<button class="nv-nav__item" role="tab" id="tab-model"
|
||||
aria-controls="panel-model" aria-selected="false" tabindex="-1">Model</button>
|
||||
<button class="nv-nav__item" role="tab" id="tab-privacy"
|
||||
aria-controls="panel-privacy" aria-selected="false" tabindex="-1">Privacy</button>
|
||||
</div>
|
||||
|
||||
<div class="nv-tab-panel" id="panel-general" role="tabpanel" aria-labelledby="tab-general"
|
||||
style="padding-block-start: var(--nv-space-5)">
|
||||
<div class="nv-stack">
|
||||
<div class="nv-field">
|
||||
<label class="nv-field__label" for="ws-name">Workspace name</label>
|
||||
<input class="nv-input" id="ws-name" value="Northwire Platform">
|
||||
</div>
|
||||
|
||||
<div class="nv-field">
|
||||
<span class="nv-field__label">Appearance</span>
|
||||
<div class="nv-button-group" role="group" aria-label="Appearance">
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="light" aria-pressed="false">Light</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="dark" aria-pressed="false">Dark</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="system" aria-pressed="true">System</button>
|
||||
</div>
|
||||
<span class="nv-field__hint">The whole interface cross-fades between the two.</span>
|
||||
</div>
|
||||
|
||||
<label class="nv-choice">
|
||||
<input class="nv-switch" type="checkbox" checked>
|
||||
Send on Enter
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nv-tab-panel" id="panel-model" role="tabpanel" aria-labelledby="tab-model"
|
||||
style="padding-block-start: var(--nv-space-5)" hidden>
|
||||
<div class="nv-stack">
|
||||
<div class="nv-field">
|
||||
<label class="nv-field__label" for="default-model">Default model</label>
|
||||
<div class="nv-select">
|
||||
<select class="nv-input" id="default-model">
|
||||
<option selected>Lumen 2 — balanced</option>
|
||||
<option>Lumen 2 Pro — deeper reasoning</option>
|
||||
<option>Lumen Mini — fastest</option>
|
||||
</select>
|
||||
</div>
|
||||
<span class="nv-field__hint">Individual conversations can override this.</span>
|
||||
</div>
|
||||
|
||||
<div class="nv-field">
|
||||
<label class="nv-field__label" for="context">Context window</label>
|
||||
<div class="nv-input-group">
|
||||
<input class="nv-input" id="context" value="200000">
|
||||
<span class="nv-input-group__affix">tokens</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<label class="nv-choice">
|
||||
<input class="nv-checkbox" type="checkbox" checked>
|
||||
Cite sources in every answer
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nv-tab-panel" id="panel-privacy" role="tabpanel" aria-labelledby="tab-privacy"
|
||||
style="padding-block-start: var(--nv-space-5)" hidden>
|
||||
<div class="nv-stack">
|
||||
<div class="nv-bed nv-bed--quiet nv-bed--single">
|
||||
<p class="nv-body">
|
||||
Repository contents are used to answer this workspace's questions
|
||||
and nothing else.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<label class="nv-choice">
|
||||
<input class="nv-switch" type="checkbox">
|
||||
Keep transcripts after 30 days
|
||||
</label>
|
||||
|
||||
<div class="nv-field">
|
||||
<span class="nv-field__label">Danger zone</span>
|
||||
<div class="nv-cluster">
|
||||
<button class="nv-button nv-button--danger nv-button--sm">Delete all conversations</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nv-dialog__footer nv-dialog__footer--divided">
|
||||
<button class="nv-button" data-nv-close>Cancel</button>
|
||||
<button class="nv-button nv-button--primary" data-nv-close>Save changes</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.documentElement.addEventListener("nv:theme", (event) => {
|
||||
try { localStorage.setItem("lumen-theme", event.detail.theme); } catch {}
|
||||
});
|
||||
nv.syncThemeControls(document.documentElement.getAttribute("data-theme") || "system");
|
||||
|
||||
/* ------------------------------------------------------------------
|
||||
Canned content. A real client would fetch this; what matters here is
|
||||
that switching threads is a content change inside one surface, so it
|
||||
goes through nv.transition() and cross-fades in place.
|
||||
------------------------------------------------------------------ */
|
||||
const threads = {
|
||||
latency: {
|
||||
title: "Checkout latency after #4412",
|
||||
messages: [
|
||||
{ from: "user", html: "Why did checkout latency double after Tuesday's deploy?" },
|
||||
{ from: "agent", html:
|
||||
`<p>The <code class="nv-code">pricing</code> service began resolving tax rules
|
||||
per line item instead of per cart in <code class="nv-code">#4412</code>.
|
||||
Carts with more than two items take the slow path.</p>
|
||||
<div class="nv-cluster nv-cluster--tight">
|
||||
<span class="nv-badge nv-badge--warning">p95 +118%</span>
|
||||
<span class="nv-badge">3 files</span>
|
||||
<span class="nv-badge">pricing/tax.ts</span>
|
||||
</div>` },
|
||||
{ from: "user", html: "Show me the hot path." },
|
||||
{ from: "agent", html:
|
||||
`<p>Here is the loop that moved:</p>
|
||||
<code class="code-block nv-code">for (const line of cart.lines) {
|
||||
// was: resolved once per cart
|
||||
const rules = await taxRules.resolve(line.destination);
|
||||
line.tax = rules.apply(line);
|
||||
}</code>
|
||||
<p><code class="nv-code">taxRules.resolve</code> is a network call. Hoisting
|
||||
it above the loop restores the previous shape.</p>` },
|
||||
],
|
||||
},
|
||||
migration: {
|
||||
title: "Postgres 15 migration plan",
|
||||
messages: [
|
||||
{ from: "user", html: "What breaks if we go to Postgres 15 next sprint?" },
|
||||
{ from: "agent", html:
|
||||
`<p>Two things in this codebase, and one in the deploy pipeline.</p>
|
||||
<div class="nv-cluster nv-cluster--tight">
|
||||
<span class="nv-badge nv-badge--danger">1 blocking</span>
|
||||
<span class="nv-badge nv-badge--warning">2 to review</span>
|
||||
</div>
|
||||
<p>The blocking one is the <code class="nv-code">reporting</code> service's use
|
||||
of implicit integer casts in <code class="nv-code">?column?</code> aliases,
|
||||
removed in 15.</p>` },
|
||||
],
|
||||
},
|
||||
audit: {
|
||||
title: "Dependency audit — Q3",
|
||||
messages: [
|
||||
{ from: "user", html: "Which advisories actually reach our code?" },
|
||||
{ from: "agent", html:
|
||||
`<p>Four of the thirty-one. The rest are in transitive packages whose
|
||||
affected entry points you never call.</p>
|
||||
<div class="nv-cluster nv-cluster--tight">
|
||||
<span class="nv-badge nv-badge--danger">2 high</span>
|
||||
<span class="nv-badge nv-badge--warning">2 moderate</span>
|
||||
<span class="nv-badge nv-badge--success">27 unreachable</span>
|
||||
</div>` },
|
||||
],
|
||||
},
|
||||
onboarding: {
|
||||
title: "Why does billing own the cart?",
|
||||
messages: [
|
||||
{ from: "user", html: "Why does billing own the cart model?" },
|
||||
{ from: "agent", html:
|
||||
`<p>It did not, until <code class="nv-code">#2180</code> moved it there so
|
||||
promotions could be priced in one place. The commit message and the
|
||||
review thread both say this was meant to be temporary.</p>` },
|
||||
],
|
||||
},
|
||||
};
|
||||
|
||||
const transcript = document.getElementById("transcript");
|
||||
const scroller = document.getElementById("transcript-scroll");
|
||||
const heading = document.getElementById("thread-heading");
|
||||
|
||||
function messageHTML(message) {
|
||||
const isUser = message.from === "user";
|
||||
return `
|
||||
<div class="message message--from-${message.from}">
|
||||
<span class="avatar avatar--${isUser ? "user" : "agent"}" aria-hidden="true">${isUser ? "RK" : "L"}</span>
|
||||
<div class="message__body">${message.html}</div>
|
||||
</div>`;
|
||||
}
|
||||
|
||||
function render(key) {
|
||||
const thread = threads[key];
|
||||
heading.textContent = thread.title;
|
||||
transcript.innerHTML = thread.messages.map(messageHTML).join("");
|
||||
}
|
||||
|
||||
render("latency");
|
||||
|
||||
/* Switching threads: the selection travels in the sidebar and the
|
||||
transcript cross-fades, because both are one state change. */
|
||||
const list = document.getElementById("threads");
|
||||
document.querySelectorAll("[data-thread]").forEach((item) => {
|
||||
item.addEventListener("click", () => {
|
||||
if (item.hasAttribute("aria-current")) return;
|
||||
nv.transition(() => {
|
||||
document.querySelectorAll("[data-thread][aria-current]")
|
||||
.forEach((el) => el.removeAttribute("aria-current"));
|
||||
item.setAttribute("aria-current", "page");
|
||||
render(item.dataset.thread);
|
||||
});
|
||||
scroller.scrollTop = 0;
|
||||
});
|
||||
});
|
||||
|
||||
/* Choosing a model updates the menu's own checked state. */
|
||||
document.querySelectorAll("[data-model]").forEach((option) => {
|
||||
option.addEventListener("click", () => {
|
||||
document.querySelectorAll("[data-model]").forEach((other) => {
|
||||
other.setAttribute("aria-checked", String(other === option));
|
||||
});
|
||||
document.getElementById("model-label").textContent = option.dataset.model;
|
||||
document.getElementById("model-menu").hidePopover();
|
||||
});
|
||||
});
|
||||
|
||||
/* Sending: append, show that something is happening, then answer. The
|
||||
point is the states, not the reply. */
|
||||
const form = document.getElementById("composer");
|
||||
const prompt = document.getElementById("prompt");
|
||||
|
||||
function append(html) {
|
||||
transcript.insertAdjacentHTML("beforeend", html);
|
||||
scroller.scrollTo({ top: scroller.scrollHeight, behavior: "smooth" });
|
||||
}
|
||||
|
||||
form.addEventListener("submit", (event) => {
|
||||
event.preventDefault();
|
||||
const text = prompt.value.trim();
|
||||
if (!text) return;
|
||||
|
||||
append(messageHTML({ from: "user", html: text.replace(/[<>&]/g, (c) =>
|
||||
({ "<": "<", ">": ">", "&": "&" }[c])) }));
|
||||
prompt.value = "";
|
||||
|
||||
append(`<div class="message message--from-agent" id="pending">
|
||||
<span class="avatar avatar--agent" aria-hidden="true">L</span>
|
||||
<div class="message__body">
|
||||
<span class="thinking" role="status" aria-label="Thinking">
|
||||
<span></span><span></span><span></span>
|
||||
</span>
|
||||
</div>
|
||||
</div>`);
|
||||
|
||||
setTimeout(() => {
|
||||
document.getElementById("pending")?.remove();
|
||||
append(messageHTML({ from: "agent", html:
|
||||
`<p>This example has no model behind it — it exists to show the states
|
||||
a chat interface moves through: the message landing, something
|
||||
visibly happening, and the answer arriving in the same column.</p>` }));
|
||||
}, 1400);
|
||||
});
|
||||
|
||||
prompt.addEventListener("keydown", (event) => {
|
||||
if (event.key === "Enter" && !event.shiftKey) {
|
||||
event.preventDefault();
|
||||
form.requestSubmit();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById("new-thread").addEventListener("click", () => {
|
||||
nv.transition(() => {
|
||||
document.querySelectorAll("[data-thread][aria-current]")
|
||||
.forEach((el) => el.removeAttribute("aria-current"));
|
||||
heading.textContent = "New conversation";
|
||||
transcript.innerHTML = `
|
||||
<div class="nv-bed nv-bed--quiet" style="text-align: center">
|
||||
<p class="nv-title">Ask about this codebase</p>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-2)">
|
||||
Lumen has indexed 1,284 files across 3 repositories.
|
||||
</p>
|
||||
</div>`;
|
||||
});
|
||||
prompt.focus();
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
186
docs/Website/niveus/examples/ai-landing/app.css
Normal file
186
docs/Website/niveus/examples/ai-landing/app.css
Normal file
|
|
@ -0,0 +1,186 @@
|
|||
/* ==========================================================================
|
||||
Lumen — page layout
|
||||
--------------------------------------------------------------------------
|
||||
Everything in this file is *layout*: where things sit, how wide they get,
|
||||
when they stack. Not one color, shadow, radius, type size or duration is
|
||||
stated here — those all come from Niveus, which is the whole point of the
|
||||
split. If you find yourself writing a hex value in a file like this, the
|
||||
system is missing a token.
|
||||
========================================================================== */
|
||||
|
||||
.shell {
|
||||
max-inline-size: var(--nv-layout-max-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--nv-layout-gutter);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-block: var(--nv-space-20);
|
||||
}
|
||||
|
||||
.section__head {
|
||||
max-inline-size: var(--nv-measure);
|
||||
margin-block-end: var(--nv-space-8);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-3);
|
||||
}
|
||||
|
||||
.section__head--center {
|
||||
margin-inline: auto;
|
||||
text-align: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
/* --- Hero ------------------------------------------------------------------ */
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
gap: var(--nv-space-12);
|
||||
padding-block: var(--nv-space-16) var(--nv-space-8);
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
@media (min-width: 62rem) {
|
||||
.hero {
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: var(--nv-space-16);
|
||||
}
|
||||
}
|
||||
|
||||
.hero__copy {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-5);
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.hero__title {
|
||||
font-size: clamp(var(--nv-font-size-4xl), 6vw, var(--nv-font-size-5xl));
|
||||
}
|
||||
|
||||
/* --- Preview ---------------------------------------------------------------
|
||||
A mock of the product, sitting on a bed so the frosted panel above it has
|
||||
something to be frosted against. */
|
||||
|
||||
.preview {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-3);
|
||||
max-inline-size: 30rem;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: var(--nv-space-3) var(--nv-space-4);
|
||||
border-radius: var(--nv-radius-lg);
|
||||
max-inline-size: 85%;
|
||||
}
|
||||
|
||||
.bubble--from-user {
|
||||
align-self: flex-end;
|
||||
border-end-end-radius: var(--nv-radius-xs);
|
||||
}
|
||||
|
||||
.bubble--from-agent {
|
||||
align-self: flex-start;
|
||||
border-end-start-radius: var(--nv-radius-xs);
|
||||
}
|
||||
|
||||
/* --- Logos ------------------------------------------------------------------ */
|
||||
|
||||
.logos {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: var(--nv-space-8);
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-block: var(--nv-space-8);
|
||||
opacity: 0.75;
|
||||
}
|
||||
|
||||
/* --- Features ---------------------------------------------------------------- */
|
||||
|
||||
.features {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
|
||||
gap: var(--nv-space-5);
|
||||
}
|
||||
|
||||
.feature__icon {
|
||||
inline-size: var(--nv-space-10);
|
||||
block-size: var(--nv-space-10);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
margin-block-end: var(--nv-space-4);
|
||||
border-radius: var(--nv-radius-md);
|
||||
background-color: var(--nv-color-accent-subtle);
|
||||
color: var(--nv-color-accent-text);
|
||||
}
|
||||
|
||||
.feature__icon svg {
|
||||
inline-size: var(--nv-space-5);
|
||||
block-size: var(--nv-space-5);
|
||||
}
|
||||
|
||||
/* --- Pricing ------------------------------------------------------------------ */
|
||||
|
||||
.pricing {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(17rem, 100%), 1fr));
|
||||
gap: var(--nv-space-5);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.price {
|
||||
display: flex;
|
||||
align-items: baseline;
|
||||
gap: var(--nv-space-2);
|
||||
margin-block: var(--nv-space-4);
|
||||
}
|
||||
|
||||
.price__amount {
|
||||
font-size: var(--nv-font-size-4xl);
|
||||
font-weight: var(--nv-font-weight-semibold);
|
||||
letter-spacing: var(--nv-letter-spacing-tighter);
|
||||
color: var(--nv-color-text-primary);
|
||||
}
|
||||
|
||||
.plan-features {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-2-5);
|
||||
}
|
||||
|
||||
.plan-features li {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: var(--nv-space-2-5);
|
||||
}
|
||||
|
||||
.plan-features svg {
|
||||
inline-size: 1em;
|
||||
block-size: 1em;
|
||||
margin-block-start: 0.25em;
|
||||
flex: none;
|
||||
color: var(--nv-color-success-text);
|
||||
}
|
||||
|
||||
/* --- Closing ------------------------------------------------------------------- */
|
||||
|
||||
.closing {
|
||||
text-align: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-5);
|
||||
padding-block: var(--nv-space-12);
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
padding-block: var(--nv-space-8) var(--nv-space-12);
|
||||
border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
468
docs/Website/niveus/examples/ai-landing/index.html
Normal file
468
docs/Website/niveus/examples/ai-landing/index.html
Normal file
|
|
@ -0,0 +1,468 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Lumen — an AI product landing page built with Niveus</title>
|
||||
|
||||
<!-- The system, and its optional continuity helpers. -->
|
||||
<link rel="stylesheet" href="../../dist/niveus.css">
|
||||
<script src="../../niveus/niveus.js" defer></script>
|
||||
|
||||
<!-- This page's own layout. No colors, no shadows, no type sizes. -->
|
||||
<link rel="stylesheet" href="app.css">
|
||||
|
||||
<script>
|
||||
/* Applied before first paint so the page never renders in the wrong
|
||||
appearance and then corrects itself. */
|
||||
try {
|
||||
var saved = localStorage.getItem("lumen-theme");
|
||||
if (saved && saved !== "system") document.documentElement.setAttribute("data-theme", saved);
|
||||
} catch (error) { /* storage unavailable — follow the system */ }
|
||||
</script>
|
||||
|
||||
<!-- The entire identity of this site is one declaration. -->
|
||||
<style>
|
||||
:root { --nv-brand: #6d5efc; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="nv-nav" id="topnav" aria-label="Main" style="--nv-selection-name: nv-lumen-nav">
|
||||
<a class="nv-nav__brand" href="#top">
|
||||
<svg class="nv-nav__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M12 3v18M3 12h18M6 6l12 12M18 6L6 18" stroke="currentColor"
|
||||
stroke-width="1.5" stroke-linecap="round"/>
|
||||
</svg>
|
||||
Lumen
|
||||
</a>
|
||||
|
||||
<ul class="nv-nav__list" data-nv-travel>
|
||||
<li><a class="nv-nav__item" href="#features" aria-current="page">Features</a></li>
|
||||
<li><a class="nv-nav__item" href="#build">Build</a></li>
|
||||
<li><a class="nv-nav__item" href="#pricing">Pricing</a></li>
|
||||
<li><a class="nv-nav__item" href="#faq">FAQ</a></li>
|
||||
</ul>
|
||||
|
||||
<span class="nv-nav__spacer"></span>
|
||||
|
||||
<div class="nv-button-group" role="group" aria-label="Appearance">
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="light" aria-pressed="false">Light</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="dark" aria-pressed="false">Dark</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="system" aria-pressed="true">Auto</button>
|
||||
</div>
|
||||
|
||||
<a class="nv-button nv-button--sm nv-button--primary" href="#pricing">Get started</a>
|
||||
</nav>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<header class="shell hero">
|
||||
<div class="hero__copy">
|
||||
<span class="nv-badge nv-badge--accent">
|
||||
<span class="nv-badge__dot"></span>
|
||||
Lumen 2 is available
|
||||
</span>
|
||||
|
||||
<h1 class="nv-display hero__title">The model that reads your whole codebase.</h1>
|
||||
|
||||
<p class="nv-prose">
|
||||
Lumen answers questions about the systems you actually run — not a
|
||||
snapshot of them. Point it at a repository and it keeps up as the code
|
||||
changes.
|
||||
</p>
|
||||
|
||||
<div class="nv-cluster">
|
||||
<a class="nv-button nv-button--primary nv-button--lg" href="#pricing">Start free</a>
|
||||
<button class="nv-button nv-button--lg" data-nv-dialog="demo">Watch a demo</button>
|
||||
</div>
|
||||
|
||||
<p class="nv-caption">No card required · 14-day trial · SOC 2 Type II</p>
|
||||
</div>
|
||||
|
||||
<div class="nv-bed nv-bed--single">
|
||||
<span class="nv-bed__label">Live preview</span>
|
||||
|
||||
<div class="preview">
|
||||
<div class="bubble bubble--from-user nv-material-elevated">
|
||||
Why did checkout latency double after Tuesday's deploy?
|
||||
</div>
|
||||
|
||||
<div class="nv-card nv-card--frosted bubble bubble--from-agent"
|
||||
style="max-inline-size: 100%; padding: var(--nv-space-4)">
|
||||
<p class="nv-body">
|
||||
The <code class="nv-code">pricing</code> service started resolving
|
||||
tax rules per line item instead of per cart in
|
||||
<code class="nv-code">#4412</code>. Two of three carts hit the slow
|
||||
path.
|
||||
</p>
|
||||
<div class="nv-cluster nv-cluster--tight" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="nv-badge nv-badge--warning">p95 +118%</span>
|
||||
<span class="nv-badge">3 files</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="bubble bubble--from-user nv-material-elevated">
|
||||
Open a PR that reverts just that change.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<section class="shell">
|
||||
<div class="logos">
|
||||
<span class="nv-overline">Northwire</span>
|
||||
<span class="nv-overline">Harbour</span>
|
||||
<span class="nv-overline">Greenhouse Labs</span>
|
||||
<span class="nv-overline">Quarry</span>
|
||||
<span class="nv-overline">Signal Systems</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<section class="shell section" id="features">
|
||||
<div class="section__head">
|
||||
<span class="nv-overline">Why teams switch</span>
|
||||
<h2 class="nv-heading">Built for the code you already have.</h2>
|
||||
<p class="nv-prose">
|
||||
Three things matter when a model works on a real system: that it sees
|
||||
the current state, that it explains itself, and that it cannot quietly
|
||||
do something irreversible.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="features">
|
||||
<article class="nv-card">
|
||||
<div class="nv-card__body">
|
||||
<span class="feature__icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M4 7h16M4 12h16M4 17h10" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<h3 class="nv-title">Always current</h3>
|
||||
<p style="margin-block-start: var(--nv-space-2)">
|
||||
Indexes on every push, so answers describe the branch you are on
|
||||
rather than the one it was trained on.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card">
|
||||
<div class="nv-card__body">
|
||||
<span class="feature__icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<circle cx="11" cy="11" r="6.5" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M16 16l4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<h3 class="nv-title">Shows its work</h3>
|
||||
<p style="margin-block-start: var(--nv-space-2)">
|
||||
Every claim links to the lines it came from. If it cannot cite a
|
||||
file, it says so instead of guessing.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card">
|
||||
<div class="nv-card__body">
|
||||
<span class="feature__icon">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M12 3l7 3v6c0 4-3 7-7 9-4-2-7-5-7-9V6z" stroke="currentColor"
|
||||
stroke-width="2" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<h3 class="nv-title">Asks before it acts</h3>
|
||||
<p style="margin-block-start: var(--nv-space-2)">
|
||||
Writes, merges and deploys are proposals until a human approves
|
||||
them. Read access is the default.
|
||||
</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<section class="shell section" id="build">
|
||||
<div class="section__head">
|
||||
<span class="nv-overline">What teams build</span>
|
||||
<h2 class="nv-heading">One model, several jobs.</h2>
|
||||
</div>
|
||||
|
||||
<div class="nv-scroller-frame" data-nv-scroller>
|
||||
<div class="nv-scroller">
|
||||
<article class="nv-card nv-card--interactive">
|
||||
<div class="nv-card__header"><div>
|
||||
<h3 class="nv-card__title">Incident triage</h3>
|
||||
<p class="nv-card__subtitle">Platform teams</p>
|
||||
</div></div>
|
||||
<div class="nv-card__body">
|
||||
Correlates a regression with the change that caused it, and drafts
|
||||
the revert.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--interactive">
|
||||
<div class="nv-card__header"><div>
|
||||
<h3 class="nv-card__title">Review assistant</h3>
|
||||
<p class="nv-card__subtitle">Every repository</p>
|
||||
</div></div>
|
||||
<div class="nv-card__body">
|
||||
Reads the diff in the context of the whole system, not just the
|
||||
files that changed.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--interactive">
|
||||
<div class="nv-card__header"><div>
|
||||
<h3 class="nv-card__title">Migration planning</h3>
|
||||
<p class="nv-card__subtitle">Staff engineers</p>
|
||||
</div></div>
|
||||
<div class="nv-card__body">
|
||||
Finds every call site, groups them by risk, and sequences the work.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--interactive">
|
||||
<div class="nv-card__header"><div>
|
||||
<h3 class="nv-card__title">Onboarding</h3>
|
||||
<p class="nv-card__subtitle">New joiners</p>
|
||||
</div></div>
|
||||
<div class="nv-card__body">
|
||||
Answers "why is this here" with the commit, the review and the
|
||||
issue behind it.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--interactive">
|
||||
<div class="nv-card__header"><div>
|
||||
<h3 class="nv-card__title">Dependency audits</h3>
|
||||
<p class="nv-card__subtitle">Security</p>
|
||||
</div></div>
|
||||
<div class="nv-card__body">
|
||||
Separates the advisories that reach your code from the ones that
|
||||
do not.
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
|
||||
<button class="nv-button nv-button--icon nv-button--float" data-nv-scroll="prev" aria-label="Previous">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M14 6l-6 6 6 6" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="nv-button nv-button--icon nv-button--float" data-nv-scroll="next" aria-label="Next">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M10 6l6 6-6 6" stroke="currentColor" stroke-width="2"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<section class="shell section" id="pricing">
|
||||
<div class="section__head section__head--center">
|
||||
<span class="nv-overline">Pricing</span>
|
||||
<h2 class="nv-heading">Per seat, not per question.</h2>
|
||||
<p class="nv-prose">
|
||||
Usage limits make people ration the tool that is supposed to save them
|
||||
time.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="pricing">
|
||||
<article class="nv-card">
|
||||
<div class="nv-card__body">
|
||||
<h3 class="nv-title">Starter</h3>
|
||||
<p class="nv-caption">For a single repository.</p>
|
||||
<p class="price">
|
||||
<span class="price__amount">$0</span>
|
||||
<span class="nv-caption">forever</span>
|
||||
</p>
|
||||
<ul class="plan-features nv-caption">
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> One repository</li>
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Community support</li>
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Read-only access</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nv-card__footer">
|
||||
<button class="nv-button nv-button--block">Start free</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--floating">
|
||||
<div class="nv-card__body">
|
||||
<div class="nv-cluster nv-cluster--between">
|
||||
<h3 class="nv-title">Team</h3>
|
||||
<span class="nv-badge nv-badge--accent nv-badge--solid">Most chosen</span>
|
||||
</div>
|
||||
<p class="nv-caption">For a codebase several people touch.</p>
|
||||
<p class="price">
|
||||
<span class="price__amount">$24</span>
|
||||
<span class="nv-caption">per seat / month</span>
|
||||
</p>
|
||||
<ul class="plan-features nv-caption">
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Unlimited repositories</li>
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Pull request proposals</li>
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Shared context across the team</li>
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> SSO and audit log</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nv-card__footer">
|
||||
<button class="nv-button nv-button--primary nv-button--block">Start 14-day trial</button>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card">
|
||||
<div class="nv-card__body">
|
||||
<h3 class="nv-title">Enterprise</h3>
|
||||
<p class="nv-caption">For code that cannot leave your network.</p>
|
||||
<p class="price">
|
||||
<span class="price__amount">Talk</span>
|
||||
<span class="nv-caption">to us</span>
|
||||
</p>
|
||||
<ul class="plan-features nv-caption">
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Self-hosted or VPC</li>
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Custom retention</li>
|
||||
<li><svg viewBox="0 0 24 24" fill="none"><path d="M5 13l4 4L19 7" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"/></svg> Named support engineer</li>
|
||||
</ul>
|
||||
</div>
|
||||
<div class="nv-card__footer">
|
||||
<button class="nv-button nv-button--block">Contact sales</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<section class="shell section" id="faq">
|
||||
<div class="section__head">
|
||||
<span class="nv-overline">Questions</span>
|
||||
<h2 class="nv-heading">Before you ask.</h2>
|
||||
</div>
|
||||
|
||||
<div class="nv-disclosure-group" style="max-inline-size: var(--nv-measure-wide)">
|
||||
<details class="nv-disclosure" open>
|
||||
<summary class="nv-disclosure__summary">
|
||||
Do you train on our code?
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="nv-disclosure__body">
|
||||
No. Repository contents are used to answer your team's questions and
|
||||
nothing else. Enterprise plans can run entirely inside your own
|
||||
network, where the question never leaves it either.
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="nv-disclosure">
|
||||
<summary class="nv-disclosure__summary">
|
||||
What happens when it is wrong?
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="nv-disclosure__body">
|
||||
Every answer cites the files it drew on, so a wrong answer is
|
||||
checkable rather than merely confident. Anything that writes is a
|
||||
proposal until a person approves it.
|
||||
</div>
|
||||
</details>
|
||||
|
||||
<details class="nv-disclosure">
|
||||
<summary class="nv-disclosure__summary">
|
||||
How large a codebase can it handle?
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="nv-disclosure__body">
|
||||
The largest repository we index in production is around eleven
|
||||
million lines. Indexing is incremental, so size affects the first
|
||||
run far more than the ones after it.
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<!-- ================================================================== -->
|
||||
<section class="shell">
|
||||
<div class="nv-bed nv-bed--vivid closing">
|
||||
<h2 class="nv-heading">Point it at a repository.</h2>
|
||||
<p class="nv-prose">
|
||||
The first answer usually arrives before the tour would have finished.
|
||||
</p>
|
||||
<div class="nv-cluster">
|
||||
<a class="nv-button nv-button--primary nv-button--lg" href="#pricing">Start free</a>
|
||||
<button class="nv-button nv-button--lg nv-button--frosted" data-nv-dialog="demo">
|
||||
Watch a demo
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="shell site-footer">
|
||||
<div class="nv-cluster nv-cluster--between">
|
||||
<span class="nv-caption">Lumen — an example interface built with Niveus.</span>
|
||||
<span class="nv-caption">Made by Nexoniarz</span>
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
</main>
|
||||
|
||||
<dialog class="nv-dialog" id="demo" aria-labelledby="demo-title">
|
||||
<div class="nv-dialog__header">
|
||||
<div>
|
||||
<h2 class="nv-dialog__title" id="demo-title">A two-minute tour</h2>
|
||||
<p class="nv-dialog__description">
|
||||
This dialog expanded from the button you pressed, and the page behind
|
||||
it is blurred rather than hidden.
|
||||
</p>
|
||||
</div>
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-dialog__close"
|
||||
aria-label="Close" data-nv-close>
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nv-dialog__body">
|
||||
<div class="nv-bed nv-bed--quiet" style="display: grid; place-items: center; min-block-size: 12rem">
|
||||
<span class="nv-caption">Where the video would be.</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-dialog__footer">
|
||||
<button class="nv-button" data-nv-close>Close</button>
|
||||
<a class="nv-button nv-button--primary" href="#pricing" data-nv-close>Start free</a>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
/* niveus.js wires the appearance buttons; the page decides what
|
||||
"remember it" means. */
|
||||
document.documentElement.addEventListener("nv:theme", (event) => {
|
||||
try { localStorage.setItem("lumen-theme", event.detail.theme); } catch {}
|
||||
});
|
||||
nv.syncThemeControls(document.documentElement.getAttribute("data-theme") || "system");
|
||||
|
||||
/* The nav's selection travels to whatever section you jump to. */
|
||||
const nav = document.querySelector("[data-nv-travel]");
|
||||
nav.addEventListener("click", (event) => {
|
||||
const item = event.target.closest(".nv-nav__item");
|
||||
if (!item || item.hasAttribute("aria-current")) return;
|
||||
nv.transition(() => {
|
||||
nav.querySelectorAll("[aria-current]").forEach((el) => el.removeAttribute("aria-current"));
|
||||
item.setAttribute("aria-current", "page");
|
||||
});
|
||||
});
|
||||
|
||||
/* The bar earns its shadow only once content has passed under it. */
|
||||
const topnav = document.getElementById("topnav");
|
||||
const onScroll = () => topnav.toggleAttribute("data-scrolled", scrollY > 8);
|
||||
addEventListener("scroll", onScroll, { passive: true });
|
||||
onScroll();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
153
docs/Website/niveus/examples/dashboard/app.css
Normal file
153
docs/Website/niveus/examples/dashboard/app.css
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/* ==========================================================================
|
||||
Northwire Ops — dashboard layout
|
||||
--------------------------------------------------------------------------
|
||||
Layout only. A data-dense view is where a design system is tested hardest:
|
||||
there are more things on screen than anywhere else, so any inconsistency in
|
||||
spacing or weight shows up immediately. Nothing here restates the language —
|
||||
even the chart draws itself in semantic colors.
|
||||
========================================================================== */
|
||||
|
||||
html, body { block-size: 100%; }
|
||||
|
||||
.app {
|
||||
display: grid;
|
||||
grid-template-columns: 15rem 1fr;
|
||||
min-block-size: 100svh;
|
||||
}
|
||||
|
||||
@media (max-width: 62rem) {
|
||||
.app { grid-template-columns: 1fr; }
|
||||
.app__sidebar { display: none; }
|
||||
}
|
||||
|
||||
.app__main {
|
||||
min-inline-size: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.app__content {
|
||||
flex: 1;
|
||||
padding: var(--nv-space-6) var(--nv-layout-gutter) var(--nv-space-12);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-6);
|
||||
max-inline-size: 80rem;
|
||||
inline-size: 100%;
|
||||
margin-inline: auto;
|
||||
}
|
||||
|
||||
.page-head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: var(--nv-space-4);
|
||||
}
|
||||
|
||||
/* --- Stat tiles -------------------------------------------------------------- */
|
||||
|
||||
.stats {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(13rem, 100%), 1fr));
|
||||
gap: var(--nv-space-4);
|
||||
}
|
||||
|
||||
.stat__value {
|
||||
display: block;
|
||||
margin-block: var(--nv-space-2) var(--nv-space-1);
|
||||
font-size: var(--nv-font-size-3xl);
|
||||
font-weight: var(--nv-font-weight-semibold);
|
||||
letter-spacing: var(--nv-letter-spacing-tighter);
|
||||
color: var(--nv-color-text-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
/* --- Chart --------------------------------------------------------------------
|
||||
Drawn in semantic colors, so it follows the brand and both appearances with
|
||||
no second palette to maintain. */
|
||||
|
||||
.chart {
|
||||
inline-size: 100%;
|
||||
block-size: 14rem;
|
||||
display: block;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
.chart__grid {
|
||||
stroke: var(--nv-color-border-subtle);
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
.chart__line {
|
||||
fill: none;
|
||||
stroke: var(--nv-color-accent);
|
||||
stroke-width: 2;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
vector-effect: non-scaling-stroke;
|
||||
}
|
||||
|
||||
.chart__line--compare {
|
||||
stroke: var(--nv-color-border-strong);
|
||||
stroke-dasharray: 4 4;
|
||||
}
|
||||
|
||||
.chart__area {
|
||||
fill: url(#chart-fill);
|
||||
}
|
||||
|
||||
.chart__labels {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
margin-block-start: var(--nv-space-2);
|
||||
}
|
||||
|
||||
/* --- Layout of the lower half --------------------------------------------------- */
|
||||
|
||||
.split {
|
||||
display: grid;
|
||||
gap: var(--nv-space-6);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (min-width: 70rem) {
|
||||
.split { grid-template-columns: 1.6fr 1fr; }
|
||||
}
|
||||
|
||||
.activity {
|
||||
list-style: none;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.activity li {
|
||||
display: flex;
|
||||
gap: var(--nv-space-3);
|
||||
padding-block: var(--nv-space-3);
|
||||
border-block-end: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
|
||||
.activity li:last-child { border-block-end: 0; }
|
||||
|
||||
.activity__mark {
|
||||
inline-size: var(--nv-space-2);
|
||||
block-size: var(--nv-space-2);
|
||||
border-radius: var(--nv-radius-full);
|
||||
margin-block-start: 0.45em;
|
||||
flex: none;
|
||||
background-color: var(--nv-color-border-strong);
|
||||
}
|
||||
|
||||
.activity__mark--danger { background-color: var(--nv-color-danger); }
|
||||
.activity__mark--success { background-color: var(--nv-color-success); }
|
||||
.activity__mark--accent { background-color: var(--nv-color-accent); }
|
||||
.activity__mark--warning { background-color: var(--nv-color-warning); }
|
||||
|
||||
.service-name {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: var(--nv-space-2-5);
|
||||
}
|
||||
578
docs/Website/niveus/examples/dashboard/index.html
Normal file
578
docs/Website/niveus/examples/dashboard/index.html
Normal file
|
|
@ -0,0 +1,578 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Northwire Ops — a dashboard built with Niveus</title>
|
||||
|
||||
<link rel="stylesheet" href="../../dist/niveus.css">
|
||||
<script src="../../niveus/niveus.js" defer></script>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
|
||||
<script>
|
||||
try {
|
||||
var saved = localStorage.getItem("ops-theme");
|
||||
if (saved && saved !== "system") document.documentElement.setAttribute("data-theme", saved);
|
||||
} catch (error) {}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
:root { --nv-brand: #0d9488; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div class="app">
|
||||
|
||||
<aside class="nv-nav nv-nav--vertical app__sidebar" aria-label="Sections"
|
||||
style="--nv-selection-name: nv-ops-sidebar">
|
||||
<a class="nv-nav__brand" href="#">
|
||||
<svg class="nv-nav__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M4 18V9M10 18V5M16 18v-6M22 18H2" stroke="currentColor"
|
||||
stroke-width="1.8" stroke-linecap="round"/>
|
||||
</svg>
|
||||
Northwire Ops
|
||||
</a>
|
||||
|
||||
<span class="nv-nav__section">Monitor</span>
|
||||
<ul class="nv-nav__list" data-nv-travel="local">
|
||||
<li><a class="nv-nav__item" href="#" aria-current="page">Overview</a></li>
|
||||
<li><a class="nv-nav__item" href="#">Services <span class="nv-nav__badge">6</span></a></li>
|
||||
<li><a class="nv-nav__item" href="#">Incidents <span class="nv-nav__badge">1</span></a></li>
|
||||
<li><a class="nv-nav__item" href="#">Traces</a></li>
|
||||
</ul>
|
||||
|
||||
<span class="nv-nav__section">Manage</span>
|
||||
<ul class="nv-nav__list">
|
||||
<li><a class="nv-nav__item" href="#">Deployments</a></li>
|
||||
<li><a class="nv-nav__item" href="#">On-call</a></li>
|
||||
<li><span class="nv-nav__item" aria-disabled="true">Cost (soon)</span></li>
|
||||
</ul>
|
||||
|
||||
<span class="nv-nav__spacer"></span>
|
||||
|
||||
<div class="nv-bed nv-bed--quiet nv-bed--single" style="padding: var(--nv-space-4)">
|
||||
<p class="nv-label">Retention</p>
|
||||
<p class="nv-caption" style="margin-block: var(--nv-space-1) var(--nv-space-3)">
|
||||
14 of 30 days used.
|
||||
</p>
|
||||
<button class="nv-button nv-button--sm nv-button--primary nv-button--block">Upgrade plan</button>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<main class="app__main">
|
||||
|
||||
<nav class="nv-nav nv-nav--static" aria-label="Toolbar">
|
||||
<ol class="nv-breadcrumbs">
|
||||
<li><a href="#">Northwire</a></li>
|
||||
<li><a href="#">Platform</a></li>
|
||||
<li aria-current="page">Overview</li>
|
||||
</ol>
|
||||
|
||||
<span class="nv-nav__spacer"></span>
|
||||
|
||||
<button class="nv-button nv-button--sm nv-menu-trigger" popovertarget="range-menu"
|
||||
style="--nv-anchor: --range">
|
||||
<span id="range-label">Last 24 hours</span>
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"
|
||||
style="margin-inline-start: var(--nv-space-1)"></span>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="range-menu" style="--nv-anchor: --range">
|
||||
<button class="nv-menu__item" aria-checked="true" data-range="Last 24 hours">Last 24 hours</button>
|
||||
<button class="nv-menu__item" data-range="Last 7 days">Last 7 days</button>
|
||||
<button class="nv-menu__item" data-range="Last 30 days">Last 30 days</button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item" data-range="Custom range">Custom range…</button>
|
||||
</div>
|
||||
|
||||
<button class="nv-button nv-button--sm nv-button--ghost">Export</button>
|
||||
|
||||
<div class="nv-button-group" role="group" aria-label="Appearance">
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="light" aria-pressed="false">Light</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="dark" aria-pressed="false">Dark</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="system" aria-pressed="true">Auto</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="app__content">
|
||||
|
||||
<div class="page-head">
|
||||
<div>
|
||||
<h1 class="nv-heading">Platform overview</h1>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">
|
||||
Six services · updated 40 seconds ago
|
||||
</p>
|
||||
</div>
|
||||
<div class="nv-cluster">
|
||||
<span class="nv-badge nv-badge--danger"><span class="nv-badge__dot"></span>1 incident open</span>
|
||||
<button class="nv-button nv-button--sm nv-button--primary" data-nv-dialog="incident">
|
||||
Review incident
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Stat tiles ------------------------------------------------------ -->
|
||||
<div class="stats">
|
||||
<article class="nv-card"><div class="nv-card__body">
|
||||
<span class="nv-overline">Requests</span>
|
||||
<span class="stat__value">14.2k</span>
|
||||
<span class="nv-badge nv-badge--success">+6.1% vs yesterday</span>
|
||||
</div></article>
|
||||
|
||||
<article class="nv-card"><div class="nv-card__body">
|
||||
<span class="nv-overline">Error rate</span>
|
||||
<span class="stat__value">0.42%</span>
|
||||
<span class="nv-badge nv-badge--danger">+0.29 pts</span>
|
||||
</div></article>
|
||||
|
||||
<article class="nv-card"><div class="nv-card__body">
|
||||
<span class="nv-overline">p95 latency</span>
|
||||
<span class="stat__value">318 ms</span>
|
||||
<span class="nv-badge nv-badge--warning">+118%</span>
|
||||
</div></article>
|
||||
|
||||
<article class="nv-card"><div class="nv-card__body">
|
||||
<span class="nv-overline">Uptime</span>
|
||||
<span class="stat__value">99.71%</span>
|
||||
<span class="nv-badge">30-day window</span>
|
||||
</div></article>
|
||||
</div>
|
||||
|
||||
<!-- Chart ------------------------------------------------------------ -->
|
||||
<article class="nv-card">
|
||||
<div class="nv-card__header">
|
||||
<div>
|
||||
<h2 class="nv-card__title">p95 latency</h2>
|
||||
<p class="nv-card__subtitle">Solid: today · dashed: same day last week</p>
|
||||
</div>
|
||||
<span class="nv-badge nv-badge--warning">Regression at 14:00</span>
|
||||
</div>
|
||||
|
||||
<div class="nv-card__body">
|
||||
<svg class="chart" viewBox="0 0 600 200" preserveAspectRatio="none"
|
||||
role="img" aria-label="p95 latency over 24 hours, rising sharply after 14:00">
|
||||
<defs>
|
||||
<linearGradient id="chart-fill" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0%" stop-color="var(--nv-color-accent)" stop-opacity="0.28"/>
|
||||
<stop offset="100%" stop-color="var(--nv-color-accent)" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
|
||||
<g class="chart__grid">
|
||||
<line x1="0" y1="40" x2="600" y2="40"/>
|
||||
<line x1="0" y1="90" x2="600" y2="90"/>
|
||||
<line x1="0" y1="140" x2="600" y2="140"/>
|
||||
<line x1="0" y1="190" x2="600" y2="190"/>
|
||||
</g>
|
||||
|
||||
<path class="chart__area"
|
||||
d="M0,150 L60,146 L120,152 L180,143 L240,148 L300,140 L360,132 L420,60 L480,44 L540,52 L600,40 L600,190 L0,190 Z"/>
|
||||
<polyline class="chart__line"
|
||||
points="0,150 60,146 120,152 180,143 240,148 300,140 360,132 420,60 480,44 540,52 600,40"/>
|
||||
<polyline class="chart__line chart__line--compare"
|
||||
points="0,152 60,149 120,151 180,146 240,150 300,145 360,148 420,143 480,147 540,141 600,145"/>
|
||||
</svg>
|
||||
|
||||
<div class="chart__labels nv-caption">
|
||||
<span>00:00</span><span>06:00</span><span>12:00</span><span>18:00</span><span>now</span>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<!-- Services and activity -------------------------------------------- -->
|
||||
<div class="split">
|
||||
<article class="nv-card nv-card--flush">
|
||||
<div class="nv-card__header" style="padding: var(--nv-space-5)">
|
||||
<div>
|
||||
<h2 class="nv-card__title">Services</h2>
|
||||
<p class="nv-card__subtitle">Health across the platform</p>
|
||||
</div>
|
||||
<div class="nv-nav-tabs" role="tablist" data-nv-tabs aria-label="Service view"
|
||||
style="--nv-selection-name: nv-ops-tabs">
|
||||
<button class="nv-nav__item" role="tab" id="tab-all"
|
||||
aria-controls="panel-all" aria-selected="true">All</button>
|
||||
<button class="nv-nav__item" role="tab" id="tab-attention"
|
||||
aria-controls="panel-attention" aria-selected="false" tabindex="-1">Needs attention</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nv-tab-panel" id="panel-all" role="tabpanel" aria-labelledby="tab-all">
|
||||
<div class="nv-table-frame">
|
||||
<table class="nv-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Service</th>
|
||||
<th scope="col" class="nv-table__number">Uptime</th>
|
||||
<th scope="col" class="nv-table__number">p95</th>
|
||||
<th scope="col" class="nv-table__number">req/min</th>
|
||||
<th scope="col"><span class="nv-visually-hidden">Actions</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>Healthy</span>
|
||||
Checkout API
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">99.98%</td>
|
||||
<td class="nv-table__number">142 ms</td>
|
||||
<td class="nv-table__number">2.4k</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-1" style="--nv-anchor: --svc1" aria-label="Options for Checkout API">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-1" style="--nv-anchor: --svc1">
|
||||
<span class="nv-menu__label">Checkout API</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--warning"><span class="nv-badge__dot"></span>Degraded</span>
|
||||
Pricing
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">99.71%</td>
|
||||
<td class="nv-table__number">318 ms</td>
|
||||
<td class="nv-table__number">1.9k</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-2" style="--nv-anchor: --svc2" aria-label="Options for Pricing">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-2" style="--nv-anchor: --svc2">
|
||||
<span class="nv-menu__label">Pricing</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>Healthy</span>
|
||||
Identity
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">99.99%</td>
|
||||
<td class="nv-table__number">88 ms</td>
|
||||
<td class="nv-table__number">6.1k</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-3" style="--nv-anchor: --svc3" aria-label="Options for Identity">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-3" style="--nv-anchor: --svc3">
|
||||
<span class="nv-menu__label">Identity</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>Healthy</span>
|
||||
Search
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">99.95%</td>
|
||||
<td class="nv-table__number">204 ms</td>
|
||||
<td class="nv-table__number">3.3k</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-4" style="--nv-anchor: --svc4" aria-label="Options for Search">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-4" style="--nv-anchor: --svc4">
|
||||
<span class="nv-menu__label">Search</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--danger"><span class="nv-badge__dot"></span>Failing</span>
|
||||
Notifications
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">97.20%</td>
|
||||
<td class="nv-table__number">—</td>
|
||||
<td class="nv-table__number">0</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-5" style="--nv-anchor: --svc5" aria-label="Options for Notifications">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-5" style="--nv-anchor: --svc5">
|
||||
<span class="nv-menu__label">Notifications</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>Healthy</span>
|
||||
Reporting
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">99.90%</td>
|
||||
<td class="nv-table__number">512 ms</td>
|
||||
<td class="nv-table__number">410</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-6" style="--nv-anchor: --svc6" aria-label="Options for Reporting">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-6" style="--nv-anchor: --svc6">
|
||||
<span class="nv-menu__label">Reporting</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nv-tab-panel" id="panel-attention" role="tabpanel"
|
||||
aria-labelledby="tab-attention" hidden>
|
||||
<div class="nv-table-frame">
|
||||
<table class="nv-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Service</th>
|
||||
<th scope="col" class="nv-table__number">Uptime</th>
|
||||
<th scope="col" class="nv-table__number">p95</th>
|
||||
<th scope="col" class="nv-table__number">req/min</th>
|
||||
<th scope="col"><span class="nv-visually-hidden">Actions</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--warning"><span class="nv-badge__dot"></span>Degraded</span>
|
||||
Pricing
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">99.71%</td>
|
||||
<td class="nv-table__number">318 ms</td>
|
||||
<td class="nv-table__number">1.9k</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-2" style="--nv-anchor: --svc2" aria-label="Options for Pricing">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-2" style="--nv-anchor: --svc2">
|
||||
<span class="nv-menu__label">Pricing</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<span class="service-name">
|
||||
<span class="nv-badge nv-badge--danger"><span class="nv-badge__dot"></span>Failing</span>
|
||||
Notifications
|
||||
</span>
|
||||
</td>
|
||||
<td class="nv-table__number">97.20%</td>
|
||||
<td class="nv-table__number">—</td>
|
||||
<td class="nv-table__number">0</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm nv-menu-trigger"
|
||||
popovertarget="svc-menu-5" style="--nv-anchor: --svc5" aria-label="Options for Notifications">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
|
||||
<circle cx="5" cy="12" r="1.6"/><circle cx="12" cy="12" r="1.6"/><circle cx="19" cy="12" r="1.6"/>
|
||||
</svg>
|
||||
</button>
|
||||
<div class="nv-menu" popover id="svc-menu-5" style="--nv-anchor: --svc5">
|
||||
<span class="nv-menu__label">Notifications</span>
|
||||
<button class="nv-menu__item">Open dashboard</button>
|
||||
<button class="nv-menu__item">View logs<span class="nv-menu__shortcut">L</span></button>
|
||||
<hr class="nv-menu__separator">
|
||||
<button class="nv-menu__item nv-menu__item--danger">Restart service</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="nv-stack">
|
||||
<article class="nv-card">
|
||||
<div class="nv-card__header">
|
||||
<div><h2 class="nv-card__title">Activity</h2></div>
|
||||
</div>
|
||||
<div class="nv-card__body" style="padding-block-start: 0">
|
||||
<ul class="activity nv-caption">
|
||||
<li>
|
||||
<span class="activity__mark activity__mark--danger"></span>
|
||||
<span><strong class="nv-text-primary">Notifications</strong> stopped responding
|
||||
<span style="display: block">14:12 · alert #881</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="activity__mark activity__mark--warning"></span>
|
||||
<span><strong class="nv-text-primary">Pricing</strong> p95 crossed 300 ms
|
||||
<span style="display: block">14:04 · alert #880</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="activity__mark activity__mark--accent"></span>
|
||||
<span><strong class="nv-text-primary">#4412</strong> deployed to production
|
||||
<span style="display: block">13:58 · by robin</span></span>
|
||||
</li>
|
||||
<li>
|
||||
<span class="activity__mark activity__mark--success"></span>
|
||||
<span><strong class="nv-text-primary">Search</strong> recovered
|
||||
<span style="display: block">09:31 · alert #879</span></span>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<div class="nv-disclosure-group">
|
||||
<details class="nv-disclosure" open>
|
||||
<summary class="nv-disclosure__summary">
|
||||
What changed at 14:00?
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="nv-disclosure__body">
|
||||
One deploy landed in the window: <code class="nv-code">#4412</code>,
|
||||
which moved tax resolution inside the cart loop.
|
||||
</div>
|
||||
</details>
|
||||
<details class="nv-disclosure">
|
||||
<summary class="nv-disclosure__summary">
|
||||
Who is on call?
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"></span>
|
||||
</summary>
|
||||
<div class="nv-disclosure__body">
|
||||
Robin Kade until 18:00, then Sam Oyelaran. Escalation goes to
|
||||
the platform channel after 10 minutes.
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="nv-cluster nv-cluster--between" style="padding-block-start: var(--nv-space-6)">
|
||||
<span class="nv-caption">Northwire Ops — an example interface built with Niveus.</span>
|
||||
<span class="nv-caption">Made by Nexoniarz</span>
|
||||
</footer>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
|
||||
<dialog class="nv-dialog" id="incident" aria-labelledby="incident-title">
|
||||
<div class="nv-dialog__header">
|
||||
<div>
|
||||
<h2 class="nv-dialog__title" id="incident-title">Incident #881</h2>
|
||||
<p class="nv-dialog__description">Notifications stopped responding at 14:12.</p>
|
||||
</div>
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-dialog__close"
|
||||
aria-label="Close" data-nv-close>
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
<div class="nv-dialog__body">
|
||||
<div class="nv-stack nv-stack--tight">
|
||||
<div class="nv-cluster nv-cluster--tight">
|
||||
<span class="nv-badge nv-badge--danger nv-badge--solid">Sev 2</span>
|
||||
<span class="nv-badge">27 minutes open</span>
|
||||
<span class="nv-badge">Robin Kade</span>
|
||||
</div>
|
||||
<p class="nv-body">
|
||||
The queue consumer exited after the pricing service began timing out,
|
||||
and did not restart because the health check still reported ready.
|
||||
</p>
|
||||
<div class="nv-field">
|
||||
<label class="nv-field__label" for="note">Add a note</label>
|
||||
<textarea class="nv-input" id="note" rows="3"
|
||||
placeholder="What did you find?"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-dialog__footer nv-dialog__footer--divided">
|
||||
<button class="nv-button" data-nv-close>Close</button>
|
||||
<button class="nv-button nv-button--danger">Page the team</button>
|
||||
<button class="nv-button nv-button--primary" data-nv-close>Acknowledge</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.documentElement.addEventListener("nv:theme", (event) => {
|
||||
try { localStorage.setItem("ops-theme", event.detail.theme); } catch {}
|
||||
});
|
||||
nv.syncThemeControls(document.documentElement.getAttribute("data-theme") || "system");
|
||||
|
||||
const sidebar = document.querySelector('[data-nv-travel="local"]');
|
||||
sidebar.addEventListener("click", (event) => {
|
||||
const item = event.target.closest(".nv-nav__item");
|
||||
if (!item || item.hasAttribute("aria-current")) return;
|
||||
event.preventDefault();
|
||||
nv.transition(() => {
|
||||
sidebar.querySelectorAll("[aria-current]").forEach((el) => el.removeAttribute("aria-current"));
|
||||
item.setAttribute("aria-current", "page");
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-range]").forEach((option) => {
|
||||
option.addEventListener("click", () => {
|
||||
document.querySelectorAll("[data-range]").forEach((other) => {
|
||||
other.setAttribute("aria-checked", String(other === option));
|
||||
});
|
||||
document.getElementById("range-label").textContent = option.dataset.range;
|
||||
document.getElementById("range-menu").hidePopover();
|
||||
});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
251
docs/Website/niveus/examples/index.html
Normal file
251
docs/Website/niveus/examples/index.html
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Niveus — examples</title>
|
||||
|
||||
<link rel="stylesheet" href="../dist/niveus.css">
|
||||
<script src="../niveus/niveus.js" defer></script>
|
||||
|
||||
<script>
|
||||
try {
|
||||
var saved = localStorage.getItem("niveus-theme");
|
||||
if (saved && saved !== "system") document.documentElement.setAttribute("data-theme", saved);
|
||||
} catch (error) {}
|
||||
</script>
|
||||
|
||||
<style>
|
||||
/* Layout only — see each example's app.css for the same discipline. */
|
||||
.shell {
|
||||
max-inline-size: 64rem;
|
||||
margin-inline: auto;
|
||||
padding: var(--nv-space-10) var(--nv-layout-gutter) var(--nv-space-20);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-10);
|
||||
}
|
||||
|
||||
.intro {
|
||||
max-inline-size: var(--nv-measure);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-4);
|
||||
}
|
||||
|
||||
.examples {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(min(19rem, 100%), 1fr));
|
||||
gap: var(--nv-space-5);
|
||||
}
|
||||
|
||||
/* Each card previews its own brand by re-deriving the set for itself. */
|
||||
.swatches {
|
||||
display: flex;
|
||||
gap: var(--nv-space-1-5);
|
||||
margin-block-start: var(--nv-space-1);
|
||||
}
|
||||
|
||||
.swatches span {
|
||||
inline-size: var(--nv-space-5);
|
||||
block-size: var(--nv-space-5);
|
||||
border-radius: var(--nv-radius-xs);
|
||||
border: var(--nv-border-width) solid var(--nv-color-border);
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
padding-block-start: var(--nv-space-8);
|
||||
border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="nv-nav" aria-label="Main">
|
||||
<a class="nv-nav__brand" href="../index.html">
|
||||
<svg class="nv-nav__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<rect x="3" y="3" width="18" height="18" rx="6" stroke="currentColor" stroke-width="1.6"/>
|
||||
<path d="M8 15.5V8.5l8 7v-7" stroke="currentColor" stroke-width="1.6"
|
||||
stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Niveus
|
||||
</a>
|
||||
<ul class="nv-nav__list">
|
||||
<li><a class="nv-nav__item" href="../index.html">Specimen</a></li>
|
||||
<li><span class="nv-nav__item" aria-current="page">Examples</span></li>
|
||||
<li><a class="nv-nav__item" href="../REFERENCE.md">Reference</a></li>
|
||||
</ul>
|
||||
<span class="nv-nav__spacer"></span>
|
||||
<div class="nv-button-group" role="group" aria-label="Appearance">
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="light" aria-pressed="false">Light</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="dark" aria-pressed="false">Dark</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="system" aria-pressed="true">Auto</button>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<main class="shell">
|
||||
|
||||
<header class="intro">
|
||||
<span class="nv-overline">Examples</span>
|
||||
<h1 class="nv-display">Four products, one system.</h1>
|
||||
<p class="nv-prose">
|
||||
Each of these is a complete interface built from Niveus and nothing else.
|
||||
They look like different products because they are — but none of them
|
||||
overrides a component, forks a token, or writes a color. What differs is
|
||||
one declaration of <code class="nv-code">--nv-brand</code>, and the
|
||||
layout each one needs for its own content.
|
||||
</p>
|
||||
<p class="nv-prose">
|
||||
Open any <code class="nv-code">app.css</code> beside them: it contains
|
||||
grids, widths and breakpoints, and not a single hex value. That split —
|
||||
the system owns the language, the application owns the layout — is the
|
||||
thing these examples are really demonstrating.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
<div class="examples">
|
||||
|
||||
<article class="nv-card nv-card--interactive" data-nv-brand style="--nv-brand: #6d5efc">
|
||||
<div class="nv-card__header">
|
||||
<div>
|
||||
<h2 class="nv-card__title">
|
||||
<a class="nv-card__link" href="ai-landing/index.html"
|
||||
style="color: inherit; text-decoration: none">Lumen</a>
|
||||
</h2>
|
||||
<p class="nv-card__subtitle">AI product landing page</p>
|
||||
</div>
|
||||
<span class="nv-badge nv-badge--accent">Marketing</span>
|
||||
</div>
|
||||
<div class="nv-card__body">
|
||||
Hero, feature cards, a scrolling row, pricing and an FAQ. Shows the
|
||||
bed as a showcase surface and the disclosure group.
|
||||
<div class="swatches" aria-hidden="true">
|
||||
<span style="background: var(--nv-color-accent)"></span>
|
||||
<span style="background: var(--nv-color-accent-subtle)"></span>
|
||||
<span style="background: var(--nv-color-surface)"></span>
|
||||
<span style="background: var(--nv-color-background)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer">
|
||||
<span class="nv-caption nv-code">--nv-brand: #6d5efc</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--interactive" data-nv-brand style="--nv-brand: #6d5efc">
|
||||
<div class="nv-card__header">
|
||||
<div>
|
||||
<h2 class="nv-card__title">
|
||||
<a class="nv-card__link" href="ai-chat/index.html"
|
||||
style="color: inherit; text-decoration: none">Lumen Chat</a>
|
||||
</h2>
|
||||
<p class="nv-card__subtitle">AI chat application</p>
|
||||
</div>
|
||||
<span class="nv-badge nv-badge--accent">Web app</span>
|
||||
</div>
|
||||
<div class="nv-card__body">
|
||||
A full app shell: sidebar with travelling selection, a transcript that
|
||||
cross-fades between threads, a composer, and settings in a dialog with
|
||||
tabs.
|
||||
<div class="swatches" aria-hidden="true">
|
||||
<span style="background: var(--nv-color-accent)"></span>
|
||||
<span style="background: var(--nv-color-accent-subtle)"></span>
|
||||
<span style="background: var(--nv-color-surface)"></span>
|
||||
<span style="background: var(--nv-color-background)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer">
|
||||
<span class="nv-caption nv-code">--nv-brand: #6d5efc</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--interactive" data-nv-brand style="--nv-brand: #ea580c">
|
||||
<div class="nv-card__header">
|
||||
<div>
|
||||
<h2 class="nv-card__title">
|
||||
<a class="nv-card__link" href="it-shop/index.html"
|
||||
style="color: inherit; text-decoration: none">Northwire</a>
|
||||
</h2>
|
||||
<p class="nv-card__subtitle">IT hardware shop</p>
|
||||
</div>
|
||||
<span class="nv-badge nv-badge--accent">Commerce</span>
|
||||
</div>
|
||||
<div class="nv-card__body">
|
||||
Filters, a product grid, stock states in the status colors, and a
|
||||
basket in a dialog with a table. Same components, warm brand.
|
||||
<div class="swatches" aria-hidden="true">
|
||||
<span style="background: var(--nv-color-accent)"></span>
|
||||
<span style="background: var(--nv-color-accent-subtle)"></span>
|
||||
<span style="background: var(--nv-color-surface)"></span>
|
||||
<span style="background: var(--nv-color-background)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer">
|
||||
<span class="nv-caption nv-code">--nv-brand: #ea580c</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="nv-card nv-card--interactive" data-nv-brand style="--nv-brand: #0d9488">
|
||||
<div class="nv-card__header">
|
||||
<div>
|
||||
<h2 class="nv-card__title">
|
||||
<a class="nv-card__link" href="dashboard/index.html"
|
||||
style="color: inherit; text-decoration: none">Northwire Ops</a>
|
||||
</h2>
|
||||
<p class="nv-card__subtitle">Monitoring dashboard</p>
|
||||
</div>
|
||||
<span class="nv-badge nv-badge--accent">Data app</span>
|
||||
</div>
|
||||
<div class="nv-card__body">
|
||||
Stat tiles, a chart drawn in semantic colors, a service table with
|
||||
per-row menus, and tabs that filter it. The densest test of the system.
|
||||
<div class="swatches" aria-hidden="true">
|
||||
<span style="background: var(--nv-color-accent)"></span>
|
||||
<span style="background: var(--nv-color-accent-subtle)"></span>
|
||||
<span style="background: var(--nv-color-surface)"></span>
|
||||
<span style="background: var(--nv-color-background)"></span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer">
|
||||
<span class="nv-caption nv-code">--nv-brand: #0d9488</span>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="nv-bed">
|
||||
<span class="nv-bed__label">How to read these</span>
|
||||
<div class="nv-stack" style="max-inline-size: var(--nv-measure)">
|
||||
<p class="nv-body">
|
||||
Every example links exactly two files from the system —
|
||||
<code class="nv-code">dist/niveus.css</code> and the optional
|
||||
<code class="nv-code">niveus/niveus.js</code> — then its own
|
||||
<code class="nv-code">app.css</code> for layout. Nothing else is shared
|
||||
between them.
|
||||
</p>
|
||||
<p class="nv-body">
|
||||
Where one of them needed something the system did not have, the answer
|
||||
was to add it to the system, not to the page: the badge and table
|
||||
components exist because these examples asked for them.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<footer class="site-footer">
|
||||
<div class="nv-cluster nv-cluster--between">
|
||||
<span class="nv-caption">Niveus — define the system once, implement it everywhere.</span>
|
||||
<span class="nv-caption">Made by Nexoniarz</span>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.documentElement.addEventListener("nv:theme", (event) => {
|
||||
try { localStorage.setItem("niveus-theme", event.detail.theme); } catch {}
|
||||
});
|
||||
nv.syncThemeControls(document.documentElement.getAttribute("data-theme") || "system");
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
145
docs/Website/niveus/examples/it-shop/app.css
Normal file
145
docs/Website/niveus/examples/it-shop/app.css
Normal file
|
|
@ -0,0 +1,145 @@
|
|||
/* ==========================================================================
|
||||
Northwire — shop layout
|
||||
--------------------------------------------------------------------------
|
||||
Layout only. Note what this shop did *not* need in order to look like a
|
||||
different product from the AI site next door: a second stylesheet, an
|
||||
override file, or a single color. It set --nv-brand and kept going.
|
||||
========================================================================== */
|
||||
|
||||
.shell {
|
||||
max-inline-size: var(--nv-layout-max-width);
|
||||
margin-inline: auto;
|
||||
padding-inline: var(--nv-layout-gutter);
|
||||
}
|
||||
|
||||
.section {
|
||||
padding-block: var(--nv-space-10);
|
||||
}
|
||||
|
||||
.section__head {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: var(--nv-space-4);
|
||||
margin-block-end: var(--nv-space-6);
|
||||
}
|
||||
|
||||
/* --- Search sits inside the bar, so it must not grow forever --------------- */
|
||||
|
||||
.search {
|
||||
flex: 1;
|
||||
max-inline-size: 26rem;
|
||||
min-inline-size: 8rem;
|
||||
}
|
||||
|
||||
/* --- Catalogue -------------------------------------------------------------- */
|
||||
|
||||
.catalogue {
|
||||
display: grid;
|
||||
gap: var(--nv-space-8);
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
@media (min-width: 60rem) {
|
||||
.catalogue {
|
||||
grid-template-columns: 15rem 1fr;
|
||||
}
|
||||
}
|
||||
|
||||
.filters {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-6);
|
||||
position: sticky;
|
||||
inset-block-start: calc(var(--nv-control-height-lg) + var(--nv-space-6));
|
||||
}
|
||||
|
||||
.filter-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-2-5);
|
||||
}
|
||||
|
||||
.products {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(min(15rem, 100%), 1fr));
|
||||
gap: var(--nv-space-4);
|
||||
}
|
||||
|
||||
/* --- Product ------------------------------------------------------------------
|
||||
The illustration tile is a bed: it gives the line art a ground that is
|
||||
visibly not the card, without inventing a colour for it. */
|
||||
|
||||
.product__art {
|
||||
display: grid;
|
||||
place-items: center;
|
||||
aspect-ratio: 4 / 3;
|
||||
border-radius: 0;
|
||||
border-inline: 0;
|
||||
border-block-start: 0;
|
||||
padding: var(--nv-space-4);
|
||||
}
|
||||
|
||||
.product__art svg {
|
||||
inline-size: 60%;
|
||||
max-inline-size: 8rem;
|
||||
color: var(--nv-color-text-tertiary);
|
||||
}
|
||||
|
||||
.product__price {
|
||||
font-size: var(--nv-font-size-lg);
|
||||
font-weight: var(--nv-font-weight-semibold);
|
||||
color: var(--nv-color-text-primary);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.product__was {
|
||||
font: var(--nv-font-caption);
|
||||
color: var(--nv-color-text-tertiary);
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
/* --- Cart ---------------------------------------------------------------------- */
|
||||
|
||||
.cart-line__art {
|
||||
inline-size: var(--nv-space-10);
|
||||
block-size: var(--nv-space-10);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border-radius: var(--nv-radius-sm);
|
||||
background-color: var(--nv-color-surface-inset);
|
||||
box-shadow: var(--nv-elevation-inset-sm);
|
||||
color: var(--nv-color-text-tertiary);
|
||||
}
|
||||
|
||||
.cart-line__art svg {
|
||||
inline-size: 60%;
|
||||
}
|
||||
|
||||
.cart-totals {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: var(--nv-space-2);
|
||||
padding-block-start: var(--nv-space-4);
|
||||
}
|
||||
|
||||
.cart-totals__row {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: var(--nv-space-4);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.cart-totals__row--due {
|
||||
padding-block-start: var(--nv-space-3);
|
||||
border-block-start: var(--nv-border-width) solid var(--nv-color-border);
|
||||
font-size: var(--nv-font-size-lg);
|
||||
font-weight: var(--nv-font-weight-semibold);
|
||||
color: var(--nv-color-text-primary);
|
||||
}
|
||||
|
||||
.site-footer {
|
||||
padding-block: var(--nv-space-8) var(--nv-space-12);
|
||||
border-block-start: var(--nv-border-width) solid var(--nv-color-border-subtle);
|
||||
}
|
||||
519
docs/Website/niveus/examples/it-shop/index.html
Normal file
519
docs/Website/niveus/examples/it-shop/index.html
Normal file
|
|
@ -0,0 +1,519 @@
|
|||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Northwire — an IT shop built with Niveus</title>
|
||||
|
||||
<link rel="stylesheet" href="../../dist/niveus.css">
|
||||
<script src="../../niveus/niveus.js" defer></script>
|
||||
<link rel="stylesheet" href="app.css">
|
||||
|
||||
<script>
|
||||
try {
|
||||
var saved = localStorage.getItem("northwire-theme");
|
||||
if (saved && saved !== "system") document.documentElement.setAttribute("data-theme", saved);
|
||||
} catch (error) {}
|
||||
</script>
|
||||
|
||||
<!-- A shop, not an AI product. One declaration is the whole difference. -->
|
||||
<style>
|
||||
:root { --nv-brand: #ea580c; }
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<nav class="nv-nav" id="topnav" aria-label="Main" style="--nv-selection-name: nv-shop-nav">
|
||||
<a class="nv-nav__brand" href="#top">
|
||||
<svg class="nv-nav__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M4 7h16l-1.5 12h-13z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/>
|
||||
<path d="M9 7V5.5a3 3 0 016 0V7" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
|
||||
</svg>
|
||||
Northwire
|
||||
</a>
|
||||
|
||||
<div class="nv-input-group search">
|
||||
<span class="nv-input-group__affix">
|
||||
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true" style="inline-size: 1em; block-size: 1em">
|
||||
<circle cx="11" cy="11" r="6.5" stroke="currentColor" stroke-width="2"/>
|
||||
<path d="M16 16l4 4" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</span>
|
||||
<label class="nv-visually-hidden" for="search">Search products</label>
|
||||
<input class="nv-input nv-input--sm" id="search" type="search" placeholder="Search 4,200 products…">
|
||||
</div>
|
||||
|
||||
<span class="nv-nav__spacer"></span>
|
||||
|
||||
<div class="nv-button-group" role="group" aria-label="Appearance">
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="light" aria-pressed="false">Light</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="dark" aria-pressed="false">Dark</button>
|
||||
<button class="nv-button nv-button--sm" data-nv-theme="system" aria-pressed="true">Auto</button>
|
||||
</div>
|
||||
|
||||
<button class="nv-button nv-button--sm nv-button--primary" data-nv-dialog="basket">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M4 7h16l-1.5 12h-13z" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
Basket
|
||||
<span class="nv-badge nv-badge--count nv-badge--solid" id="basket-count">3</span>
|
||||
</button>
|
||||
</nav>
|
||||
|
||||
<main id="top">
|
||||
|
||||
<section class="shell section">
|
||||
<div class="nv-bed nv-bed--single">
|
||||
<div class="nv-cluster nv-cluster--between">
|
||||
<div style="max-inline-size: var(--nv-measure)">
|
||||
<span class="nv-overline">Clearance</span>
|
||||
<h1 class="nv-heading" style="margin-block: var(--nv-space-2)">
|
||||
Ex-demo workstations, checked and warrantied.
|
||||
</h1>
|
||||
<p class="nv-prose">
|
||||
Everything here has been bench-tested and comes with the same
|
||||
two-year return window as new stock.
|
||||
</p>
|
||||
</div>
|
||||
<button class="nv-button nv-button--lg nv-button--primary">See all 74 deals</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shell section">
|
||||
<div class="section__head">
|
||||
<div>
|
||||
<span class="nv-overline">This week</span>
|
||||
<h2 class="nv-heading">Reduced while stock lasts</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nv-scroller-frame" data-nv-scroller>
|
||||
<div class="nv-scroller" style="--nv-scroller-item: 15rem">
|
||||
<article class="nv-card nv-card--interactive nv-card--flush">
|
||||
<div class="nv-bed nv-bed--quiet product__art" style="aspect-ratio: 16/9"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 6.5h14v9H5z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M3 18.5h18" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<h3 class="nv-label">Forge 14 Developer</h3>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Ryzen 9 · 32 GB · 1 TB NVMe</p>
|
||||
<p class="product__price" style="margin-block-start: var(--nv-space-2)">$1,849</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--interactive nv-card--flush">
|
||||
<div class="nv-bed nv-bed--quiet product__art" style="aspect-ratio: 16/9"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="4.5" width="18" height="12" rx="1.8" stroke="currentColor" stroke-width="1.6"/><path d="M9.5 19.5h5M12 16.5v3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<h3 class="nv-label">Clarity 27 4K</h3>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">27″ · 3840×2160 · 144 Hz</p>
|
||||
<p class="product__price" style="margin-block-start: var(--nv-space-2)">$629</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--interactive nv-card--flush">
|
||||
<div class="nv-bed nv-bed--quiet product__art" style="aspect-ratio: 16/9"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="2.5" y="7" width="19" height="10" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M6 10.5h1M9 10.5h1M12 10.5h1M15 10.5h1M18 10.5h1M8 13.8h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<h3 class="nv-label">Anvil TKL</h3>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Tactile · hot-swap · USB-C</p>
|
||||
<p class="product__price" style="margin-block-start: var(--nv-space-2)">$139</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--interactive nv-card--flush">
|
||||
<div class="nv-bed nv-bed--quiet product__art" style="aspect-ratio: 16/9"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="2.5" y="7.5" width="19" height="9" rx="1.6" stroke="currentColor" stroke-width="1.6"/><circle cx="8.5" cy="12" r="2.4" stroke="currentColor" stroke-width="1.6"/><circle cx="15.5" cy="12" r="2.4" stroke="currentColor" stroke-width="1.6"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<h3 class="nv-label">Halo X900 16 GB</h3>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Triple fan · 2.6 GHz boost</p>
|
||||
<p class="product__price" style="margin-block-start: var(--nv-space-2)">$1,099</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--interactive nv-card--flush">
|
||||
<div class="nv-bed nv-bed--quiet product__art" style="aspect-ratio: 16/9"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="4" y="6" width="16" height="12" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M8 10h8M8 14h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<h3 class="nv-label">Vault 4 TB NVMe</h3>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Gen4 · 7,400 MB/s read</p>
|
||||
<p class="product__price" style="margin-block-start: var(--nv-space-2)">$289</p>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
<button class="nv-button nv-button--icon nv-button--float" data-nv-scroll="prev" aria-label="Previous">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M14 6l-6 6 6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
<button class="nv-button nv-button--icon nv-button--float" data-nv-scroll="next" aria-label="Next">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M10 6l6 6-6 6" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="shell section" id="catalogue">
|
||||
<div class="catalogue">
|
||||
|
||||
<aside class="filters" aria-label="Filters">
|
||||
<div class="filter-group">
|
||||
<span class="nv-overline">Category</span>
|
||||
<label class="nv-choice nv-caption"><input class="nv-checkbox" type="checkbox" checked> Workstations</label>
|
||||
<label class="nv-choice nv-caption"><input class="nv-checkbox" type="checkbox" checked> Displays</label>
|
||||
<label class="nv-choice nv-caption"><input class="nv-checkbox" type="checkbox"> Networking</label>
|
||||
<label class="nv-choice nv-caption"><input class="nv-checkbox" type="checkbox"> Peripherals</label>
|
||||
</div>
|
||||
|
||||
<hr class="nv-divider" style="margin: 0">
|
||||
|
||||
<div class="filter-group">
|
||||
<span class="nv-overline">Price</span>
|
||||
<div class="nv-cluster nv-cluster--tight" style="flex-wrap: nowrap">
|
||||
<div class="nv-input-group">
|
||||
<span class="nv-input-group__affix">$</span>
|
||||
<label class="nv-visually-hidden" for="min">Minimum price</label>
|
||||
<input class="nv-input nv-input--sm" id="min" inputmode="numeric" value="0">
|
||||
</div>
|
||||
<span class="nv-caption">to</span>
|
||||
<div class="nv-input-group">
|
||||
<span class="nv-input-group__affix">$</span>
|
||||
<label class="nv-visually-hidden" for="max">Maximum price</label>
|
||||
<input class="nv-input nv-input--sm" id="max" inputmode="numeric" value="2000">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr class="nv-divider" style="margin: 0">
|
||||
|
||||
<div class="filter-group">
|
||||
<span class="nv-overline">Availability</span>
|
||||
<label class="nv-choice nv-caption"><input class="nv-switch" type="checkbox" checked> In stock only</label>
|
||||
<label class="nv-choice nv-caption"><input class="nv-switch" type="checkbox"> Ex-demo</label>
|
||||
</div>
|
||||
|
||||
<hr class="nv-divider" style="margin: 0">
|
||||
|
||||
<div class="filter-group">
|
||||
<span class="nv-overline">Condition</span>
|
||||
<label class="nv-choice nv-caption"><input class="nv-radio" type="radio" name="condition" checked> Any</label>
|
||||
<label class="nv-choice nv-caption"><input class="nv-radio" type="radio" name="condition"> New</label>
|
||||
<label class="nv-choice nv-caption"><input class="nv-radio" type="radio" name="condition"> Refurbished</label>
|
||||
</div>
|
||||
|
||||
<button class="nv-button nv-button--ghost nv-button--sm">Clear filters</button>
|
||||
</aside>
|
||||
|
||||
<div>
|
||||
<div class="section__head">
|
||||
<div>
|
||||
<h2 class="nv-title">Workstations & displays</h2>
|
||||
<p class="nv-caption">8 of 412 products</p>
|
||||
</div>
|
||||
|
||||
<button class="nv-button nv-button--sm nv-menu-trigger" popovertarget="sort-menu"
|
||||
style="--nv-anchor: --sort">
|
||||
Sort: <span id="sort-label">Relevance</span>
|
||||
<span class="nv-disclosure__chevron" aria-hidden="true"
|
||||
style="margin-inline-start: var(--nv-space-1)"></span>
|
||||
</button>
|
||||
|
||||
<div class="nv-menu" popover id="sort-menu" style="--nv-anchor: --sort">
|
||||
<button class="nv-menu__item" aria-checked="true" data-sort="Relevance">Relevance</button>
|
||||
<button class="nv-menu__item" data-sort="Price, low to high">Price, low to high</button>
|
||||
<button class="nv-menu__item" data-sort="Price, high to low">Price, high to low</button>
|
||||
<button class="nv-menu__item" data-sort="Newest">Newest</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="products">
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 6.5h14v9H5z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M3 18.5h18" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Forge 14 Developer</h3>
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>In stock</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Ryzen 9 · 32 GB · 1 TB NVMe</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$1,849</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Forge 14 Developer">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="4.5" width="18" height="12" rx="1.8" stroke="currentColor" stroke-width="1.6"/><path d="M9.5 19.5h5M12 16.5v3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Clarity 27 4K</h3>
|
||||
<span class="nv-badge nv-badge--warning"><span class="nv-badge__dot"></span>4 left</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">27″ · 3840×2160 · 144 Hz</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$629</span>
|
||||
<span class="product__was">$749</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Clarity 27 4K">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="2.5" y="7" width="19" height="10" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M6 10.5h1M9 10.5h1M12 10.5h1M15 10.5h1M18 10.5h1M8 13.8h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Anvil TKL</h3>
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>In stock</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Tactile · hot-swap · USB-C</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$139</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Anvil TKL">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="2.5" y="7.5" width="19" height="9" rx="1.6" stroke="currentColor" stroke-width="1.6"/><circle cx="8.5" cy="12" r="2.4" stroke="currentColor" stroke-width="1.6"/><circle cx="15.5" cy="12" r="2.4" stroke="currentColor" stroke-width="1.6"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Halo X900 16 GB</h3>
|
||||
<span class="nv-badge nv-badge--danger"><span class="nv-badge__dot"></span>Backorder</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Triple fan · 2.6 GHz boost</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$1,099</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Halo X900 16 GB">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="4" y="6" width="16" height="12" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M8 10h8M8 14h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Vault 4 TB NVMe</h3>
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>In stock</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Gen4 · 7,400 MB/s read</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$289</span>
|
||||
<span class="product__was">$329</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Vault 4 TB NVMe">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="13" width="18" height="6" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M7 16.5h.01M10 16.5h.01M8 9.5a5 5 0 018 0M11 6.2a8.5 8.5 0 012 0" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Mesh Six Pro</h3>
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>In stock</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Wi-Fi 6E · 3-pack · 2.5 GbE</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$399</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Mesh Six Pro">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 15v-3a7 7 0 0114 0v3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/><rect x="3" y="14" width="4" height="6" rx="1.6" stroke="currentColor" stroke-width="1.6"/><rect x="17" y="14" width="4" height="6" rx="1.6" stroke="currentColor" stroke-width="1.6"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Quiet Field 2</h3>
|
||||
<span class="nv-badge nv-badge--warning"><span class="nv-badge__dot"></span>2 left</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">ANC · 40 h · dual link</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$219</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Quiet Field 2">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
<article class="nv-card nv-card--flush nv-card--interactive">
|
||||
<div class="nv-bed nv-bed--quiet product__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="3" y="9" width="18" height="7" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M7 12.5h2M11 12.5h2M15.5 12.5h2" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></div>
|
||||
<div class="nv-card__body" style="padding: var(--nv-space-4)">
|
||||
<div class="nv-cluster nv-cluster--between" style="gap: var(--nv-space-2)">
|
||||
<h3 class="nv-label">Bridge 13-in-1</h3>
|
||||
<span class="nv-badge nv-badge--success"><span class="nv-badge__dot"></span>In stock</span>
|
||||
</div>
|
||||
<p class="nv-caption" style="margin-block-start: var(--nv-space-1)">Thunderbolt 4 · 96 W PD</p>
|
||||
<div class="nv-cluster" style="margin-block-start: var(--nv-space-3)">
|
||||
<span class="product__price">$179</span>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<div class="nv-card__footer nv-card__footer--plain" style="padding: 0 var(--nv-space-4) var(--nv-space-4)">
|
||||
<button class="nv-button nv-button--primary nv-button--sm nv-button--block"
|
||||
data-add="Bridge 13-in-1">Add to basket</button>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="shell site-footer">
|
||||
<div class="nv-cluster nv-cluster--between">
|
||||
<span class="nv-caption">Northwire — an example interface built with Niveus.</span>
|
||||
<span class="nv-caption">Made by Nexoniarz</span>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
|
||||
<dialog class="nv-dialog nv-dialog--wide" id="basket" aria-labelledby="basket-title">
|
||||
<div class="nv-dialog__header">
|
||||
<div>
|
||||
<h2 class="nv-dialog__title" id="basket-title">Your basket</h2>
|
||||
<p class="nv-dialog__description">Three items · delivered Thursday</p>
|
||||
</div>
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-dialog__close"
|
||||
aria-label="Close" data-nv-close>
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div class="nv-dialog__body">
|
||||
<div class="nv-table-frame">
|
||||
<table class="nv-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Item</th>
|
||||
<th scope="col" class="nv-table__number">Qty</th>
|
||||
<th scope="col" class="nv-table__number">Price</th>
|
||||
<th scope="col"><span class="nv-visually-hidden">Remove</span></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="nv-cluster" style="flex-wrap: nowrap">
|
||||
<span class="cart-line__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><path d="M5 6.5h14v9H5z" stroke="currentColor" stroke-width="1.6" stroke-linejoin="round"/><path d="M3 18.5h18" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></span>
|
||||
<span>
|
||||
Forge 14 Developer
|
||||
<span class="nv-caption" style="display: block">Ryzen 9 · 32 GB</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="nv-table__number">1</td>
|
||||
<td class="nv-table__number">$1,849</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm" aria-label="Remove Forge 14 Developer">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="nv-cluster" style="flex-wrap: nowrap">
|
||||
<span class="cart-line__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="4" y="6" width="16" height="12" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M8 10h8M8 14h5" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></span>
|
||||
<span>
|
||||
Vault 4 TB NVMe
|
||||
<span class="nv-caption" style="display: block">Gen4 NVMe</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="nv-table__number">1</td>
|
||||
<td class="nv-table__number">$289</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm" aria-label="Remove Vault 4 TB NVMe">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<div class="nv-cluster" style="flex-wrap: nowrap">
|
||||
<span class="cart-line__art"><svg viewBox="0 0 24 24" fill="none" aria-hidden="true"><rect x="2.5" y="7" width="19" height="10" rx="2" stroke="currentColor" stroke-width="1.6"/><path d="M6 10.5h1M9 10.5h1M12 10.5h1M15 10.5h1M18 10.5h1M8 13.8h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg></span>
|
||||
<span>
|
||||
Anvil TKL
|
||||
<span class="nv-caption" style="display: block">Tactile · hot-swap</span>
|
||||
</span>
|
||||
</div>
|
||||
</td>
|
||||
<td class="nv-table__number">1</td>
|
||||
<td class="nv-table__number">$139</td>
|
||||
<td class="nv-table__actions">
|
||||
<button class="nv-button nv-button--ghost nv-button--icon nv-button--sm" aria-label="Remove Anvil TKL">
|
||||
<svg class="nv-button__icon" viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
||||
<path d="M6 6l12 12M18 6L6 18" stroke="currentColor" stroke-width="2" stroke-linecap="round"/>
|
||||
</svg>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="cart-totals">
|
||||
<div class="cart-totals__row nv-caption"><span>Subtotal</span><span>$2,277.00</span></div>
|
||||
<div class="cart-totals__row nv-caption"><span>Delivery</span><span>Free</span></div>
|
||||
<div class="cart-totals__row nv-caption"><span>VAT (20%)</span><span>$455.40</span></div>
|
||||
<div class="cart-totals__row cart-totals__row--due"><span>Due today</span><span>$2,732.40</span></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="nv-dialog__footer nv-dialog__footer--divided">
|
||||
<button class="nv-button" data-nv-close>Keep shopping</button>
|
||||
<button class="nv-button nv-button--primary">Checkout</button>
|
||||
</div>
|
||||
</dialog>
|
||||
|
||||
<script>
|
||||
document.addEventListener("DOMContentLoaded", () => {
|
||||
document.documentElement.addEventListener("nv:theme", (event) => {
|
||||
try { localStorage.setItem("northwire-theme", event.detail.theme); } catch {}
|
||||
});
|
||||
nv.syncThemeControls(document.documentElement.getAttribute("data-theme") || "system");
|
||||
|
||||
/* Adding to the basket: the count changes, so it should be watchable. */
|
||||
const count = document.getElementById("basket-count");
|
||||
document.querySelectorAll("[data-add]").forEach((button) => {
|
||||
button.addEventListener("click", () => {
|
||||
nv.transition(() => {
|
||||
count.textContent = String(Number(count.textContent) + 1);
|
||||
});
|
||||
button.textContent = "Added";
|
||||
setTimeout(() => { button.textContent = "Add to basket"; }, 1200);
|
||||
});
|
||||
});
|
||||
|
||||
document.querySelectorAll("[data-sort]").forEach((option) => {
|
||||
option.addEventListener("click", () => {
|
||||
document.querySelectorAll("[data-sort]").forEach((other) => {
|
||||
other.setAttribute("aria-checked", String(other === option));
|
||||
});
|
||||
document.getElementById("sort-label").textContent = option.dataset.sort;
|
||||
document.getElementById("sort-menu").hidePopover();
|
||||
});
|
||||
});
|
||||
|
||||
const topnav = document.getElementById("topnav");
|
||||
const onScroll = () => topnav.toggleAttribute("data-scrolled", scrollY > 8);
|
||||
addEventListener("scroll", onScroll, { passive: true });
|
||||
onScroll();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Loading…
Reference in a new issue