openvibers/openvibe.shared Commits
4.36.1: charts never widen a phone page, and kit headings hold their look on every site (#173)
Changes
@@@ -6,6 +6,17 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).## Unreleased++## 4.36.1 — 2026-10-29++++- **Kit 2 charts:** a scatter label right of centre could run past the chart and widen a phone page (a 390 px++ phone laid the page out at 601 px). The chart clips to its own box, and kit-web.js moves a label that would cross the++ right edge to the left of its dot. The real cause on Report was the screen-reader copy of the numbers: a table++ cannot shrink below its content, so even visually hidden it set the page's width; it now sits in a hidden box.++- **Kit headings hold their look on every site:** a site rule such as `.rp h2` had the same specificity as the kit's++ `.ovk-section > h2` and loads later, so it restyled kit headings (Report showed a section title at display size).++ The kit's heading and paragraph rules now double their class (`.ovk-section.ovk-section > h2`, `.ovk-card .ovk-card-title`),++ which outranks any one-class site rule. No markup changed.++## 4.36.0 — 2026-10-29- **Kit 2:** the pieces data and tool pages need, in `kit.js`/`kit.css` (`kit-web.js` adds their behaviour):@@@ -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.36.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed++**Status:** alpha, v4.36.1 (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.36.0",++ "version": "4.36.1","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)",@@@ -201,7 +201,10 @@const names = Array.prototype.slice.call(plot.querySelectorAll('.ovk-chart-name'));if (names.length < 2) return;const place = () => {-- names.forEach((n) => n.classList.remove('is-hidden'));++ names.forEach((n) => { n.classList.remove('is-hidden'); n.parentNode.classList.remove('is-flip'); });++ // A label that would run past the plot's right edge goes to the left of its dot.++ const edge = plot.getBoundingClientRect().right + 4;++ names.forEach((n) => { if (!n.parentNode.classList.contains('is-right') && n.getBoundingClientRect().right > edge) n.parentNode.classList.add('is-flip'); });const order = names.slice().sort((a, b) => (b.parentNode.classList.contains('is-best') - a.parentNode.classList.contains('is-best')) || (parseFloat(b.parentNode.style.bottom) - parseFloat(a.parentNode.style.bottom)));const kept = [];order.forEach((n) => {@@@ -33,6 +33,9 @@--ovk-font: var(--ov-font, 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif);}++/* 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). */++/* ── Basics ── */.ovk-i { width: 1.15em; height: 1.15em; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vertical-align: -.2em; }.ovk-sr { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }@@@ -76,7 +79,7 @@.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-id { flex: 1; min-width: 0; }--.ovk-head-id h1, .ovk-head-id h2 { margin: 0; color: var(--ovk-text); font-family: var(--ovk-font); font-size: clamp(1.15rem, 4.2vw, 1.5rem); font-weight: 800; letter-spacing: -.01em; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }++.ovk-head-id.ovk-head-id h1, .ovk-head-id.ovk-head-id h2 { margin: 0; color: var(--ovk-text); font-family: var(--ovk-font); font-size: clamp(1.15rem, 4.2vw, 1.5rem); font-weight: 800; letter-spacing: -.01em; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ovk-sub { margin: 2px 0 0; color: var(--ovk-muted); font-size: .86rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }.ovk-head-tools { display: flex; align-items: center; gap: 2px; margin-right: -8px; }.ovk-head-tools form { margin: 0; }@@@ -90,7 +93,7 @@/* ── Lists: rows that read like a phone's (a mark, the name and time on one line, one line under it) ── */.ovk-list-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 18px 0 8px; }--.ovk-list-head h2 { margin: 0; color: var(--ovk-muted); font-family: inherit; font-size: .76rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }++.ovk-list-head.ovk-list-head h2 { margin: 0; color: var(--ovk-muted); font-family: inherit; font-size: .76rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }.ovk-list-head a { color: var(--ovk-accent); font-size: .84rem; font-weight: 700; text-decoration: none; }.ovk-list { list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); margin: 0 0 14px; padding: 0; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); overflow: hidden; }.ovk-list--flush { margin: 0; border: 0; border-radius: 0; border-top: 1px solid var(--ovk-line); background: transparent; }@@@ -117,7 +120,7 @@/* ── Sections (settings groups, a page's parts) and folds ── */.ovk-section { margin: 0 0 12px; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); overflow: hidden; color: var(--ovk-text); }--.ovk-section > h2 { display: flex; align-items: center; gap: 6px; margin: 0; padding: 12px 14px 6px; color: var(--ovk-muted); font-family: inherit; font-size: .74rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }++.ovk-section.ovk-section > h2 { display: flex; align-items: center; gap: 6px; margin: 0; padding: 12px 14px 6px; color: var(--ovk-muted); font-family: inherit; font-size: .74rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }.ovk-count { color: var(--ovk-text); font-weight: 800; letter-spacing: 0; }.ovk-note { margin: 0; padding: 0 14px 10px; color: var(--ovk-muted); font-size: .86rem; }.ovk-section > .ovk-form, .ovk-section > form:not(.ovk-find):not(.ovk-inline) { padding: 4px 14px 14px; }@@@ -171,7 +174,7 @@ div.ovk-set-row { cursor: default; }.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 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 > p { margin: 0; }++.ovk-form.ovk-form > p { margin: 0; }/* A settings row inside a form stays a row: the label on the left, its control (not stretched) on the right. */.ovk-form label.ovk-set-row { display: flex; color: var(--ovk-text); font-size: 1rem; }.ovk-form .ovk-set-row select { width: auto; min-height: 0; padding: 7px 8px; font-size: .9rem; }@@@ -212,7 +215,7 @@ div.ovk-set-row { cursor: default; }.ovk-stats dd { margin: 0; color: var(--ovk-text); font-size: 1.2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.2; }.ovk-empty { display: grid; justify-items: center; gap: 8px; margin: 0 0 14px; padding: 24px 16px; border: 1px dashed var(--ovk-line); border-radius: var(--ovk-radius); color: var(--ovk-muted); text-align: center; }.ovk-empty > .ovk-i { width: 28px; height: 28px; }--.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-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 > .ovk-i { margin-top: 2px; }@@@ -229,7 +232,7 @@ div.ovk-set-row { cursor: default; }.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); }--.ovk-sheet-head h2 { flex: 1; margin: 0; font-size: 1.05rem; }++.ovk-sheet-head.ovk-sheet-head h2 { flex: 1; margin: 0; font-size: 1.05rem; }.ovk-sheet-body { padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px)); overflow-y: auto; }/* ── Phones (< 600 px): bottom sheets, full-width forms ── */@@@ -317,7 +320,7 @@ div.ovk-set-row { cursor: default; }}/* Charts: the lines are an SVG stretched to the box; every label is real text at its real size */--.ovk-chart { margin: 0 0 14px; padding: 14px 14px 10px; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); color: var(--ovk-text); font-family: var(--ovk-font); }++.ovk-chart { overflow: clip; margin: 0 0 14px; padding: 14px 14px 10px; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); color: var(--ovk-text); font-family: var(--ovk-font); }.ovk-chart figcaption { display: grid; gap: 2px; margin: 0 0 8px; }.ovk-chart figcaption strong { font-size: 1rem; font-weight: 800; }.ovk-chart figcaption span { color: var(--ovk-muted); font-size: .84rem; }@@@ -353,7 +356,7 @@ div.ovk-set-row { cursor: default; }.ovk-chart-dot.is-right .ovk-chart-pop { left: auto; right: -6px; transform: none; }.ovk-chart-name.is-hidden { visibility: hidden; }.ovk-chart-name { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--ovk-text); font-size: .74rem; font-weight: 600; white-space: nowrap; pointer-events: none; text-shadow: 0 0 3px var(--ovk-panel), 0 0 3px var(--ovk-panel); }--.ovk-chart-dot.is-right .ovk-chart-name { left: auto; right: 14px; }++.ovk-chart-dot.is-right .ovk-chart-name, .ovk-chart-dot.is-flip .ovk-chart-name { left: auto; right: 14px; }.ovk-chart-bars { position: absolute; inset: 0; display: flex; align-items: flex-end; gap: clamp(1px, calc(12% / var(--n, 10)), 6px); }.ovk-chart-bar { position: relative; flex: 1 1 0; min-width: 2px; height: var(--h, 0%); border-radius: 3px 3px 0 0; background: var(--ovk-c1); outline: none; }.ovk-chart-bar:hover, .ovk-chart-bar:focus-visible { filter: brightness(1.15); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ovk-accent) 45%, transparent); }@@@ -414,7 +417,7 @@ div.ovk-set-row { cursor: default; }.ovk-receipt--ok { border-color: color-mix(in srgb, var(--ovk-ok) 45%, var(--ovk-line)); }.ovk-receipt--bad { border-color: color-mix(in srgb, var(--ovk-bad) 45%, var(--ovk-line)); }.ovk-receipt-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 8px 0 14px; min-height: 44px; }--.ovk-receipt-head h2 { margin: 0; font-size: 1rem; font-weight: 800; }++.ovk-receipt-head.ovk-receipt-head h2 { margin: 0; font-size: 1rem; font-weight: 800; }.ovk-receipt-acts { display: inline-flex; gap: 2px; }.ovk-receipt-body { padding: 10px 14px 14px; line-height: 1.5; overflow-wrap: anywhere; }.ovk-receipt-body > :first-child { margin-top: 0; } .ovk-receipt-body > :last-child { margin-bottom: 0; }@@@ -576,7 +576,7 @@const axisNames = yo.label ? `<span class="ovk-chart-yname" aria-hidden="true">${esc(yo.label)}</span>` : '';return `<figure class="ovk-chart ovk-chart--${kind}"${id ? ` id="${esc(id)}"` : ''} style="--ovk-chart-h:${Math.max(120, Math.min(600, Number(height) || 220))}px;--ovk-chart-g:${gutter}px">${cap}${legend}${axisNames}`+ `<div class="ovk-chart-plot"${data ? ` tabindex="0" role="img" aria-label="${esc(name)}" data-ovk-chart="${esc(JSON.stringify(data))}"` : ` role="group" aria-label="${esc(name)}"`}>${plot}${data ? '<span class="ovk-chart-rule" hidden></span><span class="ovk-chart-tip" hidden></span>' : ''}</div>`-- + `${xo.label ? `<p class="ovk-chart-xname" aria-hidden="true">${esc(xo.label)}</p>` : ''}<table class="ovk-sr"><caption>${esc(name)}</caption>${table}</table></figure>`;++ + `${xo.label ? `<p class="ovk-chart-xname" aria-hidden="true">${esc(xo.label)}</p>` : ''}<div class="ovk-sr"><table><caption>${esc(name)}</caption>${table}</table></div></figure>`;}/** The grid and the tick labels: x [[pos%, text]], y [[pos%, text]] — real text, placed by percent. */function axes(xt, yt) {@@@ -1,12 +1,12 @@{"name": "openvibe-shared",-- "version": "4.36.0",++ "version": "4.36.1","lockfileVersion": 3,"requires": true,"packages": {"": {"name": "openvibe-shared",-- "version": "4.36.0",++ "version": "4.36.1","license": "MIT","dependencies": {"jsonwebtoken": "^9.0.0",@@@ -1,6 +1,6 @@{"name": "openvibe-shared",-- "version": "4.36.0",++ "version": "4.36.1","description": "Shared navbar, footer, themes, icons, legal pages, SEO, auth and analytics for every OpenVibe site","main": "index.js","bin": {@@@ -27,7 +27,7 @@.ovk-card-body { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }.ovk-card-body > .ovk-mark { flex: none; }.ovk-card-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }--.ovk-card-title { margin: 0; font-size: 15px; font-weight: 700; line-height: 1.3; letter-spacing: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }++.ovk-card .ovk-card-title { margin: 0; font-size: 15px; font-weight: 700; line-height: 1.3; letter-spacing: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }.ovk-card-link { color: inherit; text-decoration: none; transition: color .2s; }.ovk-card-link::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: var(--ovk-radius); }.ovk-card-link:focus-visible { outline: none; }@@@ -48,7 +48,7 @@/* ── Rails (kit.rail): a row that scrolls sideways and snaps ── */.ovk-rail { margin: 26px 0 0; min-width: 0; --ovk-rail-bleed: 0px; }.ovk-rail-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0 0 12px; }--.ovk-rail-head h2 { margin: 0; font-size: 1.15rem; font-weight: 800; letter-spacing: -.01em; }++.ovk-rail-head.ovk-rail-head h2 { margin: 0; font-size: 1.15rem; font-weight: 800; letter-spacing: -.01em; }.ovk-rail-more { display: inline-flex; align-items: center; gap: 2px; min-height: 32px; color: var(--room-strong, var(--ovk-accent)); font-weight: 700; font-size: .9rem; text-decoration: none; white-space: nowrap; }.ovk-rail-more:hover { text-decoration: underline; }.ovk-rail-wrap { position: relative; }@@@ -155,7 +155,7 @@ const kit = require('../kit');assert.ok(line.includes('class="ovk-chart-yt"') && line.includes('$12.00'), 'y ticks are real text, formatted');assert.ok(line.includes('>Sep 1<') || line.includes('>Sep 2<'), 'x ticks fall on days');assert.ok(line.includes('Price <x> <b>$9.99</b>'), 'the legend says the latest value, escaped');-- assert.ok(line.includes('<table class="ovk-sr"><caption>Price</caption>'), 'a table with the same numbers for screen readers');++ assert.ok(line.includes('<div class="ovk-sr"><table><caption>Price</caption>'), 'a table with the same numbers for screen readers, in a box that can be 1 px wide (a table cannot)');const raw = line.match(/data-ovk-chart="([^"]*)"/)[1].replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, "'").replace(/&/g, '&');const data = JSON.parse(raw);assert.strictEqual(data.x.length, 4);
@@ -6,6 +6,17 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
@@ -6,6 +6,17 @@ A release is the git tag `vX.Y.Z`; consumers pin the tag's tarball (see README).
## Unreleased
## Unreleased
+## 4.36.1 — 2026-10-29
+
+- **Kit 2 charts:** a scatter label right of centre could run past the chart and widen a phone page (a 390 px
+ phone laid the page out at 601 px). The chart clips to its own box, and kit-web.js moves a label that would cross the
+ right edge to the left of its dot. The real cause on Report was the screen-reader copy of the numbers: a table
+ cannot shrink below its content, so even visually hidden it set the page's width; it now sits in a hidden box.
+- **Kit headings hold their look on every site:** a site rule such as `.rp h2` had the same specificity as the kit's
+ `.ovk-section > h2` and loads later, so it restyled kit headings (Report showed a section title at display size).
+ The kit's heading and paragraph rules now double their class (`.ovk-section.ovk-section > h2`, `.ovk-card .ovk-card-title`),
+ which outranks any one-class site rule. No markup changed.
+
## 4.36.0 — 2026-10-29
## 4.36.0 — 2026-10-29
- **Kit 2:** the pieces data and tool pages need, in `kit.js`/`kit.css` (`kit-web.js` adds their behaviour):
- **Kit 2:** the pieces data and tool pages need, in `kit.js`/`kit.css` (`kit-web.js` adds their behaviour):
@@ -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.36.0 (every release is a tag; see [CHANGELOG.md](CHANGELOG.md)). Every deployed
+**Status:** alpha, v4.36.1 (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.36.0",
+ "version": "4.36.1",
"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)",
@@ -201,7 +201,10 @@
@@ -201,7 +201,10 @@
const names = Array.prototype.slice.call(plot.querySelectorAll('.ovk-chart-name'));
const names = Array.prototype.slice.call(plot.querySelectorAll('.ovk-chart-name'));
if (names.length < 2) return;
if (names.length < 2) return;
const place = () => {
const place = () => {
- names.forEach((n) => n.classList.remove('is-hidden'));
+ names.forEach((n) => { n.classList.remove('is-hidden'); n.parentNode.classList.remove('is-flip'); });
+ // A label that would run past the plot's right edge goes to the left of its dot.
+ const edge = plot.getBoundingClientRect().right + 4;
+ names.forEach((n) => { if (!n.parentNode.classList.contains('is-right') && n.getBoundingClientRect().right > edge) n.parentNode.classList.add('is-flip'); });
const order = names.slice().sort((a, b) => (b.parentNode.classList.contains('is-best') - a.parentNode.classList.contains('is-best')) || (parseFloat(b.parentNode.style.bottom) - parseFloat(a.parentNode.style.bottom)));
const order = names.slice().sort((a, b) => (b.parentNode.classList.contains('is-best') - a.parentNode.classList.contains('is-best')) || (parseFloat(b.parentNode.style.bottom) - parseFloat(a.parentNode.style.bottom)));
const kept = [];
const kept = [];
order.forEach((n) => {
order.forEach((n) => {
@@ -33,6 +33,9 @@
@@ -33,6 +33,9 @@
--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);
}
}
+/* 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). */
+
/* ── Basics ── */
/* ── Basics ── */
.ovk-i { width: 1.15em; height: 1.15em; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vertical-align: -.2em; }
.ovk-i { width: 1.15em; height: 1.15em; flex: none; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; vertical-align: -.2em; }
.ovk-sr { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
.ovk-sr { position: absolute !important; width: 1px; height: 1px; padding: 0; margin: -1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0; }
@@ -76,7 +79,7 @@
@@ -76,7 +79,7 @@
.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; }
.ovk-head-id { flex: 1; min-width: 0; }
.ovk-head-id { flex: 1; min-width: 0; }
-.ovk-head-id h1, .ovk-head-id h2 { margin: 0; color: var(--ovk-text); font-family: var(--ovk-font); font-size: clamp(1.15rem, 4.2vw, 1.5rem); font-weight: 800; letter-spacing: -.01em; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
+.ovk-head-id.ovk-head-id h1, .ovk-head-id.ovk-head-id h2 { margin: 0; color: var(--ovk-text); font-family: var(--ovk-font); font-size: clamp(1.15rem, 4.2vw, 1.5rem); font-weight: 800; letter-spacing: -.01em; line-height: 1.2; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovk-sub { margin: 2px 0 0; color: var(--ovk-muted); font-size: .86rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovk-sub { margin: 2px 0 0; color: var(--ovk-muted); font-size: .86rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.ovk-head-tools { display: flex; align-items: center; gap: 2px; margin-right: -8px; }
.ovk-head-tools { display: flex; align-items: center; gap: 2px; margin-right: -8px; }
.ovk-head-tools form { margin: 0; }
.ovk-head-tools form { margin: 0; }
@@ -90,7 +93,7 @@
@@ -90,7 +93,7 @@
/* ── Lists: rows that read like a phone's (a mark, the name and time on one line, one line under it) ── */
/* ── Lists: rows that read like a phone's (a mark, the name and time on one line, one line under it) ── */
.ovk-list-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 18px 0 8px; }
.ovk-list-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin: 18px 0 8px; }
-.ovk-list-head h2 { margin: 0; color: var(--ovk-muted); font-family: inherit; font-size: .76rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
+.ovk-list-head.ovk-list-head h2 { margin: 0; color: var(--ovk-muted); font-family: inherit; font-size: .76rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
.ovk-list-head a { color: var(--ovk-accent); font-size: .84rem; font-weight: 700; text-decoration: none; }
.ovk-list-head a { color: var(--ovk-accent); font-size: .84rem; font-weight: 700; text-decoration: none; }
.ovk-list { list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); margin: 0 0 14px; padding: 0; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); overflow: hidden; }
.ovk-list { list-style: none; display: grid; grid-template-columns: minmax(0, 1fr); margin: 0 0 14px; padding: 0; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); overflow: hidden; }
.ovk-list--flush { margin: 0; border: 0; border-radius: 0; border-top: 1px solid var(--ovk-line); background: transparent; }
.ovk-list--flush { margin: 0; border: 0; border-radius: 0; border-top: 1px solid var(--ovk-line); background: transparent; }
@@ -117,7 +120,7 @@
@@ -117,7 +120,7 @@
/* ── Sections (settings groups, a page's parts) and folds ── */
/* ── Sections (settings groups, a page's parts) and folds ── */
.ovk-section { margin: 0 0 12px; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); overflow: hidden; color: var(--ovk-text); }
.ovk-section { margin: 0 0 12px; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); overflow: hidden; color: var(--ovk-text); }
-.ovk-section > h2 { display: flex; align-items: center; gap: 6px; margin: 0; padding: 12px 14px 6px; color: var(--ovk-muted); font-family: inherit; font-size: .74rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
+.ovk-section.ovk-section > h2 { display: flex; align-items: center; gap: 6px; margin: 0; padding: 12px 14px 6px; color: var(--ovk-muted); font-family: inherit; font-size: .74rem; font-weight: 800; letter-spacing: .06em; text-transform: uppercase; }
.ovk-count { color: var(--ovk-text); font-weight: 800; letter-spacing: 0; }
.ovk-count { color: var(--ovk-text); font-weight: 800; letter-spacing: 0; }
.ovk-note { margin: 0; padding: 0 14px 10px; color: var(--ovk-muted); font-size: .86rem; }
.ovk-note { margin: 0; padding: 0 14px 10px; color: var(--ovk-muted); font-size: .86rem; }
.ovk-section > .ovk-form, .ovk-section > form:not(.ovk-find):not(.ovk-inline) { padding: 4px 14px 14px; }
.ovk-section > .ovk-form, .ovk-section > form:not(.ovk-find):not(.ovk-inline) { padding: 4px 14px 14px; }
@@ -171,7 +174,7 @@ div.ovk-set-row { cursor: default; }
@@ -171,7 +174,7 @@ div.ovk-set-row { cursor: default; }
.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: 10px; 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 > p { margin: 0; }
+.ovk-form.ovk-form > p { margin: 0; }
/* A settings row inside a form stays a row: the label on the left, its control (not stretched) on the right. */
/* A settings row inside a form stays a row: the label on the left, its control (not stretched) on the right. */
.ovk-form label.ovk-set-row { display: flex; color: var(--ovk-text); font-size: 1rem; }
.ovk-form label.ovk-set-row { display: flex; color: var(--ovk-text); font-size: 1rem; }
.ovk-form .ovk-set-row select { width: auto; min-height: 0; padding: 7px 8px; font-size: .9rem; }
.ovk-form .ovk-set-row select { width: auto; min-height: 0; padding: 7px 8px; font-size: .9rem; }
@@ -212,7 +215,7 @@ div.ovk-set-row { cursor: default; }
@@ -212,7 +215,7 @@ div.ovk-set-row { cursor: default; }
.ovk-stats dd { margin: 0; color: var(--ovk-text); font-size: 1.2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.2; }
.ovk-stats dd { margin: 0; color: var(--ovk-text); font-size: 1.2rem; font-weight: 800; font-variant-numeric: tabular-nums; line-height: 1.2; }
.ovk-empty { display: grid; justify-items: center; gap: 8px; margin: 0 0 14px; padding: 24px 16px; border: 1px dashed var(--ovk-line); border-radius: var(--ovk-radius); color: var(--ovk-muted); text-align: center; }
.ovk-empty { display: grid; justify-items: center; gap: 8px; margin: 0 0 14px; padding: 24px 16px; border: 1px dashed var(--ovk-line); border-radius: var(--ovk-radius); color: var(--ovk-muted); text-align: center; }
.ovk-empty > .ovk-i { width: 28px; height: 28px; }
.ovk-empty > .ovk-i { width: 28px; height: 28px; }
-.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: 12px; 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; }
@@ -229,7 +232,7 @@ div.ovk-set-row { cursor: default; }
@@ -229,7 +232,7 @@ div.ovk-set-row { cursor: default; }
.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); }
-.ovk-sheet-head h2 { flex: 1; margin: 0; font-size: 1.05rem; }
+.ovk-sheet-head.ovk-sheet-head h2 { flex: 1; margin: 0; font-size: 1.05rem; }
.ovk-sheet-body { padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px)); overflow-y: auto; }
.ovk-sheet-body { padding: 14px 16px calc(16px + env(safe-area-inset-bottom, 0px)); overflow-y: auto; }
/* ── Phones (< 600 px): bottom sheets, full-width forms ── */
/* ── Phones (< 600 px): bottom sheets, full-width forms ── */
@@ -317,7 +320,7 @@ div.ovk-set-row { cursor: default; }
@@ -317,7 +320,7 @@ div.ovk-set-row { cursor: default; }
}
}
/* Charts: the lines are an SVG stretched to the box; every label is real text at its real size */
/* Charts: the lines are an SVG stretched to the box; every label is real text at its real size */
-.ovk-chart { margin: 0 0 14px; padding: 14px 14px 10px; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); color: var(--ovk-text); font-family: var(--ovk-font); }
+.ovk-chart { overflow: clip; margin: 0 0 14px; padding: 14px 14px 10px; border: 1px solid var(--ovk-line); border-radius: var(--ovk-radius); background: var(--ovk-panel); color: var(--ovk-text); font-family: var(--ovk-font); }
.ovk-chart figcaption { display: grid; gap: 2px; margin: 0 0 8px; }
.ovk-chart figcaption { display: grid; gap: 2px; margin: 0 0 8px; }
.ovk-chart figcaption strong { font-size: 1rem; font-weight: 800; }
.ovk-chart figcaption strong { font-size: 1rem; font-weight: 800; }
.ovk-chart figcaption span { color: var(--ovk-muted); font-size: .84rem; }
.ovk-chart figcaption span { color: var(--ovk-muted); font-size: .84rem; }
@@ -353,7 +356,7 @@ div.ovk-set-row { cursor: default; }
@@ -353,7 +356,7 @@ div.ovk-set-row { cursor: default; }
.ovk-chart-dot.is-right .ovk-chart-pop { left: auto; right: -6px; transform: none; }
.ovk-chart-dot.is-right .ovk-chart-pop { left: auto; right: -6px; transform: none; }
.ovk-chart-name.is-hidden { visibility: hidden; }
.ovk-chart-name.is-hidden { visibility: hidden; }
.ovk-chart-name { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--ovk-text); font-size: .74rem; font-weight: 600; white-space: nowrap; pointer-events: none; text-shadow: 0 0 3px var(--ovk-panel), 0 0 3px var(--ovk-panel); }
.ovk-chart-name { position: absolute; left: 14px; top: 50%; transform: translateY(-50%); color: var(--ovk-text); font-size: .74rem; font-weight: 600; white-space: nowrap; pointer-events: none; text-shadow: 0 0 3px var(--ovk-panel), 0 0 3px var(--ovk-panel); }
-.ovk-chart-dot.is-right .ovk-chart-name { left: auto; right: 14px; }
+.ovk-chart-dot.is-right .ovk-chart-name, .ovk-chart-dot.is-flip .ovk-chart-name { left: auto; right: 14px; }
.ovk-chart-bars { position: absolute; inset: 0; display: flex; align-items: flex-end; gap: clamp(1px, calc(12% / var(--n, 10)), 6px); }
.ovk-chart-bars { position: absolute; inset: 0; display: flex; align-items: flex-end; gap: clamp(1px, calc(12% / var(--n, 10)), 6px); }
.ovk-chart-bar { position: relative; flex: 1 1 0; min-width: 2px; height: var(--h, 0%); border-radius: 3px 3px 0 0; background: var(--ovk-c1); outline: none; }
.ovk-chart-bar { position: relative; flex: 1 1 0; min-width: 2px; height: var(--h, 0%); border-radius: 3px 3px 0 0; background: var(--ovk-c1); outline: none; }
.ovk-chart-bar:hover, .ovk-chart-bar:focus-visible { filter: brightness(1.15); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ovk-accent) 45%, transparent); }
.ovk-chart-bar:hover, .ovk-chart-bar:focus-visible { filter: brightness(1.15); box-shadow: 0 0 0 2px color-mix(in srgb, var(--ovk-accent) 45%, transparent); }
@@ -414,7 +417,7 @@ div.ovk-set-row { cursor: default; }
@@ -414,7 +417,7 @@ div.ovk-set-row { cursor: default; }
.ovk-receipt--ok { border-color: color-mix(in srgb, var(--ovk-ok) 45%, var(--ovk-line)); }
.ovk-receipt--ok { border-color: color-mix(in srgb, var(--ovk-ok) 45%, var(--ovk-line)); }
.ovk-receipt--bad { border-color: color-mix(in srgb, var(--ovk-bad) 45%, var(--ovk-line)); }
.ovk-receipt--bad { border-color: color-mix(in srgb, var(--ovk-bad) 45%, var(--ovk-line)); }
.ovk-receipt-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 8px 0 14px; min-height: 44px; }
.ovk-receipt-head { display: flex; align-items: center; justify-content: space-between; gap: 10px; padding: 8px 8px 0 14px; min-height: 44px; }
-.ovk-receipt-head h2 { margin: 0; font-size: 1rem; font-weight: 800; }
+.ovk-receipt-head.ovk-receipt-head h2 { margin: 0; font-size: 1rem; font-weight: 800; }
.ovk-receipt-acts { display: inline-flex; gap: 2px; }
.ovk-receipt-acts { display: inline-flex; gap: 2px; }
.ovk-receipt-body { padding: 10px 14px 14px; line-height: 1.5; overflow-wrap: anywhere; }
.ovk-receipt-body { padding: 10px 14px 14px; line-height: 1.5; overflow-wrap: anywhere; }
.ovk-receipt-body > :first-child { margin-top: 0; } .ovk-receipt-body > :last-child { margin-bottom: 0; }
.ovk-receipt-body > :first-child { margin-top: 0; } .ovk-receipt-body > :last-child { margin-bottom: 0; }
@@ -576,7 +576,7 @@
@@ -576,7 +576,7 @@
const axisNames = yo.label ? `<span class="ovk-chart-yname" aria-hidden="true">${esc(yo.label)}</span>` : '';
const axisNames = yo.label ? `<span class="ovk-chart-yname" aria-hidden="true">${esc(yo.label)}</span>` : '';
return `<figure class="ovk-chart ovk-chart--${kind}"${id ? ` id="${esc(id)}"` : ''} style="--ovk-chart-h:${Math.max(120, Math.min(600, Number(height) || 220))}px;--ovk-chart-g:${gutter}px">${cap}${legend}${axisNames}`
return `<figure class="ovk-chart ovk-chart--${kind}"${id ? ` id="${esc(id)}"` : ''} style="--ovk-chart-h:${Math.max(120, Math.min(600, Number(height) || 220))}px;--ovk-chart-g:${gutter}px">${cap}${legend}${axisNames}`
+ `<div class="ovk-chart-plot"${data ? ` tabindex="0" role="img" aria-label="${esc(name)}" data-ovk-chart="${esc(JSON.stringify(data))}"` : ` role="group" aria-label="${esc(name)}"`}>${plot}${data ? '<span class="ovk-chart-rule" hidden></span><span class="ovk-chart-tip" hidden></span>' : ''}</div>`
+ `<div class="ovk-chart-plot"${data ? ` tabindex="0" role="img" aria-label="${esc(name)}" data-ovk-chart="${esc(JSON.stringify(data))}"` : ` role="group" aria-label="${esc(name)}"`}>${plot}${data ? '<span class="ovk-chart-rule" hidden></span><span class="ovk-chart-tip" hidden></span>' : ''}</div>`
- + `${xo.label ? `<p class="ovk-chart-xname" aria-hidden="true">${esc(xo.label)}</p>` : ''}<table class="ovk-sr"><caption>${esc(name)}</caption>${table}</table></figure>`;
+ + `${xo.label ? `<p class="ovk-chart-xname" aria-hidden="true">${esc(xo.label)}</p>` : ''}<div class="ovk-sr"><table><caption>${esc(name)}</caption>${table}</table></div></figure>`;
}
}
/** The grid and the tick labels: x [[pos%, text]], y [[pos%, text]] — real text, placed by percent. */
/** The grid and the tick labels: x [[pos%, text]], y [[pos%, text]] — real text, placed by percent. */
function axes(xt, yt) {
function axes(xt, yt) {
@@ -1,12 +1,12 @@
@@ -1,12 +1,12 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.36.0",
+ "version": "4.36.1",
"lockfileVersion": 3,
"lockfileVersion": 3,
"requires": true,
"requires": true,
"packages": {
"packages": {
"": {
"": {
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.36.0",
+ "version": "4.36.1",
"license": "MIT",
"license": "MIT",
"dependencies": {
"dependencies": {
"jsonwebtoken": "^9.0.0",
"jsonwebtoken": "^9.0.0",
@@ -1,6 +1,6 @@
@@ -1,6 +1,6 @@
{
{
"name": "openvibe-shared",
"name": "openvibe-shared",
- "version": "4.36.0",
+ "version": "4.36.1",
"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": {
@@ -27,7 +27,7 @@
@@ -27,7 +27,7 @@
.ovk-card-body { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }
.ovk-card-body { display: flex; gap: 10px; align-items: flex-start; min-width: 0; }
.ovk-card-body > .ovk-mark { flex: none; }
.ovk-card-body > .ovk-mark { flex: none; }
.ovk-card-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
.ovk-card-text { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 2px; }
-.ovk-card-title { margin: 0; font-size: 15px; font-weight: 700; line-height: 1.3; letter-spacing: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
+.ovk-card .ovk-card-title { margin: 0; font-size: 15px; font-weight: 700; line-height: 1.3; letter-spacing: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; overflow-wrap: anywhere; }
.ovk-card-link { color: inherit; text-decoration: none; transition: color .2s; }
.ovk-card-link { color: inherit; text-decoration: none; transition: color .2s; }
.ovk-card-link::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: var(--ovk-radius); }
.ovk-card-link::after { content: ""; position: absolute; inset: 0; z-index: 1; border-radius: var(--ovk-radius); }
.ovk-card-link:focus-visible { outline: none; }
.ovk-card-link:focus-visible { outline: none; }
@@ -48,7 +48,7 @@
@@ -48,7 +48,7 @@
/* ── Rails (kit.rail): a row that scrolls sideways and snaps ── */
/* ── Rails (kit.rail): a row that scrolls sideways and snaps ── */
.ovk-rail { margin: 26px 0 0; min-width: 0; --ovk-rail-bleed: 0px; }
.ovk-rail { margin: 26px 0 0; min-width: 0; --ovk-rail-bleed: 0px; }
.ovk-rail-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0 0 12px; }
.ovk-rail-head { display: flex; align-items: baseline; justify-content: space-between; gap: 12px; margin: 0 0 12px; }
-.ovk-rail-head h2 { margin: 0; font-size: 1.15rem; font-weight: 800; letter-spacing: -.01em; }
+.ovk-rail-head.ovk-rail-head h2 { margin: 0; font-size: 1.15rem; font-weight: 800; letter-spacing: -.01em; }
.ovk-rail-more { display: inline-flex; align-items: center; gap: 2px; min-height: 32px; color: var(--room-strong, var(--ovk-accent)); font-weight: 700; font-size: .9rem; text-decoration: none; white-space: nowrap; }
.ovk-rail-more { display: inline-flex; align-items: center; gap: 2px; min-height: 32px; color: var(--room-strong, var(--ovk-accent)); font-weight: 700; font-size: .9rem; text-decoration: none; white-space: nowrap; }
.ovk-rail-more:hover { text-decoration: underline; }
.ovk-rail-more:hover { text-decoration: underline; }
.ovk-rail-wrap { position: relative; }
.ovk-rail-wrap { position: relative; }
@@ -155,7 +155,7 @@ const kit = require('../kit');
@@ -155,7 +155,7 @@ const kit = require('../kit');
assert.ok(line.includes('class="ovk-chart-yt"') && line.includes('$12.00'), 'y ticks are real text, formatted');
assert.ok(line.includes('class="ovk-chart-yt"') && line.includes('$12.00'), 'y ticks are real text, formatted');
assert.ok(line.includes('>Sep 1<') || line.includes('>Sep 2<'), 'x ticks fall on days');
assert.ok(line.includes('>Sep 1<') || line.includes('>Sep 2<'), 'x ticks fall on days');
assert.ok(line.includes('Price <x> <b>$9.99</b>'), 'the legend says the latest value, escaped');
assert.ok(line.includes('Price <x> <b>$9.99</b>'), 'the legend says the latest value, escaped');
- assert.ok(line.includes('<table class="ovk-sr"><caption>Price</caption>'), 'a table with the same numbers for screen readers');
+ assert.ok(line.includes('<div class="ovk-sr"><table><caption>Price</caption>'), 'a table with the same numbers for screen readers, in a box that can be 1 px wide (a table cannot)');
const raw = line.match(/data-ovk-chart="([^"]*)"/)[1].replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, "'").replace(/&/g, '&');
const raw = line.match(/data-ovk-chart="([^"]*)"/)[1].replace(/"/g, '"').replace(/</g, '<').replace(/>/g, '>').replace(/'/g, "'").replace(/&/g, '&');
const data = JSON.parse(raw);
const data = JSON.parse(raw);
assert.strictEqual(data.x.length, 4);
assert.strictEqual(data.x.length, 4);