openvibers/openvibe.design Commits
Tokens: a look's corners in every format (#95)
Changes
@@@ -12,7 +12,7 @@* androidXml(doc) an Android colors.xml*/const tokens = require('openvibe-shared/tokens');--const { MOTION, BRAND, colourList } = require('./kit');++const { MOTION, BRAND, colourList, radiusList } = require('./kit');const metaOf = (doc) => (doc && doc.$extensions && doc.$extensions['network.openvibe']) || { name: 'Theme', slug: 'theme', mode: 'dark' };const camel = (path) => String(path).split('.').map((p, i) => (i ? p[0].toUpperCase() + p.slice(1) : p)).join('').replace(/[^A-Za-z0-9]/g, '');@@@ -61,6 +61,8 @@ function css(doc) {lines.push(` /* the kit's motion (openvibe-shared/ov-fx) */`);for (const d of MOTION.durations) lines.push(` ${d.css}: ${d.ms}ms;`);for (const e of MOTION.easings) lines.push(` ${e.css}: cubic-bezier(${e.value.join(', ')});`);++ lines.push(` /* the kit's shape (openvibe-shared kit.css) */`);++ for (const r of radiusList(doc)) lines.push(` ${r.css}: ${r.px}px;`);lines.push(` /* the mark */`);for (const b of BRAND) lines.push(` ${b.css}: ${b.value};`);lines.push(` color-scheme: ${m.mode === 'light' ? 'light' : 'dark'};`);@@@ -84,7 +86,9 @@ function tailwind(doc) {for (const d of MOTION.durations) transitionDuration[d.id] = `${d.ms}ms`;const transitionTimingFunction = {};for (const e of MOTION.easings) transitionTimingFunction[e.id] = `cubic-bezier(${e.value.join(', ')})`;-- const config = { theme: { extend: { colors, transitionDuration, transitionTimingFunction } } };++ const borderRadius = {};++ for (const r of radiusList(doc)) borderRadius[`ov-${r.id}`] = `${r.px}px`;++ const config = { theme: { extend: { colors, transitionDuration, transitionTimingFunction, borderRadius } } };return `${header(doc, 'js')}\nmodule.exports = ${JSON.stringify(config, null, 4)};\n`;}@@@ -100,6 +104,7 @@ function styleDictionary(doc) {for (const c of colourList(doc)) put(out, c.path, { value: c.value, type: 'color', description: c.description });for (const d of MOTION.durations) put(out, `motion.duration.${d.id}`, { value: `${d.ms}ms`, type: 'duration', description: d.description });for (const e of MOTION.easings) put(out, `motion.easing.${e.id}`, { value: e.value.slice(), type: 'cubicBezier', description: e.description });++ for (const r of radiusList(doc)) put(out, `shape.radius.${r.id}`, { value: `${r.px}px`, type: 'dimension', description: r.description });for (const b of BRAND) put(out, `brand.${b.id}`, { value: b.value, type: 'color', description: b.description });return `${JSON.stringify(out, null, 2)}\n`;}@@@ -138,6 +143,10 @@ function swift(doc) {lines.push(` /// The kit's motion: ${e.css}.`);lines.push(` public static let ${e.name} = Animation.timingCurve(${e.value.join(', ')})`);}++ for (const r of radiusList(doc)) {++ lines.push(` /// The kit's shape: ${r.css}, ${safe(r.description).toLowerCase()}.`);++ lines.push(` public static let ${r.name}: CGFloat = ${r.px}`);++ }for (const b of BRAND) {lines.push(` /// ${safe(b.description)}`);lines.push(` public static let ${b.name} = Color(hex: 0x${argb(b.value).argb})`);@@@ -155,6 +164,7 @@ function kotlin(doc) {lines.push('');lines.push('import androidx.compose.animation.core.CubicBezierEasing');lines.push('import androidx.compose.ui.graphics.Color');++ lines.push('import androidx.compose.ui.unit.dp');lines.push('');lines.push(`/** OpenVibe's tokens for ${safe(m.name)} (${m.mode === 'light' ? 'light' : 'dark'}). */`);lines.push('object OVTokens {');@@@ -172,6 +182,10 @@ function kotlin(doc) {lines.push(` /** The kit's motion: ${e.css}. */`);lines.push(` val ${e.name} = CubicBezierEasing(${e.value.map((n) => `${n}f`).join(', ')})`);}++ for (const r of radiusList(doc)) {++ lines.push(` /** The kit's shape: ${r.css}, ${safe(r.description).toLowerCase()}. */`);++ lines.push(` val ${r.name} = ${r.px}.dp`);++ }for (const b of BRAND) {lines.push(` /** ${safe(b.description)} */`);lines.push(` val ${b.name} = Color(0x${argb(b.value).argb})`);@@@ -197,6 +211,10 @@ function androidXml(doc) {lines.push(` <!-- ${safe(b.description)} -->`);lines.push(` <color name="ov_brand_${b.id}">#${argb(b.value).rgb}</color>`);}++ for (const r of radiusList(doc)) {++ lines.push(` <!-- The kit's shape: ${safe(r.description).toLowerCase()} -->`);++ lines.push(` <dimen name="ov_radius_${r.id}">${r.px}dp</dimen>`);++ }lines.push('</resources>');return `${lines.join('\n')}\n`;}@@@ -29,6 +29,21 @@ const MOTION = {],};++// The kit's shape (openvibe-shared 4.81.0, kit.css --ov-radius-1…3): three corner radii, set by the look's personality.++// The quiet looks are square, Neon Tokyo and Vapor round, every other look the kit's own corners.++const SQUARE = new Set(['paper', 'solarized-light', 'nord', 'nord-light', 'matrix', 'hacker', 'high-contrast']);++const ROUND = new Set(['neon-tokyo', 'vapor']);++const RADII = { default: [10, 14, 20], square: [4, 6, 10], round: [14, 20, 28] };++const RADIUS = [++ { id: '1', name: 'radius1', css: '--ov-radius-1', description: 'Small things: chips, inputs, rows' },++ { id: '2', name: 'radius2', css: '--ov-radius-2', description: 'Cards, sections, panels' },++ { id: '3', name: 'radius3', css: '--ov-radius-3', description: 'Sheets, dialogs, big surfaces' },++];++/** The look's shape: 'square', 'round' or 'default', by its id (the same lists as kit.css). */++const shapeOf = (theme) => { const id = String((theme || {}).slug || (theme || {}).id || ''); return SQUARE.has(id) ? 'square' : ROUND.has(id) ? 'round' : 'default'; };++/** The look's three corner radii: [{ id, name, css, px, description }]. */++const radii = (theme) => RADIUS.map((r, i) => ({ ...r, px: RADII[shapeOf(theme)][i] }));++// The mark's own two colours (openvibe-shared/ov-fx.js names them as the mark's O and V).const BRAND = [{ id: 'o', name: 'brandO', value: '#4f7cff', css: '--ov-brand-o', description: "The mark's O" },@@@ -79,8 +94,17 @@ function tokenDocument(theme) {},color: {},motion: { duration: {}, easing: {} },++ shape: { radius: {} },brand: {},};++ for (const r of radii(t)) {++ doc.shape.radius[r.id] = {++ $type: 'dimension',++ $value: `${r.px}px`,++ $description: r.description,++ $extensions: { 'network.openvibe': { css: r.css, shape: shapeOf(t) } },++ };++ }for (const c of colours(t)) {put(doc.color, c.path.replace(/^color\./, ''), {$type: 'color',@@@ -132,4 +156,13 @@ function colourList(doc) {return out;}--module.exports = { MOTION, BRAND, colours, tokenDocument, colourList };++/** The corner radii of a document, in order, with their CSS names and pixels (the kit's own when the document has none). */++function radiusList(doc) {++ const own = (doc && doc.shape && doc.shape.radius) || {};++ return RADIUS.map((r, i) => {++ const px = parseInt(String((own[r.id] || {}).$value || ''), 10);++ return { ...r, px: Number.isFinite(px) && px >= 0 && px <= 64 ? px : RADII.default[i] };++ });++}++++module.exports = { MOTION, BRAND, colours, tokenDocument, colourList, radiusList, shapeOf, radii };@@@ -36,6 +36,15 @@ const curveOf = (v) => { const m = /cubic-bezier\(([^)]+)\)/.exec(String(v)); re}});++ await check('a look\'s shape: the quiet looks square, Neon round, the rest the kit\'s own corners', async () => {++ const kitTokens = require('../server/tokens/kit');++ const px = (theme) => kitTokens.radiusList(kitTokens.tokenDocument(theme)).map((r) => r.px);++ assert.deepStrictEqual(px({ slug: 'paper' }), [4, 6, 10]);++ assert.deepStrictEqual(px({ slug: 'neon-tokyo' }), [14, 20, 28]);++ assert.deepStrictEqual(px({ slug: 'vibe' }), [10, 14, 20]);++ assert.strictEqual(kitTokens.tokenDocument({ slug: 'paper' }).shape.radius['1'].$extensions['network.openvibe'].shape, 'square');++ });++await check('the motion tokens are exactly the kit\'s', async () => {assert.deepStrictEqual(Object.keys(KIT_VARS).sort(), ['--ov-dur-1', '--ov-dur-2', '--ov-dur-3', '--ov-dur-4', '--ov-ease-in-out', '--ov-ease-out', '--ov-ease-spring']);assert.strictEqual(msOf(KIT_VARS['--ov-dur-1']), 120);@@@ -113,7 +122,15 @@ const curveOf = (v) => { const m = /cubic-bezier\(([^)]+)\)/.exec(String(v)); reassert.match(kt, /const val duration1 = 120L/);assert.match(kt, /val easeOut = CubicBezierEasing\(0\.2f, 0\.8f, 0\.2f, 1f\)/);++ // The kit's shape (openvibe-shared 4.81.0): Vibe has the kit's own corners in every format.++ assert.match(css, /--ov-radius-1: 10px;\n --ov-radius-2: 14px;\n --ov-radius-3: 20px;/);++ assert.strictEqual(preset.borderRadius['ov-2'], '14px');++ assert.strictEqual(sd.shape.radius['3'].value, '20px');++ assert.strictEqual(sd.shape.radius['3'].type, 'dimension');++ assert.match(swift, /public static let radius1: CGFloat = 10/);++ assert.match(kt, /val radius2 = 14\.dp/);const xml = (await t.get('/tokens/vibe.xml')).text;++ assert.match(xml, /<dimen name="ov_radius_3">20dp<\/dimen>/);assert.match(xml, /<color name="ov_bg_base">#0A0F1C<\/color>/);assert.match(xml, /<color name="ov_accent_base">#3B82F6<\/color>/);assert.match(xml, /<color name="ov_brand_v">#8B5CF6<\/color>/);
@@ -12,7 +12,7 @@
@@ -12,7 +12,7 @@
* androidXml(doc) an Android colors.xml
* androidXml(doc) an Android colors.xml
*/
*/
const tokens = require('openvibe-shared/tokens');
const tokens = require('openvibe-shared/tokens');
-const { MOTION, BRAND, colourList } = require('./kit');
+const { MOTION, BRAND, colourList, radiusList } = require('./kit');
const metaOf = (doc) => (doc && doc.$extensions && doc.$extensions['network.openvibe']) || { name: 'Theme', slug: 'theme', mode: 'dark' };
const metaOf = (doc) => (doc && doc.$extensions && doc.$extensions['network.openvibe']) || { name: 'Theme', slug: 'theme', mode: 'dark' };
const camel = (path) => String(path).split('.').map((p, i) => (i ? p[0].toUpperCase() + p.slice(1) : p)).join('').replace(/[^A-Za-z0-9]/g, '');
const camel = (path) => String(path).split('.').map((p, i) => (i ? p[0].toUpperCase() + p.slice(1) : p)).join('').replace(/[^A-Za-z0-9]/g, '');
@@ -61,6 +61,8 @@ function css(doc) {
@@ -61,6 +61,8 @@ function css(doc) {
lines.push(` /* the kit's motion (openvibe-shared/ov-fx) */`);
lines.push(` /* the kit's motion (openvibe-shared/ov-fx) */`);
for (const d of MOTION.durations) lines.push(` ${d.css}: ${d.ms}ms;`);
for (const d of MOTION.durations) lines.push(` ${d.css}: ${d.ms}ms;`);
for (const e of MOTION.easings) lines.push(` ${e.css}: cubic-bezier(${e.value.join(', ')});`);
for (const e of MOTION.easings) lines.push(` ${e.css}: cubic-bezier(${e.value.join(', ')});`);
+ lines.push(` /* the kit's shape (openvibe-shared kit.css) */`);
+ for (const r of radiusList(doc)) lines.push(` ${r.css}: ${r.px}px;`);
lines.push(` /* the mark */`);
lines.push(` /* the mark */`);
for (const b of BRAND) lines.push(` ${b.css}: ${b.value};`);
for (const b of BRAND) lines.push(` ${b.css}: ${b.value};`);
lines.push(` color-scheme: ${m.mode === 'light' ? 'light' : 'dark'};`);
lines.push(` color-scheme: ${m.mode === 'light' ? 'light' : 'dark'};`);
@@ -84,7 +86,9 @@ function tailwind(doc) {
@@ -84,7 +86,9 @@ function tailwind(doc) {
for (const d of MOTION.durations) transitionDuration[d.id] = `${d.ms}ms`;
for (const d of MOTION.durations) transitionDuration[d.id] = `${d.ms}ms`;
const transitionTimingFunction = {};
const transitionTimingFunction = {};
for (const e of MOTION.easings) transitionTimingFunction[e.id] = `cubic-bezier(${e.value.join(', ')})`;
for (const e of MOTION.easings) transitionTimingFunction[e.id] = `cubic-bezier(${e.value.join(', ')})`;
- const config = { theme: { extend: { colors, transitionDuration, transitionTimingFunction } } };
+ const borderRadius = {};
+ for (const r of radiusList(doc)) borderRadius[`ov-${r.id}`] = `${r.px}px`;
+ const config = { theme: { extend: { colors, transitionDuration, transitionTimingFunction, borderRadius } } };
return `${header(doc, 'js')}\nmodule.exports = ${JSON.stringify(config, null, 4)};\n`;
return `${header(doc, 'js')}\nmodule.exports = ${JSON.stringify(config, null, 4)};\n`;
}
}
@@ -100,6 +104,7 @@ function styleDictionary(doc) {
@@ -100,6 +104,7 @@ function styleDictionary(doc) {
for (const c of colourList(doc)) put(out, c.path, { value: c.value, type: 'color', description: c.description });
for (const c of colourList(doc)) put(out, c.path, { value: c.value, type: 'color', description: c.description });
for (const d of MOTION.durations) put(out, `motion.duration.${d.id}`, { value: `${d.ms}ms`, type: 'duration', description: d.description });
for (const d of MOTION.durations) put(out, `motion.duration.${d.id}`, { value: `${d.ms}ms`, type: 'duration', description: d.description });
for (const e of MOTION.easings) put(out, `motion.easing.${e.id}`, { value: e.value.slice(), type: 'cubicBezier', description: e.description });
for (const e of MOTION.easings) put(out, `motion.easing.${e.id}`, { value: e.value.slice(), type: 'cubicBezier', description: e.description });
+ for (const r of radiusList(doc)) put(out, `shape.radius.${r.id}`, { value: `${r.px}px`, type: 'dimension', description: r.description });
for (const b of BRAND) put(out, `brand.${b.id}`, { value: b.value, type: 'color', description: b.description });
for (const b of BRAND) put(out, `brand.${b.id}`, { value: b.value, type: 'color', description: b.description });
return `${JSON.stringify(out, null, 2)}\n`;
return `${JSON.stringify(out, null, 2)}\n`;
}
}
@@ -138,6 +143,10 @@ function swift(doc) {
@@ -138,6 +143,10 @@ function swift(doc) {
lines.push(` /// The kit's motion: ${e.css}.`);
lines.push(` /// The kit's motion: ${e.css}.`);
lines.push(` public static let ${e.name} = Animation.timingCurve(${e.value.join(', ')})`);
lines.push(` public static let ${e.name} = Animation.timingCurve(${e.value.join(', ')})`);
}
}
+ for (const r of radiusList(doc)) {
+ lines.push(` /// The kit's shape: ${r.css}, ${safe(r.description).toLowerCase()}.`);
+ lines.push(` public static let ${r.name}: CGFloat = ${r.px}`);
+ }
for (const b of BRAND) {
for (const b of BRAND) {
lines.push(` /// ${safe(b.description)}`);
lines.push(` /// ${safe(b.description)}`);
lines.push(` public static let ${b.name} = Color(hex: 0x${argb(b.value).argb})`);
lines.push(` public static let ${b.name} = Color(hex: 0x${argb(b.value).argb})`);
@@ -155,6 +164,7 @@ function kotlin(doc) {
@@ -155,6 +164,7 @@ function kotlin(doc) {
lines.push('');
lines.push('');
lines.push('import androidx.compose.animation.core.CubicBezierEasing');
lines.push('import androidx.compose.animation.core.CubicBezierEasing');
lines.push('import androidx.compose.ui.graphics.Color');
lines.push('import androidx.compose.ui.graphics.Color');
+ lines.push('import androidx.compose.ui.unit.dp');
lines.push('');
lines.push('');
lines.push(`/** OpenVibe's tokens for ${safe(m.name)} (${m.mode === 'light' ? 'light' : 'dark'}). */`);
lines.push(`/** OpenVibe's tokens for ${safe(m.name)} (${m.mode === 'light' ? 'light' : 'dark'}). */`);
lines.push('object OVTokens {');
lines.push('object OVTokens {');
@@ -172,6 +182,10 @@ function kotlin(doc) {
@@ -172,6 +182,10 @@ function kotlin(doc) {
lines.push(` /** The kit's motion: ${e.css}. */`);
lines.push(` /** The kit's motion: ${e.css}. */`);
lines.push(` val ${e.name} = CubicBezierEasing(${e.value.map((n) => `${n}f`).join(', ')})`);
lines.push(` val ${e.name} = CubicBezierEasing(${e.value.map((n) => `${n}f`).join(', ')})`);
}
}
+ for (const r of radiusList(doc)) {
+ lines.push(` /** The kit's shape: ${r.css}, ${safe(r.description).toLowerCase()}. */`);
+ lines.push(` val ${r.name} = ${r.px}.dp`);
+ }
for (const b of BRAND) {
for (const b of BRAND) {
lines.push(` /** ${safe(b.description)} */`);
lines.push(` /** ${safe(b.description)} */`);
lines.push(` val ${b.name} = Color(0x${argb(b.value).argb})`);
lines.push(` val ${b.name} = Color(0x${argb(b.value).argb})`);
@@ -197,6 +211,10 @@ function androidXml(doc) {
@@ -197,6 +211,10 @@ function androidXml(doc) {
lines.push(` <!-- ${safe(b.description)} -->`);
lines.push(` <!-- ${safe(b.description)} -->`);
lines.push(` <color name="ov_brand_${b.id}">#${argb(b.value).rgb}</color>`);
lines.push(` <color name="ov_brand_${b.id}">#${argb(b.value).rgb}</color>`);
}
}
+ for (const r of radiusList(doc)) {
+ lines.push(` <!-- The kit's shape: ${safe(r.description).toLowerCase()} -->`);
+ lines.push(` <dimen name="ov_radius_${r.id}">${r.px}dp</dimen>`);
+ }
lines.push('</resources>');
lines.push('</resources>');
return `${lines.join('\n')}\n`;
return `${lines.join('\n')}\n`;
}
}
@@ -29,6 +29,21 @@ const MOTION = {
@@ -29,6 +29,21 @@ const MOTION = {
],
],
};
};
+// The kit's shape (openvibe-shared 4.81.0, kit.css --ov-radius-1…3): three corner radii, set by the look's personality.
+// The quiet looks are square, Neon Tokyo and Vapor round, every other look the kit's own corners.
+const SQUARE = new Set(['paper', 'solarized-light', 'nord', 'nord-light', 'matrix', 'hacker', 'high-contrast']);
+const ROUND = new Set(['neon-tokyo', 'vapor']);
+const RADII = { default: [10, 14, 20], square: [4, 6, 10], round: [14, 20, 28] };
+const RADIUS = [
+ { id: '1', name: 'radius1', css: '--ov-radius-1', description: 'Small things: chips, inputs, rows' },
+ { id: '2', name: 'radius2', css: '--ov-radius-2', description: 'Cards, sections, panels' },
+ { id: '3', name: 'radius3', css: '--ov-radius-3', description: 'Sheets, dialogs, big surfaces' },
+];
+/** The look's shape: 'square', 'round' or 'default', by its id (the same lists as kit.css). */
+const shapeOf = (theme) => { const id = String((theme || {}).slug || (theme || {}).id || ''); return SQUARE.has(id) ? 'square' : ROUND.has(id) ? 'round' : 'default'; };
+/** The look's three corner radii: [{ id, name, css, px, description }]. */
+const radii = (theme) => RADIUS.map((r, i) => ({ ...r, px: RADII[shapeOf(theme)][i] }));
+
// The mark's own two colours (openvibe-shared/ov-fx.js names them as the mark's O and V).
// The mark's own two colours (openvibe-shared/ov-fx.js names them as the mark's O and V).
const BRAND = [
const BRAND = [
{ id: 'o', name: 'brandO', value: '#4f7cff', css: '--ov-brand-o', description: "The mark's O" },
{ id: 'o', name: 'brandO', value: '#4f7cff', css: '--ov-brand-o', description: "The mark's O" },
@@ -79,8 +94,17 @@ function tokenDocument(theme) {
@@ -79,8 +94,17 @@ function tokenDocument(theme) {
},
},
color: {},
color: {},
motion: { duration: {}, easing: {} },
motion: { duration: {}, easing: {} },
+ shape: { radius: {} },
brand: {},
brand: {},
};
};
+ for (const r of radii(t)) {
+ doc.shape.radius[r.id] = {
+ $type: 'dimension',
+ $value: `${r.px}px`,
+ $description: r.description,
+ $extensions: { 'network.openvibe': { css: r.css, shape: shapeOf(t) } },
+ };
+ }
for (const c of colours(t)) {
for (const c of colours(t)) {
put(doc.color, c.path.replace(/^color\./, ''), {
put(doc.color, c.path.replace(/^color\./, ''), {
$type: 'color',
$type: 'color',
@@ -132,4 +156,13 @@ function colourList(doc) {
@@ -132,4 +156,13 @@ function colourList(doc) {
return out;
return out;
}
}
-module.exports = { MOTION, BRAND, colours, tokenDocument, colourList };
+/** The corner radii of a document, in order, with their CSS names and pixels (the kit's own when the document has none). */
+function radiusList(doc) {
+ const own = (doc && doc.shape && doc.shape.radius) || {};
+ return RADIUS.map((r, i) => {
+ const px = parseInt(String((own[r.id] || {}).$value || ''), 10);
+ return { ...r, px: Number.isFinite(px) && px >= 0 && px <= 64 ? px : RADII.default[i] };
+ });
+}
+
+module.exports = { MOTION, BRAND, colours, tokenDocument, colourList, radiusList, shapeOf, radii };
@@ -36,6 +36,15 @@ const curveOf = (v) => { const m = /cubic-bezier\(([^)]+)\)/.exec(String(v)); re
@@ -36,6 +36,15 @@ const curveOf = (v) => { const m = /cubic-bezier\(([^)]+)\)/.exec(String(v)); re
}
}
});
});
+ await check('a look\'s shape: the quiet looks square, Neon round, the rest the kit\'s own corners', async () => {
+ const kitTokens = require('../server/tokens/kit');
+ const px = (theme) => kitTokens.radiusList(kitTokens.tokenDocument(theme)).map((r) => r.px);
+ assert.deepStrictEqual(px({ slug: 'paper' }), [4, 6, 10]);
+ assert.deepStrictEqual(px({ slug: 'neon-tokyo' }), [14, 20, 28]);
+ assert.deepStrictEqual(px({ slug: 'vibe' }), [10, 14, 20]);
+ assert.strictEqual(kitTokens.tokenDocument({ slug: 'paper' }).shape.radius['1'].$extensions['network.openvibe'].shape, 'square');
+ });
+
await check('the motion tokens are exactly the kit\'s', async () => {
await check('the motion tokens are exactly the kit\'s', async () => {
assert.deepStrictEqual(Object.keys(KIT_VARS).sort(), ['--ov-dur-1', '--ov-dur-2', '--ov-dur-3', '--ov-dur-4', '--ov-ease-in-out', '--ov-ease-out', '--ov-ease-spring']);
assert.deepStrictEqual(Object.keys(KIT_VARS).sort(), ['--ov-dur-1', '--ov-dur-2', '--ov-dur-3', '--ov-dur-4', '--ov-ease-in-out', '--ov-ease-out', '--ov-ease-spring']);
assert.strictEqual(msOf(KIT_VARS['--ov-dur-1']), 120);
assert.strictEqual(msOf(KIT_VARS['--ov-dur-1']), 120);
@@ -113,7 +122,15 @@ const curveOf = (v) => { const m = /cubic-bezier\(([^)]+)\)/.exec(String(v)); re
@@ -113,7 +122,15 @@ const curveOf = (v) => { const m = /cubic-bezier\(([^)]+)\)/.exec(String(v)); re
assert.match(kt, /const val duration1 = 120L/);
assert.match(kt, /const val duration1 = 120L/);
assert.match(kt, /val easeOut = CubicBezierEasing\(0\.2f, 0\.8f, 0\.2f, 1f\)/);
assert.match(kt, /val easeOut = CubicBezierEasing\(0\.2f, 0\.8f, 0\.2f, 1f\)/);
+ // The kit's shape (openvibe-shared 4.81.0): Vibe has the kit's own corners in every format.
+ assert.match(css, /--ov-radius-1: 10px;\n --ov-radius-2: 14px;\n --ov-radius-3: 20px;/);
+ assert.strictEqual(preset.borderRadius['ov-2'], '14px');
+ assert.strictEqual(sd.shape.radius['3'].value, '20px');
+ assert.strictEqual(sd.shape.radius['3'].type, 'dimension');
+ assert.match(swift, /public static let radius1: CGFloat = 10/);
+ assert.match(kt, /val radius2 = 14\.dp/);
const xml = (await t.get('/tokens/vibe.xml')).text;
const xml = (await t.get('/tokens/vibe.xml')).text;
+ assert.match(xml, /<dimen name="ov_radius_3">20dp<\/dimen>/);
assert.match(xml, /<color name="ov_bg_base">#0A0F1C<\/color>/);
assert.match(xml, /<color name="ov_bg_base">#0A0F1C<\/color>/);
assert.match(xml, /<color name="ov_accent_base">#3B82F6<\/color>/);
assert.match(xml, /<color name="ov_accent_base">#3B82F6<\/color>/);
assert.match(xml, /<color name="ov_brand_v">#8B5CF6<\/color>/);
assert.match(xml, /<color name="ov_brand_v">#8B5CF6<\/color>/);