openvibers/openvibe.shared Commits
Shape and elevation are tokens, and a look has a shape (4.81.0) (#234)
Changes
@@@ -4,6 +4,20 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semvebreaking change to any exported module, browser global or served file name is a new major.A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).++## 4.81.0 — 2026-11-01++++- **Shape and elevation are tokens, and a look has a shape** (plan L45 step 2: "Paper is quiet and square, Neon glows++ and bounces").++ - `--ov-radius-1..3` (small things, cards, sheets) and `--ov-elev-1..3` (on its surface, a bar over scrolled content,++ a menu above the page) are in `kit.css`. Their defaults are the kit's own corners and shadows, so nothing moves.++ - Every kit corner and shadow now reads them: `--ovk-radius` is `--ov-radius-2`, and a 12 px corner is++ `radius-1 + 2px`. Only pills and circles stay literal.++ - The quiet looks are square, with hairlines where shadows were: Paper, Solarized Light, Nord, Nord Light, Matrix,++ Hacker and High Contrast.++ - Neon Tokyo and Vapor are round, and their raised things glow in the accent.++ - A site's own CSS can use the same tokens to follow the look: `border-radius: var(--ov-radius-2, 14px)`.++ - `room.css` carries the same tokens, and the personalities at zero specificity.++## 4.80.0 — 2026-11-01- **33 more glyphs have a solid drawing for small sizes** (plan L50; the owner, 2026-11-01: "really tiny icons… a@@@ -2,7 +2,7 @@> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.--**Status:** alpha, v4.80.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.81.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployedconsumer installs a tagged release and none keeps a vendored copy([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` namesthe tag it pins.@@@ -4,7 +4,7 @@"code": true,"wave": "W2","package": "openvibe-shared",-- "version": "4.80.0",++ "version": "4.81.0","plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)","deployed": false,"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",@@@ -25,14 +25,38 @@--ovk-warn: var(--warning, #f59e0b);--ovk-bad: var(--danger, var(--error, #ef4444));--ovk-live: var(--live-red, #ef4444);-- --ovk-radius: 14px;-- --ovk-radius-sm: 10px;++ /* Shape and elevation (4.81.0, plan L45 step 2): the kit's corners and shadows as tokens a look can set, so a theme++ * has a shape as well as colours. The defaults are the kit's own corners and shadows; the personalities follow. */++ --ov-radius-1: 10px; /* chips, inputs, rows, small buttons */++ --ov-radius-2: 14px; /* cards, sections, panels */++ --ov-radius-3: 20px; /* sheets, dialogs, big surfaces */++ --ov-elev-1: 0 1px 3px rgba(0, 0, 0, .35); /* a knob, a chip that sits on its surface */++ --ov-elev-2: 0 10px 24px -18px rgba(0, 0, 0, .7); /* a bar that has scrolled content under it */++ --ov-elev-3: 0 14px 32px rgba(0, 0, 0, .4); /* a menu or popover above the page */++ --ovk-radius: var(--ov-radius-2);++ --ovk-radius-sm: var(--ov-radius-1);--ovk-tap: 44px;--ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */--ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);}++/* Shape personalities (plan L45: "Paper is quiet and square, Neon glows and bounces"). A quiet look has square corners++ * and lies flat, with a hairline where a shadow would be; a loud one is round and its raised things glow in its accent.++ * The ids are Network's theme catalog (as ov-fx.js's motion feels); any other look keeps the defaults above. */++:is([data-theme=paper], [data-theme=solarized-light], [data-theme=nord], [data-theme=nord-light], [data-theme=matrix], [data-theme=hacker], [data-theme=high-contrast]) {++ --ov-radius-1: 4px; --ov-radius-2: 6px; --ov-radius-3: 10px;++ --ov-elev-1: 0 0 0 1px var(--ovk-line);++ --ov-elev-2: 0 1px 0 var(--ovk-line);++ --ov-elev-3: 0 0 0 1px var(--ovk-line), 0 8px 18px -12px rgba(0, 0, 0, .35);++}++:is([data-theme=neon-tokyo], [data-theme=vapor]) {++ --ov-radius-1: 14px; --ov-radius-2: 20px; --ov-radius-3: 28px;++ --ov-elev-1: 0 0 8px color-mix(in srgb, var(--ovk-accent) 40%, transparent);++ --ov-elev-2: 0 10px 28px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);++ --ov-elev-3: 0 0 0 1px color-mix(in srgb, var(--ovk-accent) 40%, transparent), 0 18px 44px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);++}++/* Heading and paragraph rules double their class (.ovk-section.ovk-section > h2): a site's own ".site h2" has the same* specificity as ".ovk-section > h2" and loads later, so it used to restyle the kit's headings (Report, 2026-10-29). */@@@ -45,7 +69,7 @@.ovk-page--wide { max-width: 1280px; }/* ── Buttons ── */--.ovk-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 18px; border-radius: 12px; border: 1px solid transparent; font: inherit; font-weight: 700; font-size: .95rem; line-height: 1.2; text-decoration: none; cursor: pointer; transition: filter .12s, border-color .12s, background .12s; }++.ovk-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 18px; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid transparent; font: inherit; font-weight: 700; font-size: .95rem; line-height: 1.2; text-decoration: none; cursor: pointer; transition: filter .12s, border-color .12s, background .12s; }.ovk-btn--primary { background: var(--ovk-fill); color: var(--ovk-on-fill); }.ovk-btn--primary:hover { filter: brightness(1.08); }.ovk-btn--quiet { background: var(--ovk-panel); color: var(--ovk-text); border-color: var(--ovk-line); }@@@ -56,7 +80,7 @@.ovk-btn--small { min-height: 36px; padding: 6px 13px; border-radius: 999px; font-size: .88rem; }.ovk-btn:focus-visible, .ovk-icon-btn:focus-visible, .ovk-go:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }.ovk-btn[disabled] { opacity: .55; cursor: default; filter: none; }--.ovk-icon-btn { display: inline-grid; place-items: center; box-sizing: border-box; width: 40px; height: 40px; flex: none; padding: 0; border-radius: 12px; border: 1px solid transparent; background: transparent; color: var(--ovk-muted); font: inherit; cursor: pointer; text-decoration: none; list-style: none; }++.ovk-icon-btn { display: inline-grid; place-items: center; box-sizing: border-box; width: 40px; height: 40px; flex: none; padding: 0; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid transparent; background: transparent; color: var(--ovk-muted); font: inherit; cursor: pointer; text-decoration: none; list-style: none; }.ovk-icon-btn::-webkit-details-marker { display: none; }.ovk-icon-btn:hover { color: var(--ovk-text); background: var(--ovk-panel-2); border-color: var(--ovk-line); }.ovk-icon-btn[hidden] { display: none; }@@@ -74,7 +98,7 @@/* ── The head: back, title and one line, icon actions; its tabs under it; sticky under the navbar ── */.ovk-head { position: sticky; top: var(--ovk-top); z-index: 40; margin: 0 -16px 12px; padding: 6px 16px 0; background: color-mix(in srgb, var(--ovk-bg) 88%, transparent); -webkit-backdrop-filter: saturate(1.4) blur(14px); backdrop-filter: saturate(1.4) blur(14px); border-bottom: 1px solid transparent; transition: border-color .2s, box-shadow .2s; }--.ovk-scrolled .ovk-head { border-bottom-color: var(--ovk-line); box-shadow: 0 10px 24px -18px rgba(0, 0, 0, .7); }++.ovk-scrolled .ovk-head { border-bottom-color: var(--ovk-line); box-shadow: var(--ov-elev-2); }.ovk-head--static { position: static; background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; }.ovk-head-row { display: flex; align-items: center; gap: 6px; min-height: 48px; padding-bottom: 6px; }.ovk-head-row > .ovk-back { margin-left: -8px; }@@@ -115,7 +139,7 @@.ovk-row--unread .ovk-row-sub { color: var(--ovk-text); }.ovk-row-trail { display: flex; align-items: center; gap: 2px; flex: none; }.ovk-row-trail form { display: flex; align-items: center; gap: 2px; margin: 0; }--.ovk-row-trail select { max-width: 9.5em; padding: 5px 6px; border-radius: 8px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .86rem; }++.ovk-row-trail select { max-width: 9.5em; padding: 5px 6px; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .86rem; }.ovk-mark { --ovk-mark: 40px; flex: none; display: inline-grid; place-items: center; width: var(--ovk-mark); height: var(--ovk-mark); box-sizing: border-box; border-radius: 50%; object-fit: cover; background: hsl(var(--ovk-hue, 215) 45% 38%); color: #fff; font-size: calc(var(--ovk-mark) * .34); font-weight: 800; letter-spacing: .02em; line-height: 1; }.ovk-mark--square { border-radius: 28%; }.ovk-empty-line { margin: 0 0 14px; padding: 14px; border: 1px dashed var(--ovk-line); border-radius: var(--ovk-radius); color: var(--ovk-muted); }@@@ -140,9 +164,9 @@.ovk-fold--danger > summary { color: var(--ovk-bad); }.ovk-more { position: relative; display: inline-block; }.ovk-more > summary { list-style: none; }--.ovk-pop { position: absolute; right: 0; top: calc(100% + 4px); z-index: 60; min-width: 210px; padding: 6px; box-sizing: border-box; border-radius: 12px; border: 1px solid var(--ovk-line); background: var(--ovk-panel); box-shadow: 0 14px 32px rgba(0, 0, 0, .4); }++.ovk-pop { position: absolute; right: 0; top: calc(100% + 4px); z-index: 60; min-width: 210px; padding: 6px; box-sizing: border-box; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid var(--ovk-line); background: var(--ovk-panel); box-shadow: var(--ov-elev-3); }.ovk-pop form { margin: 0; }--.ovk-pop-item { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 40px; box-sizing: border-box; padding: 8px 10px; border: 0; border-radius: 8px; background: transparent; color: var(--ovk-text); font: inherit; font-weight: 600; text-align: left; text-decoration: none; cursor: pointer; }++.ovk-pop-item { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 40px; box-sizing: border-box; padding: 8px 10px; border: 0; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); background: transparent; color: var(--ovk-text); font: inherit; font-weight: 600; text-align: left; text-decoration: none; cursor: pointer; }.ovk-pop-item:hover, .ovk-pop-item:focus-visible { background: var(--ovk-panel-2); outline: none; }.ovk-pop-item--danger { color: var(--ovk-bad); }@@@ -152,17 +176,17 @@div.ovk-set-row { cursor: default; }.ovk-set-text { flex: 1; min-width: 0; display: grid; gap: 2px; }.ovk-set-text small { color: var(--ovk-muted); font-size: .8rem; }--.ovk-set-row select { flex: none; max-width: 52%; margin: 0; padding: 7px 8px; border-radius: 10px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .9rem; }++.ovk-set-row select { flex: none; max-width: 52%; margin: 0; padding: 7px 8px; border-radius: var(--ov-radius-1); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .9rem; }.ovk-set-range input[type=range] { flex: 0 1 45%; min-width: 120px; margin: 0; accent-color: var(--ovk-fill); }.ovk-set-chips { flex-wrap: wrap; }.ovk-switch { appearance: none; -webkit-appearance: none; flex: none; position: relative; width: 44px; height: 26px; margin: 0; padding: 0; border-radius: 999px; border: 0; background: var(--ovk-line); cursor: pointer; transition: background .15s; }--.ovk-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .35); transition: transform .15s; }++.ovk-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: var(--ov-elev-1); transition: transform .15s; }.ovk-switch:checked { background: var(--ovk-fill); }.ovk-switch:checked::after { transform: translateX(18px); }.ovk-switch:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }--.ovk-seg { display: inline-flex; flex: none; flex-wrap: wrap; max-width: 100%; gap: 2px; padding: 3px; border-radius: 10px; background: var(--ovk-panel-2); }++.ovk-seg { display: inline-flex; flex: none; flex-wrap: wrap; max-width: 100%; gap: 2px; padding: 3px; border-radius: var(--ov-radius-1); background: var(--ovk-panel-2); }.ovk-seg input, .ovk-chip input { position: absolute; opacity: 0; pointer-events: none; }--.ovk-seg span { display: block; padding: 6px 12px; border-radius: 8px; color: var(--ovk-muted); font-size: .86rem; font-weight: 650; cursor: pointer; }++.ovk-seg span { display: block; padding: 6px 12px; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); color: var(--ovk-muted); font-size: .86rem; font-weight: 650; cursor: pointer; }.ovk-seg input:checked + span { background: var(--ovk-fill); color: var(--ovk-on-fill); }.ovk-seg input:focus-visible + span, .ovk-chip input:focus-visible + span { outline: 2px solid var(--ovk-accent); outline-offset: 1px; }.ovk-chips { display: flex; flex-wrap: wrap; gap: 6px; }@@@ -173,7 +197,7 @@ div.ovk-set-row { cursor: default; }.ovk-form { display: grid; gap: 12px; margin: 0; }.ovk-form label { display: grid; gap: 5px; color: var(--ovk-muted); font-size: .9rem; }.ovk-form label.ovk-check { display: flex; align-items: flex-start; gap: 10px; color: var(--ovk-text); font-size: .95rem; }--.ovk-form input:not([type=checkbox]):not([type=radio]):not([type=range]), .ovk-form select, .ovk-form textarea { width: 100%; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 12px; border-radius: 10px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 1rem; }++.ovk-form input:not([type=checkbox]):not([type=radio]):not([type=range]), .ovk-form select, .ovk-form textarea { width: 100%; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 12px; border-radius: var(--ov-radius-1); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 1rem; }.ovk-form textarea { min-height: 96px; resize: vertical; }.ovk-form input:focus-visible, .ovk-form select:focus-visible, .ovk-form textarea:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 0; border-color: transparent; }.ovk-form.ovk-form > p { margin: 0; }@@@ -211,7 +235,7 @@ div.ovk-set-row { cursor: default; }/* ── Numbers, empty states, notices, code, sheets ── */.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }.ovk-stats::-webkit-scrollbar { display: none; }--.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }++.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: calc(var(--ov-radius-1) + 2px); background: var(--ovk-panel); }.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }.ovk-stats dt a { color: inherit; text-decoration: none; }/* A number that is a link: the whole tile is the target (the label's link stretched over it), never an underline. */@@@ -225,7 +249,7 @@ div.ovk-set-row { cursor: default; }.ovk-empty > .ovk-i { width: 28px; height: 28px; }.ovk-empty.ovk-empty p { margin: 0; max-width: 46ch; }.ovk-empty .ovk-empty-title { color: var(--ovk-text); font-weight: 750; }--.ovk-notice { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 12px; padding: 10px 14px; border-radius: 12px; border: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); font-size: .92rem; }++.ovk-notice { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 12px; padding: 10px 14px; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); font-size: .92rem; }.ovk-notice > .ovk-i { margin-top: 2px; }.ovk-notice--ok { border-color: color-mix(in srgb, var(--ovk-ok) 45%, transparent); }.ovk-notice--ok > .ovk-i { color: var(--ovk-ok); }@@@ -235,8 +259,8 @@ div.ovk-set-row { cursor: default; }.ovk-notice--info > .ovk-i { color: var(--ovk-accent); }.ovk-snip { position: relative; }.ovk-snip > .ovk-icon-btn { position: absolute; top: 6px; right: 6px; z-index: 1; width: 34px; height: 34px; background: var(--ovk-panel); border-color: var(--ovk-line); }--.ovk-snip > pre { max-height: 50vh; margin: 0 0 10px; padding: 12px 48px 12px 12px; overflow: auto; border-radius: 10px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font-size: .84rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }--.ovk-sheet { width: min(560px, 100vw); max-width: 100vw; max-height: 85dvh; margin: auto; padding: 0; box-sizing: border-box; border: 1px solid var(--ovk-line); border-radius: 18px; background: var(--ovk-panel); color: var(--ovk-text); overflow: hidden; }++.ovk-snip > pre { max-height: 50vh; margin: 0 0 10px; padding: 12px 48px 12px 12px; overflow: auto; border-radius: var(--ov-radius-1); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font-size: .84rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }++.ovk-sheet { width: min(560px, 100vw); max-width: 100vw; max-height: 85dvh; margin: auto; padding: 0; box-sizing: border-box; border: 1px solid var(--ovk-line); border-radius: calc(var(--ov-radius-3) - 2px); background: var(--ovk-panel); color: var(--ovk-text); overflow: hidden; }.ovk-sheet::backdrop { background: rgba(0, 0, 0, .55); }.ovk-sheet[open] { display: flex; flex-direction: column; }.ovk-sheet-head { display: flex; align-items: center; gap: 8px; padding: 8px 8px 8px 16px; border-bottom: 1px solid var(--ovk-line); }@@@ -246,7 +270,7 @@ div.ovk-set-row { cursor: default; }/* ── Phones (< 600 px): bottom sheets, full-width forms ── */@media (max-width: 599px) {-- .ovk-sheet { margin: auto 0 0; width: 100vw; border-radius: 18px 18px 0 0; border-bottom: 0; }++ .ovk-sheet { margin: auto 0 0; width: 100vw; border-radius: calc(var(--ov-radius-3) - 2px) calc(var(--ov-radius-3) - 2px) 0 0; border-bottom: 0; }.ovk-row > a, .ovk-row--static { padding-left: 12px; }.ovk-head-tools { gap: 0; }}@@@ -281,16 +305,16 @@ div.ovk-set-row { cursor: default; }.ovk-with-sidebar-main { min-width: 0; }.ovk-sidebar { position: sticky; top: var(--navbar-h, 52px); height: calc(100dvh - var(--navbar-h, 52px)); box-sizing: border-box; display: flex; flex-direction: column; gap: 8px; min-width: 0; padding: 12px 10px 10px; border-right: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); overflow: hidden; }.ovk-sidebar-top { display: flex; align-items: center; gap: 6px; }--.ovk-sidebar-action { flex: 1; display: flex; align-items: center; gap: 10px; min-height: 42px; padding: 0 12px; border-radius: 12px; border: 1px solid var(--ovk-line); background: var(--ovk-bg); color: var(--ovk-text); font-weight: 650; text-decoration: none; transition: border-color .18s, background .18s; }++.ovk-sidebar-action { flex: 1; display: flex; align-items: center; gap: 10px; min-height: 42px; padding: 0 12px; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid var(--ovk-line); background: var(--ovk-bg); color: var(--ovk-text); font-weight: 650; text-decoration: none; transition: border-color .18s, background .18s; }.ovk-sidebar-action:hover { border-color: color-mix(in srgb, var(--ovk-accent) 55%, var(--ovk-line)); }.ovk-sidebar-action .ovk-i { width: 18px; height: 18px; color: var(--ovk-accent); }.ovk-sidebar-close, .ovk-sidebar-open { display: none; }.ovk-sidebar-find { position: relative; display: flex; align-items: center; margin: 0; }.ovk-sidebar-find .ovk-i { position: absolute; left: 11px; width: 16px; height: 16px; color: var(--ovk-muted); pointer-events: none; }--.ovk-sidebar-find input { width: 100%; min-height: 38px; box-sizing: border-box; padding: 0 12px 0 34px; border-radius: 10px; border: 1px solid transparent; background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 14px; }++.ovk-sidebar-find input { width: 100%; min-height: 38px; box-sizing: border-box; padding: 0 12px 0 34px; border-radius: var(--ov-radius-1); border: 1px solid transparent; background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 14px; }.ovk-sidebar-find input:focus { outline: none; border-color: var(--ovk-accent); }.ovk-sidebar-nav { display: grid; gap: 1px; padding-bottom: 8px; border-bottom: 1px solid var(--ovk-line); }--.ovk-sidebar-link { display: flex; align-items: center; gap: 11px; min-height: 38px; padding: 0 10px; border-radius: 10px; color: var(--ovk-muted); font-size: 14.5px; font-weight: 600; text-decoration: none; transition: background .15s, color .15s; }++.ovk-sidebar-link { display: flex; align-items: center; gap: 11px; min-height: 38px; padding: 0 10px; border-radius: var(--ov-radius-1); color: var(--ovk-muted); font-size: 14.5px; font-weight: 600; text-decoration: none; transition: background .15s, color .15s; }.ovk-sidebar-link .ovk-i { width: 19px; height: 19px; flex: none; }.ovk-sidebar-link span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ovk-sidebar-link:hover { background: var(--ovk-panel-2); color: var(--ovk-text); }@@@ -305,7 +329,7 @@ div.ovk-set-row { cursor: default; }@keyframes ovk-sidebar-breathe { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.75); } }.ovk-sidebar-secs { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: 0 -4px; padding: 0 4px 8px; }.ovk-sidebar-h { margin: 12px 10px 4px; font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ovk-muted); }--.ovk-sidebar-item { display: flex; align-items: center; gap: 9px; min-height: 36px; padding: 4px 10px; border-radius: 10px; color: var(--ovk-text); font-size: 14px; text-decoration: none; }++.ovk-sidebar-item { display: flex; align-items: center; gap: 9px; min-height: 36px; padding: 4px 10px; border-radius: var(--ov-radius-1); color: var(--ovk-text); font-size: 14px; text-decoration: none; }.ovk-sidebar-item:hover { background: var(--ovk-panel-2); }.ovk-sidebar-item.is-here { background: var(--ovk-panel-2); font-weight: 600; }.ovk-sidebar-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }@@@ -336,25 +360,25 @@ div.ovk-set-row { cursor: default; }/* ── Placeholders shaped like the content (4.70.0, kit.skeleton; plan L52) ── A soft light passes over grey shapes the size ofwhat is coming; nothing spins; still under reduced motion and Calm. A list paints these in its first HTML and fills them inplace. */--.ovk-skel { display: block; border-radius: 8px; background: color-mix(in srgb, var(--text-primary, #e8edf3) 7%, transparent); background-image: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--text-primary, #e8edf3) 9%, transparent) 50%, transparent 70%); background-size: 220% 100%; background-repeat: no-repeat; animation: ovkShimmer 1.6s ease-in-out infinite; }++.ovk-skel { display: block; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); background: color-mix(in srgb, var(--text-primary, #e8edf3) 7%, transparent); background-image: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--text-primary, #e8edf3) 9%, transparent) 50%, transparent 70%); background-size: 220% 100%; background-repeat: no-repeat; animation: ovkShimmer 1.6s ease-in-out infinite; }@keyframes ovkShimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }.ovk-skels { display: grid; gap: 12px; }.ovk-skels-card { grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); }.ovk-skels-tile { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }.ovk-skels-chip { display: flex; flex-wrap: wrap; gap: 8px; }.ovk-skel-card { display: grid; gap: 8px; }--.ovk-skel-thumb { aspect-ratio: 16 / 9; border-radius: 12px; }++.ovk-skel-thumb { aspect-ratio: 16 / 9; border-radius: calc(var(--ov-radius-1) + 2px); }.ovk-skel-l1 { height: 13px; width: 86%; }.ovk-skel-l2 { height: 11px; width: 58%; }.ovk-skel-row { display: flex; align-items: center; gap: 12px; }.ovk-skel-avatar { flex: none; width: 40px; height: 40px; border-radius: 50%; }.ovk-skel-lines { flex: 1; display: grid; gap: 7px; min-width: 0; }--.ovk-skel-tile { aspect-ratio: 1; border-radius: 14px; }++.ovk-skel-tile { aspect-ratio: 1; border-radius: var(--ov-radius-2); }.ovk-skel-chip { width: 84px; height: 30px; border-radius: 999px; }.ovk-skel-line { height: 12px; }.ovk-skel-chat { display: flex; align-items: center; gap: 8px; }.ovk-skel-dot { flex: none; width: 22px; height: 22px; border-radius: 50%; }--.ovk-skel-stat { display: grid; gap: 6px; padding: 12px; border-radius: 14px; border: 1px solid var(--border, rgba(255,255,255,.08)); }++.ovk-skel-stat { display: grid; gap: 6px; padding: 12px; border-radius: var(--ov-radius-2); border: 1px solid var(--border, rgba(255,255,255,.08)); }.ovk-skel-num { height: 22px; width: 48%; }@media (prefers-reduced-motion: reduce) { .ovk-skel { animation: none; } }html[data-ov-motion=reduced] .ovk-skel { animation: none; }@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.80.0",++ "version": "4.81.0","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -22,16 +22,39 @@--ovk-warn: var(--warning, #f59e0b);--ovk-bad: var(--danger, var(--error, #ef4444));--ovk-live: var(--live-red, #ef4444);-- --ovk-radius: 14px;-- --ovk-radius-sm: 10px;++ /* Shape and elevation (4.81.0, plan L45 step 2): the kit's corners and shadows as tokens a look can set, so a theme++ * has a shape as well as colours. The defaults are the kit's own corners and shadows; the personalities follow. */++ --ov-radius-1: 10px; /* chips, inputs, rows, small buttons */++ --ov-radius-2: 14px; /* cards, sections, panels */++ --ov-radius-3: 20px; /* sheets, dialogs, big surfaces */++ --ov-elev-1: 0 1px 3px rgba(0, 0, 0, .35); /* a knob, a chip that sits on its surface */++ --ov-elev-2: 0 10px 24px -18px rgba(0, 0, 0, .7); /* a bar that has scrolled content under it */++ --ov-elev-3: 0 14px 32px rgba(0, 0, 0, .4); /* a menu or popover above the page */++ --ovk-radius: var(--ov-radius-2);++ --ovk-radius-sm: var(--ov-radius-1);--ovk-tap: 44px;--ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */--ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);}++/* Shape personalities, as in kit.css but at zero specificity (plan L45: "Paper is quiet and square, Neon glows and bounces"). A quiet look has square corners++ * and lies flat, with a hairline where a shadow would be; a loud one is round and its raised things glow in its accent.++ * The ids are Network's theme catalog (as ov-fx.js's motion feels); any other look keeps the defaults above. */++:where([data-theme=paper], [data-theme=solarized-light], [data-theme=nord], [data-theme=nord-light], [data-theme=matrix], [data-theme=hacker], [data-theme=high-contrast]) {++ --ov-radius-1: 4px; --ov-radius-2: 6px; --ov-radius-3: 10px;++ --ov-elev-1: 0 0 0 1px var(--ovk-line);++ --ov-elev-2: 0 1px 0 var(--ovk-line);++ --ov-elev-3: 0 0 0 1px var(--ovk-line), 0 8px 18px -12px rgba(0, 0, 0, .35);++}++:where([data-theme=neon-tokyo], [data-theme=vapor]) {++ --ov-radius-1: 14px; --ov-radius-2: 20px; --ov-radius-3: 28px;++ --ov-elev-1: 0 0 8px color-mix(in srgb, var(--ovk-accent) 40%, transparent);++ --ov-elev-2: 0 10px 28px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);++ --ov-elev-3: 0 0 0 1px color-mix(in srgb, var(--ovk-accent) 40%, transparent), 0 18px 44px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);++}.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }.ovk-stats::-webkit-scrollbar { display: none; }--.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }++.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: calc(var(--ov-radius-1) + 2px); background: var(--ovk-panel); }.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }.ovk-stats dt a { color: inherit; text-decoration: none; }.ovk-stat--link { position: relative; transition: border-color .15s; }nnew file mode 100644@@@ -0,0 +1,29 @@++'use strict';++/**++ * Shape and elevation tokens (4.81.0, plan L45 step 2): the kit's corners and shadows are --ov-radius-1..3 and++ * --ov-elev-1..3, with the kit's own values as defaults, so a look can give a theme a shape. Paper and the quiet looks++ * are square and flat, Neon round and glowing. room.css carries the same tokens and, at zero specificity, the same++ * personalities (it can load without kit.css).++ */++const assert = require('assert');++const fs = require('fs');++const path = require('path');++++const kit = fs.readFileSync(path.join(__dirname, '..', 'kit.css'), 'utf8');++const room = fs.readFileSync(path.join(__dirname, '..', 'room.css'), 'utf8');++++assert.ok(kit.includes('--ov-radius-1: 10px;'), 'radius-1 defaults to the kit\'s own 10px');++assert.ok(kit.includes('--ov-radius-2: 14px;'), 'radius-2 defaults to the kit\'s own 14px');++assert.ok(kit.includes('--ov-radius-3: 20px;'), 'radius-3 is 20px');++assert.match(kit, /--ov-elev-1: [^;]+;/);++assert.match(kit, /--ov-elev-2: [^;]+;/);++assert.match(kit, /--ov-elev-3: [^;]+;/);++assert.ok(kit.includes('--ovk-radius: var(--ov-radius-2);') && kit.includes('--ovk-radius-sm: var(--ov-radius-1);'), 'the kit\'s own radius names follow the tokens');++assert.doesNotMatch(kit, /border-radius: (8|10|12|14|18|20)px/, 'no corner of the kit is a literal any more (pills and circles stay)');++assert.ok(kit.includes('box-shadow: var(--ov-elev-3);') && kit.includes('box-shadow: var(--ov-elev-2);') && kit.includes('box-shadow: var(--ov-elev-1);'), 'the kit\'s shadows are the elevation tokens');++assert.match(kit, /:is\(\[data-theme=paper\][^{]*\) \{\s*--ov-radius-1: 4px; --ov-radius-2: 6px; --ov-radius-3: 10px;/, 'Paper is square');++assert.match(kit, /:is\(\[data-theme=neon-tokyo\][^{]*\) \{\s*--ov-radius-1: 14px; --ov-radius-2: 20px; --ov-radius-3: 28px;/, 'Neon is round');++assert.match(kit, /--ov-elev-3: 0 0 0 1px color-mix\(in srgb, var\(--ovk-accent\)/, 'and Neon\'s raised things glow in its accent');++assert.match(room, /:where\(\[data-theme=paper\][^{]*\) \{\s*--ov-radius-1: 4px;/, 'room.css: the same personalities at zero specificity');++assert.match(room, /:where\(\[data-theme=neon-tokyo\][^{]*\) \{\s*--ov-radius-1: 14px;/);++console.log('kit-shape: tokens, the kit on them, and the personalities');
@@ -4,6 +4,20 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
@@ -4,6 +4,20 @@ All notable changes to `openvibe-shared`. Versions follow [semver](https://semve
breaking change to any exported module, browser global or served file name is a new major.
breaking change to any exported module, browser global or served file name is a new major.
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
+## 4.81.0 — 2026-11-01
+
+- **Shape and elevation are tokens, and a look has a shape** (plan L45 step 2: "Paper is quiet and square, Neon glows
+ and bounces").
+ - `--ov-radius-1..3` (small things, cards, sheets) and `--ov-elev-1..3` (on its surface, a bar over scrolled content,
+ a menu above the page) are in `kit.css`. Their defaults are the kit's own corners and shadows, so nothing moves.
+ - Every kit corner and shadow now reads them: `--ovk-radius` is `--ov-radius-2`, and a 12 px corner is
+ `radius-1 + 2px`. Only pills and circles stay literal.
+ - The quiet looks are square, with hairlines where shadows were: Paper, Solarized Light, Nord, Nord Light, Matrix,
+ Hacker and High Contrast.
+ - Neon Tokyo and Vapor are round, and their raised things glow in the accent.
+ - A site's own CSS can use the same tokens to follow the look: `border-radius: var(--ov-radius-2, 14px)`.
+ - `room.css` carries the same tokens, and the personalities at zero specificity.
+
## 4.80.0 — 2026-11-01
## 4.80.0 — 2026-11-01
- **33 more glyphs have a solid drawing for small sizes** (plan L50; the owner, 2026-11-01: "really tiny icons… a
- **33 more glyphs have a solid drawing for small sizes** (plan L50; the owner, 2026-11-01: "really tiny icons… a
@@ -2,7 +2,7 @@
@@ -2,7 +2,7 @@
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
> Versioned UI, the OpenVibe Frame (the navbar, footer and "shipped" views every site sits in), SEO, legal and release-client packages every OpenVibe site renders.
-**Status:** alpha, v4.80.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.81.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
consumer installs a tagged release and none keeps a vendored copy
consumer installs a tagged release and none keeps a vendored copy
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
([docs/migration-plan.md](docs/migration-plan.md) is done); each repository's `package.json` names
the tag it pins.
the tag it pins.
@@ -4,7 +4,7 @@
@@ -4,7 +4,7 @@
"code": true,
"code": true,
"wave": "W2",
"wave": "W2",
"package": "openvibe-shared",
"package": "openvibe-shared",
- "version": "4.80.0",
+ "version": "4.81.0",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"plan": "OpenVibe End-to-End Realignment & Implementation Plan, revision 3 (20 Sep 2026)",
"deployed": false,
"deployed": false,
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
"deployment": "none of its own: a package released as git tags; OpenVibe.Network serves /shared/* from its pinned tag, and a site can serve its own pinned copy (openvibe-shared/serve)",
@@ -25,14 +25,38 @@
@@ -25,14 +25,38 @@
--ovk-warn: var(--warning, #f59e0b);
--ovk-warn: var(--warning, #f59e0b);
--ovk-bad: var(--danger, var(--error, #ef4444));
--ovk-bad: var(--danger, var(--error, #ef4444));
--ovk-live: var(--live-red, #ef4444);
--ovk-live: var(--live-red, #ef4444);
- --ovk-radius: 14px;
- --ovk-radius-sm: 10px;
+ /* Shape and elevation (4.81.0, plan L45 step 2): the kit's corners and shadows as tokens a look can set, so a theme
+ * has a shape as well as colours. The defaults are the kit's own corners and shadows; the personalities follow. */
+ --ov-radius-1: 10px; /* chips, inputs, rows, small buttons */
+ --ov-radius-2: 14px; /* cards, sections, panels */
+ --ov-radius-3: 20px; /* sheets, dialogs, big surfaces */
+ --ov-elev-1: 0 1px 3px rgba(0, 0, 0, .35); /* a knob, a chip that sits on its surface */
+ --ov-elev-2: 0 10px 24px -18px rgba(0, 0, 0, .7); /* a bar that has scrolled content under it */
+ --ov-elev-3: 0 14px 32px rgba(0, 0, 0, .4); /* a menu or popover above the page */
+ --ovk-radius: var(--ov-radius-2);
+ --ovk-radius-sm: var(--ov-radius-1);
--ovk-tap: 44px;
--ovk-tap: 44px;
--ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */
--ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */
--ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */
--ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */
--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
}
}
+/* Shape personalities (plan L45: "Paper is quiet and square, Neon glows and bounces"). A quiet look has square corners
+ * and lies flat, with a hairline where a shadow would be; a loud one is round and its raised things glow in its accent.
+ * The ids are Network's theme catalog (as ov-fx.js's motion feels); any other look keeps the defaults above. */
+:is([data-theme=paper], [data-theme=solarized-light], [data-theme=nord], [data-theme=nord-light], [data-theme=matrix], [data-theme=hacker], [data-theme=high-contrast]) {
+ --ov-radius-1: 4px; --ov-radius-2: 6px; --ov-radius-3: 10px;
+ --ov-elev-1: 0 0 0 1px var(--ovk-line);
+ --ov-elev-2: 0 1px 0 var(--ovk-line);
+ --ov-elev-3: 0 0 0 1px var(--ovk-line), 0 8px 18px -12px rgba(0, 0, 0, .35);
+}
+:is([data-theme=neon-tokyo], [data-theme=vapor]) {
+ --ov-radius-1: 14px; --ov-radius-2: 20px; --ov-radius-3: 28px;
+ --ov-elev-1: 0 0 8px color-mix(in srgb, var(--ovk-accent) 40%, transparent);
+ --ov-elev-2: 0 10px 28px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);
+ --ov-elev-3: 0 0 0 1px color-mix(in srgb, var(--ovk-accent) 40%, transparent), 0 18px 44px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);
+}
+
/* Heading and paragraph rules double their class (.ovk-section.ovk-section > h2): a site's own ".site h2" has the same
/* Heading and paragraph rules double their class (.ovk-section.ovk-section > h2): a site's own ".site h2" has the same
* specificity as ".ovk-section > h2" and loads later, so it used to restyle the kit's headings (Report, 2026-10-29). */
* specificity as ".ovk-section > h2" and loads later, so it used to restyle the kit's headings (Report, 2026-10-29). */
@@ -45,7 +69,7 @@
@@ -45,7 +69,7 @@
.ovk-page--wide { max-width: 1280px; }
.ovk-page--wide { max-width: 1280px; }
/* ── Buttons ── */
/* ── Buttons ── */
-.ovk-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 18px; border-radius: 12px; border: 1px solid transparent; font: inherit; font-weight: 700; font-size: .95rem; line-height: 1.2; text-decoration: none; cursor: pointer; transition: filter .12s, border-color .12s, background .12s; }
+.ovk-btn { display: inline-flex; align-items: center; justify-content: center; gap: 8px; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 18px; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid transparent; font: inherit; font-weight: 700; font-size: .95rem; line-height: 1.2; text-decoration: none; cursor: pointer; transition: filter .12s, border-color .12s, background .12s; }
.ovk-btn--primary { background: var(--ovk-fill); color: var(--ovk-on-fill); }
.ovk-btn--primary { background: var(--ovk-fill); color: var(--ovk-on-fill); }
.ovk-btn--primary:hover { filter: brightness(1.08); }
.ovk-btn--primary:hover { filter: brightness(1.08); }
.ovk-btn--quiet { background: var(--ovk-panel); color: var(--ovk-text); border-color: var(--ovk-line); }
.ovk-btn--quiet { background: var(--ovk-panel); color: var(--ovk-text); border-color: var(--ovk-line); }
@@ -56,7 +80,7 @@
@@ -56,7 +80,7 @@
.ovk-btn--small { min-height: 36px; padding: 6px 13px; border-radius: 999px; font-size: .88rem; }
.ovk-btn--small { min-height: 36px; padding: 6px 13px; border-radius: 999px; font-size: .88rem; }
.ovk-btn:focus-visible, .ovk-icon-btn:focus-visible, .ovk-go:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }
.ovk-btn:focus-visible, .ovk-icon-btn:focus-visible, .ovk-go:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }
.ovk-btn[disabled] { opacity: .55; cursor: default; filter: none; }
.ovk-btn[disabled] { opacity: .55; cursor: default; filter: none; }
-.ovk-icon-btn { display: inline-grid; place-items: center; box-sizing: border-box; width: 40px; height: 40px; flex: none; padding: 0; border-radius: 12px; border: 1px solid transparent; background: transparent; color: var(--ovk-muted); font: inherit; cursor: pointer; text-decoration: none; list-style: none; }
+.ovk-icon-btn { display: inline-grid; place-items: center; box-sizing: border-box; width: 40px; height: 40px; flex: none; padding: 0; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid transparent; background: transparent; color: var(--ovk-muted); font: inherit; cursor: pointer; text-decoration: none; list-style: none; }
.ovk-icon-btn::-webkit-details-marker { display: none; }
.ovk-icon-btn::-webkit-details-marker { display: none; }
.ovk-icon-btn:hover { color: var(--ovk-text); background: var(--ovk-panel-2); border-color: var(--ovk-line); }
.ovk-icon-btn:hover { color: var(--ovk-text); background: var(--ovk-panel-2); border-color: var(--ovk-line); }
.ovk-icon-btn[hidden] { display: none; }
.ovk-icon-btn[hidden] { display: none; }
@@ -74,7 +98,7 @@
@@ -74,7 +98,7 @@
/* ── The head: back, title and one line, icon actions; its tabs under it; sticky under the navbar ── */
/* ── The head: back, title and one line, icon actions; its tabs under it; sticky under the navbar ── */
.ovk-head { position: sticky; top: var(--ovk-top); z-index: 40; margin: 0 -16px 12px; padding: 6px 16px 0; background: color-mix(in srgb, var(--ovk-bg) 88%, transparent); -webkit-backdrop-filter: saturate(1.4) blur(14px); backdrop-filter: saturate(1.4) blur(14px); border-bottom: 1px solid transparent; transition: border-color .2s, box-shadow .2s; }
.ovk-head { position: sticky; top: var(--ovk-top); z-index: 40; margin: 0 -16px 12px; padding: 6px 16px 0; background: color-mix(in srgb, var(--ovk-bg) 88%, transparent); -webkit-backdrop-filter: saturate(1.4) blur(14px); backdrop-filter: saturate(1.4) blur(14px); border-bottom: 1px solid transparent; transition: border-color .2s, box-shadow .2s; }
-.ovk-scrolled .ovk-head { border-bottom-color: var(--ovk-line); box-shadow: 0 10px 24px -18px rgba(0, 0, 0, .7); }
+.ovk-scrolled .ovk-head { border-bottom-color: var(--ovk-line); box-shadow: var(--ov-elev-2); }
.ovk-head--static { position: static; background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; }
.ovk-head--static { position: static; background: transparent; -webkit-backdrop-filter: none; backdrop-filter: none; }
.ovk-head-row { display: flex; align-items: center; gap: 6px; min-height: 48px; padding-bottom: 6px; }
.ovk-head-row { display: flex; align-items: center; gap: 6px; min-height: 48px; padding-bottom: 6px; }
.ovk-head-row > .ovk-back { margin-left: -8px; }
.ovk-head-row > .ovk-back { margin-left: -8px; }
@@ -115,7 +139,7 @@
@@ -115,7 +139,7 @@
.ovk-row--unread .ovk-row-sub { color: var(--ovk-text); }
.ovk-row--unread .ovk-row-sub { color: var(--ovk-text); }
.ovk-row-trail { display: flex; align-items: center; gap: 2px; flex: none; }
.ovk-row-trail { display: flex; align-items: center; gap: 2px; flex: none; }
.ovk-row-trail form { display: flex; align-items: center; gap: 2px; margin: 0; }
.ovk-row-trail form { display: flex; align-items: center; gap: 2px; margin: 0; }
-.ovk-row-trail select { max-width: 9.5em; padding: 5px 6px; border-radius: 8px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .86rem; }
+.ovk-row-trail select { max-width: 9.5em; padding: 5px 6px; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .86rem; }
.ovk-mark { --ovk-mark: 40px; flex: none; display: inline-grid; place-items: center; width: var(--ovk-mark); height: var(--ovk-mark); box-sizing: border-box; border-radius: 50%; object-fit: cover; background: hsl(var(--ovk-hue, 215) 45% 38%); color: #fff; font-size: calc(var(--ovk-mark) * .34); font-weight: 800; letter-spacing: .02em; line-height: 1; }
.ovk-mark { --ovk-mark: 40px; flex: none; display: inline-grid; place-items: center; width: var(--ovk-mark); height: var(--ovk-mark); box-sizing: border-box; border-radius: 50%; object-fit: cover; background: hsl(var(--ovk-hue, 215) 45% 38%); color: #fff; font-size: calc(var(--ovk-mark) * .34); font-weight: 800; letter-spacing: .02em; line-height: 1; }
.ovk-mark--square { border-radius: 28%; }
.ovk-mark--square { border-radius: 28%; }
.ovk-empty-line { margin: 0 0 14px; padding: 14px; border: 1px dashed var(--ovk-line); border-radius: var(--ovk-radius); color: var(--ovk-muted); }
.ovk-empty-line { margin: 0 0 14px; padding: 14px; border: 1px dashed var(--ovk-line); border-radius: var(--ovk-radius); color: var(--ovk-muted); }
@@ -140,9 +164,9 @@
@@ -140,9 +164,9 @@
.ovk-fold--danger > summary { color: var(--ovk-bad); }
.ovk-fold--danger > summary { color: var(--ovk-bad); }
.ovk-more { position: relative; display: inline-block; }
.ovk-more { position: relative; display: inline-block; }
.ovk-more > summary { list-style: none; }
.ovk-more > summary { list-style: none; }
-.ovk-pop { position: absolute; right: 0; top: calc(100% + 4px); z-index: 60; min-width: 210px; padding: 6px; box-sizing: border-box; border-radius: 12px; border: 1px solid var(--ovk-line); background: var(--ovk-panel); box-shadow: 0 14px 32px rgba(0, 0, 0, .4); }
+.ovk-pop { position: absolute; right: 0; top: calc(100% + 4px); z-index: 60; min-width: 210px; padding: 6px; box-sizing: border-box; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid var(--ovk-line); background: var(--ovk-panel); box-shadow: var(--ov-elev-3); }
.ovk-pop form { margin: 0; }
.ovk-pop form { margin: 0; }
-.ovk-pop-item { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 40px; box-sizing: border-box; padding: 8px 10px; border: 0; border-radius: 8px; background: transparent; color: var(--ovk-text); font: inherit; font-weight: 600; text-align: left; text-decoration: none; cursor: pointer; }
+.ovk-pop-item { display: flex; align-items: center; gap: 10px; width: 100%; min-height: 40px; box-sizing: border-box; padding: 8px 10px; border: 0; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); background: transparent; color: var(--ovk-text); font: inherit; font-weight: 600; text-align: left; text-decoration: none; cursor: pointer; }
.ovk-pop-item:hover, .ovk-pop-item:focus-visible { background: var(--ovk-panel-2); outline: none; }
.ovk-pop-item:hover, .ovk-pop-item:focus-visible { background: var(--ovk-panel-2); outline: none; }
.ovk-pop-item--danger { color: var(--ovk-bad); }
.ovk-pop-item--danger { color: var(--ovk-bad); }
@@ -152,17 +176,17 @@
@@ -152,17 +176,17 @@
div.ovk-set-row { cursor: default; }
div.ovk-set-row { cursor: default; }
.ovk-set-text { flex: 1; min-width: 0; display: grid; gap: 2px; }
.ovk-set-text { flex: 1; min-width: 0; display: grid; gap: 2px; }
.ovk-set-text small { color: var(--ovk-muted); font-size: .8rem; }
.ovk-set-text small { color: var(--ovk-muted); font-size: .8rem; }
-.ovk-set-row select { flex: none; max-width: 52%; margin: 0; padding: 7px 8px; border-radius: 10px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .9rem; }
+.ovk-set-row select { flex: none; max-width: 52%; margin: 0; padding: 7px 8px; border-radius: var(--ov-radius-1); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: .9rem; }
.ovk-set-range input[type=range] { flex: 0 1 45%; min-width: 120px; margin: 0; accent-color: var(--ovk-fill); }
.ovk-set-range input[type=range] { flex: 0 1 45%; min-width: 120px; margin: 0; accent-color: var(--ovk-fill); }
.ovk-set-chips { flex-wrap: wrap; }
.ovk-set-chips { flex-wrap: wrap; }
.ovk-switch { appearance: none; -webkit-appearance: none; flex: none; position: relative; width: 44px; height: 26px; margin: 0; padding: 0; border-radius: 999px; border: 0; background: var(--ovk-line); cursor: pointer; transition: background .15s; }
.ovk-switch { appearance: none; -webkit-appearance: none; flex: none; position: relative; width: 44px; height: 26px; margin: 0; padding: 0; border-radius: 999px; border: 0; background: var(--ovk-line); cursor: pointer; transition: background .15s; }
-.ovk-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: 0 1px 3px rgba(0, 0, 0, .35); transition: transform .15s; }
+.ovk-switch::after { content: ''; position: absolute; top: 3px; left: 3px; width: 20px; height: 20px; border-radius: 50%; background: #fff; box-shadow: var(--ov-elev-1); transition: transform .15s; }
.ovk-switch:checked { background: var(--ovk-fill); }
.ovk-switch:checked { background: var(--ovk-fill); }
.ovk-switch:checked::after { transform: translateX(18px); }
.ovk-switch:checked::after { transform: translateX(18px); }
.ovk-switch:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }
.ovk-switch:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 2px; }
-.ovk-seg { display: inline-flex; flex: none; flex-wrap: wrap; max-width: 100%; gap: 2px; padding: 3px; border-radius: 10px; background: var(--ovk-panel-2); }
+.ovk-seg { display: inline-flex; flex: none; flex-wrap: wrap; max-width: 100%; gap: 2px; padding: 3px; border-radius: var(--ov-radius-1); background: var(--ovk-panel-2); }
.ovk-seg input, .ovk-chip input { position: absolute; opacity: 0; pointer-events: none; }
.ovk-seg input, .ovk-chip input { position: absolute; opacity: 0; pointer-events: none; }
-.ovk-seg span { display: block; padding: 6px 12px; border-radius: 8px; color: var(--ovk-muted); font-size: .86rem; font-weight: 650; cursor: pointer; }
+.ovk-seg span { display: block; padding: 6px 12px; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); color: var(--ovk-muted); font-size: .86rem; font-weight: 650; cursor: pointer; }
.ovk-seg input:checked + span { background: var(--ovk-fill); color: var(--ovk-on-fill); }
.ovk-seg input:checked + span { background: var(--ovk-fill); color: var(--ovk-on-fill); }
.ovk-seg input:focus-visible + span, .ovk-chip input:focus-visible + span { outline: 2px solid var(--ovk-accent); outline-offset: 1px; }
.ovk-seg input:focus-visible + span, .ovk-chip input:focus-visible + span { outline: 2px solid var(--ovk-accent); outline-offset: 1px; }
.ovk-chips { display: flex; flex-wrap: wrap; gap: 6px; }
.ovk-chips { display: flex; flex-wrap: wrap; gap: 6px; }
@@ -173,7 +197,7 @@ div.ovk-set-row { cursor: default; }
@@ -173,7 +197,7 @@ div.ovk-set-row { cursor: default; }
.ovk-form { display: grid; gap: 12px; margin: 0; }
.ovk-form { display: grid; gap: 12px; margin: 0; }
.ovk-form label { display: grid; gap: 5px; color: var(--ovk-muted); font-size: .9rem; }
.ovk-form label { display: grid; gap: 5px; color: var(--ovk-muted); font-size: .9rem; }
.ovk-form label.ovk-check { display: flex; align-items: flex-start; gap: 10px; color: var(--ovk-text); font-size: .95rem; }
.ovk-form label.ovk-check { display: flex; align-items: flex-start; gap: 10px; color: var(--ovk-text); font-size: .95rem; }
-.ovk-form input:not([type=checkbox]):not([type=radio]):not([type=range]), .ovk-form select, .ovk-form textarea { width: 100%; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 12px; border-radius: 10px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 1rem; }
+.ovk-form input:not([type=checkbox]):not([type=radio]):not([type=range]), .ovk-form select, .ovk-form textarea { width: 100%; box-sizing: border-box; min-height: var(--ovk-tap); padding: 10px 12px; border-radius: var(--ov-radius-1); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 1rem; }
.ovk-form textarea { min-height: 96px; resize: vertical; }
.ovk-form textarea { min-height: 96px; resize: vertical; }
.ovk-form input:focus-visible, .ovk-form select:focus-visible, .ovk-form textarea:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 0; border-color: transparent; }
.ovk-form input:focus-visible, .ovk-form select:focus-visible, .ovk-form textarea:focus-visible { outline: 2px solid var(--ovk-accent); outline-offset: 0; border-color: transparent; }
.ovk-form.ovk-form > p { margin: 0; }
.ovk-form.ovk-form > p { margin: 0; }
@@ -211,7 +235,7 @@ div.ovk-set-row { cursor: default; }
@@ -211,7 +235,7 @@ div.ovk-set-row { cursor: default; }
/* ── Numbers, empty states, notices, code, sheets ── */
/* ── Numbers, empty states, notices, code, sheets ── */
.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
.ovk-stats::-webkit-scrollbar { display: none; }
.ovk-stats::-webkit-scrollbar { display: none; }
-.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }
+.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: calc(var(--ov-radius-1) + 2px); background: var(--ovk-panel); }
.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }
.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }
.ovk-stats dt a { color: inherit; text-decoration: none; }
.ovk-stats dt a { color: inherit; text-decoration: none; }
/* A number that is a link: the whole tile is the target (the label's link stretched over it), never an underline. */
/* A number that is a link: the whole tile is the target (the label's link stretched over it), never an underline. */
@@ -225,7 +249,7 @@ div.ovk-set-row { cursor: default; }
@@ -225,7 +249,7 @@ div.ovk-set-row { cursor: default; }
.ovk-empty > .ovk-i { width: 28px; height: 28px; }
.ovk-empty > .ovk-i { width: 28px; height: 28px; }
.ovk-empty.ovk-empty p { margin: 0; max-width: 46ch; }
.ovk-empty.ovk-empty p { margin: 0; max-width: 46ch; }
.ovk-empty .ovk-empty-title { color: var(--ovk-text); font-weight: 750; }
.ovk-empty .ovk-empty-title { color: var(--ovk-text); font-weight: 750; }
-.ovk-notice { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 12px; padding: 10px 14px; border-radius: 12px; border: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); font-size: .92rem; }
+.ovk-notice { display: flex; align-items: flex-start; gap: 10px; margin: 0 0 12px; padding: 10px 14px; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); font-size: .92rem; }
.ovk-notice > .ovk-i { margin-top: 2px; }
.ovk-notice > .ovk-i { margin-top: 2px; }
.ovk-notice--ok { border-color: color-mix(in srgb, var(--ovk-ok) 45%, transparent); }
.ovk-notice--ok { border-color: color-mix(in srgb, var(--ovk-ok) 45%, transparent); }
.ovk-notice--ok > .ovk-i { color: var(--ovk-ok); }
.ovk-notice--ok > .ovk-i { color: var(--ovk-ok); }
@@ -235,8 +259,8 @@ div.ovk-set-row { cursor: default; }
@@ -235,8 +259,8 @@ div.ovk-set-row { cursor: default; }
.ovk-notice--info > .ovk-i { color: var(--ovk-accent); }
.ovk-notice--info > .ovk-i { color: var(--ovk-accent); }
.ovk-snip { position: relative; }
.ovk-snip { position: relative; }
.ovk-snip > .ovk-icon-btn { position: absolute; top: 6px; right: 6px; z-index: 1; width: 34px; height: 34px; background: var(--ovk-panel); border-color: var(--ovk-line); }
.ovk-snip > .ovk-icon-btn { position: absolute; top: 6px; right: 6px; z-index: 1; width: 34px; height: 34px; background: var(--ovk-panel); border-color: var(--ovk-line); }
-.ovk-snip > pre { max-height: 50vh; margin: 0 0 10px; padding: 12px 48px 12px 12px; overflow: auto; border-radius: 10px; border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font-size: .84rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
-.ovk-sheet { width: min(560px, 100vw); max-width: 100vw; max-height: 85dvh; margin: auto; padding: 0; box-sizing: border-box; border: 1px solid var(--ovk-line); border-radius: 18px; background: var(--ovk-panel); color: var(--ovk-text); overflow: hidden; }
+.ovk-snip > pre { max-height: 50vh; margin: 0 0 10px; padding: 12px 48px 12px 12px; overflow: auto; border-radius: var(--ov-radius-1); border: 1px solid var(--ovk-line); background: var(--ovk-input); color: var(--ovk-text); font-size: .84rem; line-height: 1.5; white-space: pre-wrap; overflow-wrap: anywhere; }
+.ovk-sheet { width: min(560px, 100vw); max-width: 100vw; max-height: 85dvh; margin: auto; padding: 0; box-sizing: border-box; border: 1px solid var(--ovk-line); border-radius: calc(var(--ov-radius-3) - 2px); background: var(--ovk-panel); color: var(--ovk-text); overflow: hidden; }
.ovk-sheet::backdrop { background: rgba(0, 0, 0, .55); }
.ovk-sheet::backdrop { background: rgba(0, 0, 0, .55); }
.ovk-sheet[open] { display: flex; flex-direction: column; }
.ovk-sheet[open] { display: flex; flex-direction: column; }
.ovk-sheet-head { display: flex; align-items: center; gap: 8px; padding: 8px 8px 8px 16px; border-bottom: 1px solid var(--ovk-line); }
.ovk-sheet-head { display: flex; align-items: center; gap: 8px; padding: 8px 8px 8px 16px; border-bottom: 1px solid var(--ovk-line); }
@@ -246,7 +270,7 @@ div.ovk-set-row { cursor: default; }
@@ -246,7 +270,7 @@ div.ovk-set-row { cursor: default; }
/* ── Phones (< 600 px): bottom sheets, full-width forms ── */
/* ── Phones (< 600 px): bottom sheets, full-width forms ── */
@media (max-width: 599px) {
@media (max-width: 599px) {
- .ovk-sheet { margin: auto 0 0; width: 100vw; border-radius: 18px 18px 0 0; border-bottom: 0; }
+ .ovk-sheet { margin: auto 0 0; width: 100vw; border-radius: calc(var(--ov-radius-3) - 2px) calc(var(--ov-radius-3) - 2px) 0 0; border-bottom: 0; }
.ovk-row > a, .ovk-row--static { padding-left: 12px; }
.ovk-row > a, .ovk-row--static { padding-left: 12px; }
.ovk-head-tools { gap: 0; }
.ovk-head-tools { gap: 0; }
}
}
@@ -281,16 +305,16 @@ div.ovk-set-row { cursor: default; }
@@ -281,16 +305,16 @@ div.ovk-set-row { cursor: default; }
.ovk-with-sidebar-main { min-width: 0; }
.ovk-with-sidebar-main { min-width: 0; }
.ovk-sidebar { position: sticky; top: var(--navbar-h, 52px); height: calc(100dvh - var(--navbar-h, 52px)); box-sizing: border-box; display: flex; flex-direction: column; gap: 8px; min-width: 0; padding: 12px 10px 10px; border-right: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); overflow: hidden; }
.ovk-sidebar { position: sticky; top: var(--navbar-h, 52px); height: calc(100dvh - var(--navbar-h, 52px)); box-sizing: border-box; display: flex; flex-direction: column; gap: 8px; min-width: 0; padding: 12px 10px 10px; border-right: 1px solid var(--ovk-line); background: var(--ovk-panel); color: var(--ovk-text); overflow: hidden; }
.ovk-sidebar-top { display: flex; align-items: center; gap: 6px; }
.ovk-sidebar-top { display: flex; align-items: center; gap: 6px; }
-.ovk-sidebar-action { flex: 1; display: flex; align-items: center; gap: 10px; min-height: 42px; padding: 0 12px; border-radius: 12px; border: 1px solid var(--ovk-line); background: var(--ovk-bg); color: var(--ovk-text); font-weight: 650; text-decoration: none; transition: border-color .18s, background .18s; }
+.ovk-sidebar-action { flex: 1; display: flex; align-items: center; gap: 10px; min-height: 42px; padding: 0 12px; border-radius: calc(var(--ov-radius-1) + 2px); border: 1px solid var(--ovk-line); background: var(--ovk-bg); color: var(--ovk-text); font-weight: 650; text-decoration: none; transition: border-color .18s, background .18s; }
.ovk-sidebar-action:hover { border-color: color-mix(in srgb, var(--ovk-accent) 55%, var(--ovk-line)); }
.ovk-sidebar-action:hover { border-color: color-mix(in srgb, var(--ovk-accent) 55%, var(--ovk-line)); }
.ovk-sidebar-action .ovk-i { width: 18px; height: 18px; color: var(--ovk-accent); }
.ovk-sidebar-action .ovk-i { width: 18px; height: 18px; color: var(--ovk-accent); }
.ovk-sidebar-close, .ovk-sidebar-open { display: none; }
.ovk-sidebar-close, .ovk-sidebar-open { display: none; }
.ovk-sidebar-find { position: relative; display: flex; align-items: center; margin: 0; }
.ovk-sidebar-find { position: relative; display: flex; align-items: center; margin: 0; }
.ovk-sidebar-find .ovk-i { position: absolute; left: 11px; width: 16px; height: 16px; color: var(--ovk-muted); pointer-events: none; }
.ovk-sidebar-find .ovk-i { position: absolute; left: 11px; width: 16px; height: 16px; color: var(--ovk-muted); pointer-events: none; }
-.ovk-sidebar-find input { width: 100%; min-height: 38px; box-sizing: border-box; padding: 0 12px 0 34px; border-radius: 10px; border: 1px solid transparent; background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 14px; }
+.ovk-sidebar-find input { width: 100%; min-height: 38px; box-sizing: border-box; padding: 0 12px 0 34px; border-radius: var(--ov-radius-1); border: 1px solid transparent; background: var(--ovk-input); color: var(--ovk-text); font: inherit; font-size: 14px; }
.ovk-sidebar-find input:focus { outline: none; border-color: var(--ovk-accent); }
.ovk-sidebar-find input:focus { outline: none; border-color: var(--ovk-accent); }
.ovk-sidebar-nav { display: grid; gap: 1px; padding-bottom: 8px; border-bottom: 1px solid var(--ovk-line); }
.ovk-sidebar-nav { display: grid; gap: 1px; padding-bottom: 8px; border-bottom: 1px solid var(--ovk-line); }
-.ovk-sidebar-link { display: flex; align-items: center; gap: 11px; min-height: 38px; padding: 0 10px; border-radius: 10px; color: var(--ovk-muted); font-size: 14.5px; font-weight: 600; text-decoration: none; transition: background .15s, color .15s; }
+.ovk-sidebar-link { display: flex; align-items: center; gap: 11px; min-height: 38px; padding: 0 10px; border-radius: var(--ov-radius-1); color: var(--ovk-muted); font-size: 14.5px; font-weight: 600; text-decoration: none; transition: background .15s, color .15s; }
.ovk-sidebar-link .ovk-i { width: 19px; height: 19px; flex: none; }
.ovk-sidebar-link .ovk-i { width: 19px; height: 19px; flex: none; }
.ovk-sidebar-link span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovk-sidebar-link span { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovk-sidebar-link:hover { background: var(--ovk-panel-2); color: var(--ovk-text); }
.ovk-sidebar-link:hover { background: var(--ovk-panel-2); color: var(--ovk-text); }
@@ -305,7 +329,7 @@ div.ovk-set-row { cursor: default; }
@@ -305,7 +329,7 @@ div.ovk-set-row { cursor: default; }
@keyframes ovk-sidebar-breathe { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.75); } }
@keyframes ovk-sidebar-breathe { 0%, 100% { opacity: 1; transform: scale(1); } 50% { opacity: .45; transform: scale(.75); } }
.ovk-sidebar-secs { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: 0 -4px; padding: 0 4px 8px; }
.ovk-sidebar-secs { flex: 1; min-height: 0; overflow-y: auto; overscroll-behavior: contain; margin: 0 -4px; padding: 0 4px 8px; }
.ovk-sidebar-h { margin: 12px 10px 4px; font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ovk-muted); }
.ovk-sidebar-h { margin: 12px 10px 4px; font-size: 11.5px; font-weight: 700; letter-spacing: .06em; text-transform: uppercase; color: var(--ovk-muted); }
-.ovk-sidebar-item { display: flex; align-items: center; gap: 9px; min-height: 36px; padding: 4px 10px; border-radius: 10px; color: var(--ovk-text); font-size: 14px; text-decoration: none; }
+.ovk-sidebar-item { display: flex; align-items: center; gap: 9px; min-height: 36px; padding: 4px 10px; border-radius: var(--ov-radius-1); color: var(--ovk-text); font-size: 14px; text-decoration: none; }
.ovk-sidebar-item:hover { background: var(--ovk-panel-2); }
.ovk-sidebar-item:hover { background: var(--ovk-panel-2); }
.ovk-sidebar-item.is-here { background: var(--ovk-panel-2); font-weight: 600; }
.ovk-sidebar-item.is-here { background: var(--ovk-panel-2); font-weight: 600; }
.ovk-sidebar-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovk-sidebar-t { flex: 1; min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
@@ -336,25 +360,25 @@ div.ovk-set-row { cursor: default; }
@@ -336,25 +360,25 @@ div.ovk-set-row { cursor: default; }
/* ── Placeholders shaped like the content (4.70.0, kit.skeleton; plan L52) ── A soft light passes over grey shapes the size of
/* ── Placeholders shaped like the content (4.70.0, kit.skeleton; plan L52) ── A soft light passes over grey shapes the size of
what is coming; nothing spins; still under reduced motion and Calm. A list paints these in its first HTML and fills them in
what is coming; nothing spins; still under reduced motion and Calm. A list paints these in its first HTML and fills them in
place. */
place. */
-.ovk-skel { display: block; border-radius: 8px; background: color-mix(in srgb, var(--text-primary, #e8edf3) 7%, transparent); background-image: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--text-primary, #e8edf3) 9%, transparent) 50%, transparent 70%); background-size: 220% 100%; background-repeat: no-repeat; animation: ovkShimmer 1.6s ease-in-out infinite; }
+.ovk-skel { display: block; border-radius: max(0px, calc(var(--ov-radius-1) - 2px)); background: color-mix(in srgb, var(--text-primary, #e8edf3) 7%, transparent); background-image: linear-gradient(100deg, transparent 30%, color-mix(in srgb, var(--text-primary, #e8edf3) 9%, transparent) 50%, transparent 70%); background-size: 220% 100%; background-repeat: no-repeat; animation: ovkShimmer 1.6s ease-in-out infinite; }
@keyframes ovkShimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }
@keyframes ovkShimmer { from { background-position: 120% 0; } to { background-position: -120% 0; } }
.ovk-skels { display: grid; gap: 12px; }
.ovk-skels { display: grid; gap: 12px; }
.ovk-skels-card { grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); }
.ovk-skels-card { grid-template-columns: repeat(auto-fill, minmax(min(220px, 100%), 1fr)); }
.ovk-skels-tile { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.ovk-skels-tile { grid-template-columns: repeat(auto-fill, minmax(96px, 1fr)); }
.ovk-skels-chip { display: flex; flex-wrap: wrap; gap: 8px; }
.ovk-skels-chip { display: flex; flex-wrap: wrap; gap: 8px; }
.ovk-skel-card { display: grid; gap: 8px; }
.ovk-skel-card { display: grid; gap: 8px; }
-.ovk-skel-thumb { aspect-ratio: 16 / 9; border-radius: 12px; }
+.ovk-skel-thumb { aspect-ratio: 16 / 9; border-radius: calc(var(--ov-radius-1) + 2px); }
.ovk-skel-l1 { height: 13px; width: 86%; }
.ovk-skel-l1 { height: 13px; width: 86%; }
.ovk-skel-l2 { height: 11px; width: 58%; }
.ovk-skel-l2 { height: 11px; width: 58%; }
.ovk-skel-row { display: flex; align-items: center; gap: 12px; }
.ovk-skel-row { display: flex; align-items: center; gap: 12px; }
.ovk-skel-avatar { flex: none; width: 40px; height: 40px; border-radius: 50%; }
.ovk-skel-avatar { flex: none; width: 40px; height: 40px; border-radius: 50%; }
.ovk-skel-lines { flex: 1; display: grid; gap: 7px; min-width: 0; }
.ovk-skel-lines { flex: 1; display: grid; gap: 7px; min-width: 0; }
-.ovk-skel-tile { aspect-ratio: 1; border-radius: 14px; }
+.ovk-skel-tile { aspect-ratio: 1; border-radius: var(--ov-radius-2); }
.ovk-skel-chip { width: 84px; height: 30px; border-radius: 999px; }
.ovk-skel-chip { width: 84px; height: 30px; border-radius: 999px; }
.ovk-skel-line { height: 12px; }
.ovk-skel-line { height: 12px; }
.ovk-skel-chat { display: flex; align-items: center; gap: 8px; }
.ovk-skel-chat { display: flex; align-items: center; gap: 8px; }
.ovk-skel-dot { flex: none; width: 22px; height: 22px; border-radius: 50%; }
.ovk-skel-dot { flex: none; width: 22px; height: 22px; border-radius: 50%; }
-.ovk-skel-stat { display: grid; gap: 6px; padding: 12px; border-radius: 14px; border: 1px solid var(--border, rgba(255,255,255,.08)); }
+.ovk-skel-stat { display: grid; gap: 6px; padding: 12px; border-radius: var(--ov-radius-2); border: 1px solid var(--border, rgba(255,255,255,.08)); }
.ovk-skel-num { height: 22px; width: 48%; }
.ovk-skel-num { height: 22px; width: 48%; }
@media (prefers-reduced-motion: reduce) { .ovk-skel { animation: none; } }
@media (prefers-reduced-motion: reduce) { .ovk-skel { animation: none; } }
html[data-ov-motion=reduced] .ovk-skel { animation: none; }
html[data-ov-motion=reduced] .ovk-skel { animation: none; }
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.80.0",
+ "version": "4.81.0",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site",
"main": "index.js",
"main": "index.js",
"bin": {
"bin": {
@@ -22,16 +22,39 @@
@@ -22,16 +22,39 @@
--ovk-warn: var(--warning, #f59e0b);
--ovk-warn: var(--warning, #f59e0b);
--ovk-bad: var(--danger, var(--error, #ef4444));
--ovk-bad: var(--danger, var(--error, #ef4444));
--ovk-live: var(--live-red, #ef4444);
--ovk-live: var(--live-red, #ef4444);
- --ovk-radius: 14px;
- --ovk-radius-sm: 10px;
+ /* Shape and elevation (4.81.0, plan L45 step 2): the kit's corners and shadows as tokens a look can set, so a theme
+ * has a shape as well as colours. The defaults are the kit's own corners and shadows; the personalities follow. */
+ --ov-radius-1: 10px; /* chips, inputs, rows, small buttons */
+ --ov-radius-2: 14px; /* cards, sections, panels */
+ --ov-radius-3: 20px; /* sheets, dialogs, big surfaces */
+ --ov-elev-1: 0 1px 3px rgba(0, 0, 0, .35); /* a knob, a chip that sits on its surface */
+ --ov-elev-2: 0 10px 24px -18px rgba(0, 0, 0, .7); /* a bar that has scrolled content under it */
+ --ov-elev-3: 0 14px 32px rgba(0, 0, 0, .4); /* a menu or popover above the page */
+ --ovk-radius: var(--ov-radius-2);
+ --ovk-radius-sm: var(--ov-radius-1);
--ovk-tap: 44px;
--ovk-tap: 44px;
--ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */
--ovk-top: var(--ovnav-real-h, 52px); /* the navbar's height: sticky things stop under it */
--ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */
--ovk-bottom: var(--ovnav-appbar-h, 0px); /* the app bar's height on phones (navbar.js sets it) */
--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);
}
}
+/* Shape personalities, as in kit.css but at zero specificity (plan L45: "Paper is quiet and square, Neon glows and bounces"). A quiet look has square corners
+ * and lies flat, with a hairline where a shadow would be; a loud one is round and its raised things glow in its accent.
+ * The ids are Network's theme catalog (as ov-fx.js's motion feels); any other look keeps the defaults above. */
+:where([data-theme=paper], [data-theme=solarized-light], [data-theme=nord], [data-theme=nord-light], [data-theme=matrix], [data-theme=hacker], [data-theme=high-contrast]) {
+ --ov-radius-1: 4px; --ov-radius-2: 6px; --ov-radius-3: 10px;
+ --ov-elev-1: 0 0 0 1px var(--ovk-line);
+ --ov-elev-2: 0 1px 0 var(--ovk-line);
+ --ov-elev-3: 0 0 0 1px var(--ovk-line), 0 8px 18px -12px rgba(0, 0, 0, .35);
+}
+:where([data-theme=neon-tokyo], [data-theme=vapor]) {
+ --ov-radius-1: 14px; --ov-radius-2: 20px; --ov-radius-3: 28px;
+ --ov-elev-1: 0 0 8px color-mix(in srgb, var(--ovk-accent) 40%, transparent);
+ --ov-elev-2: 0 10px 28px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);
+ --ov-elev-3: 0 0 0 1px color-mix(in srgb, var(--ovk-accent) 40%, transparent), 0 18px 44px -14px color-mix(in srgb, var(--ovk-accent) 55%, transparent);
+}
.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
.ovk-stats { position: relative; display: flex; gap: 8px; margin: 12px -16px; padding: 0 16px; overflow-x: auto; scrollbar-width: none; }
.ovk-stats::-webkit-scrollbar { display: none; }
.ovk-stats::-webkit-scrollbar { display: none; }
-.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: 12px; background: var(--ovk-panel); }
+.ovk-stats > div { display: flex; flex-direction: column-reverse; flex: 0 0 auto; min-width: 104px; box-sizing: border-box; padding: 8px 14px; border: 1px solid var(--ovk-line); border-radius: calc(var(--ov-radius-1) + 2px); background: var(--ovk-panel); }
.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }
.ovk-stats dt { margin: 0; color: var(--ovk-muted); font-size: .76rem; line-height: 1.25; }
.ovk-stats dt a { color: inherit; text-decoration: none; }
.ovk-stats dt a { color: inherit; text-decoration: none; }
.ovk-stat--link { position: relative; transition: border-color .15s; }
.ovk-stat--link { position: relative; transition: border-color .15s; }
new file mode 100644
new file mode 100644
@@ -0,0 +1,29 @@
@@ -0,0 +1,29 @@
+'use strict';
+/**
+ * Shape and elevation tokens (4.81.0, plan L45 step 2): the kit's corners and shadows are --ov-radius-1..3 and
+ * --ov-elev-1..3, with the kit's own values as defaults, so a look can give a theme a shape. Paper and the quiet looks
+ * are square and flat, Neon round and glowing. room.css carries the same tokens and, at zero specificity, the same
+ * personalities (it can load without kit.css).
+ */
+const assert = require('assert');
+const fs = require('fs');
+const path = require('path');
+
+const kit = fs.readFileSync(path.join(__dirname, '..', 'kit.css'), 'utf8');
+const room = fs.readFileSync(path.join(__dirname, '..', 'room.css'), 'utf8');
+
+assert.ok(kit.includes('--ov-radius-1: 10px;'), 'radius-1 defaults to the kit\'s own 10px');
+assert.ok(kit.includes('--ov-radius-2: 14px;'), 'radius-2 defaults to the kit\'s own 14px');
+assert.ok(kit.includes('--ov-radius-3: 20px;'), 'radius-3 is 20px');
+assert.match(kit, /--ov-elev-1: [^;]+;/);
+assert.match(kit, /--ov-elev-2: [^;]+;/);
+assert.match(kit, /--ov-elev-3: [^;]+;/);
+assert.ok(kit.includes('--ovk-radius: var(--ov-radius-2);') && kit.includes('--ovk-radius-sm: var(--ov-radius-1);'), 'the kit\'s own radius names follow the tokens');
+assert.doesNotMatch(kit, /border-radius: (8|10|12|14|18|20)px/, 'no corner of the kit is a literal any more (pills and circles stay)');
+assert.ok(kit.includes('box-shadow: var(--ov-elev-3);') && kit.includes('box-shadow: var(--ov-elev-2);') && kit.includes('box-shadow: var(--ov-elev-1);'), 'the kit\'s shadows are the elevation tokens');
+assert.match(kit, /:is\(\[data-theme=paper\][^{]*\) \{\s*--ov-radius-1: 4px; --ov-radius-2: 6px; --ov-radius-3: 10px;/, 'Paper is square');
+assert.match(kit, /:is\(\[data-theme=neon-tokyo\][^{]*\) \{\s*--ov-radius-1: 14px; --ov-radius-2: 20px; --ov-radius-3: 28px;/, 'Neon is round');
+assert.match(kit, /--ov-elev-3: 0 0 0 1px color-mix\(in srgb, var\(--ovk-accent\)/, 'and Neon\'s raised things glow in its accent');
+assert.match(room, /:where\(\[data-theme=paper\][^{]*\) \{\s*--ov-radius-1: 4px;/, 'room.css: the same personalities at zero specificity');
+assert.match(room, /:where\(\[data-theme=neon-tokyo\][^{]*\) \{\s*--ov-radius-1: 14px;/);
+console.log('kit-shape: tokens, the kit on them, and the personalities');