openvibers/openvibe.design Commits
/brand/explore: the Wildcards remade, every one the O and the V (#71)
Changes
@@@ -478,63 +478,37 @@ html[data-ov-motion=reduced] .mb-btn *{animation:none!important}@keyframes stDot{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.35);opacity:.6}}.mx-wrap[data-density=sheet] .mx-status{display:none}--/* Each idea's own motion: only while its card is on screen, with long rests, and never under reduced motion. */++/* Each wildcard moves by its kind of motion (data-wild-motion): only while its card is on screen, with long rests,++ and never under reduced motion. The parts that move are w-a, w-b and w-c. */.mx-wild svg :is(.w-a,.w-b,.w-c){transform-box:fill-box;transform-origin:center}@media (prefers-reduced-motion:no-preference){-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild] :is(.w-a,.w-b,.w-c){animation-duration:3.6s;animation-iteration-count:infinite;animation-timing-function:ease-in-out;animation-delay:calc(var(--d,0s) + var(--i,0) * .09s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=soundwave] .w-a{transform-origin:top;animation-name:wBar;animation-duration:1.6s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=ripple] .w-a{animation-name:wRipple;animation-duration:2.4s;animation-delay:calc(var(--i,0) * .25s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=fork] .w-a{transform-origin:bottom;animation-name:wHum}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=fork] .w-b{animation-name:wFlash}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pulse] .w-a{stroke-dasharray:100;animation-name:wDraw;animation-duration:3s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bubble] .w-a{animation-name:wBob}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bubble] .w-b{stroke-dasharray:100;animation-name:wDraw}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=door] .w-a{transform-origin:right center;animation-name:wSwing;animation-duration:4.4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=door] .w-b{animation-name:wGlow;animation-duration:4.4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=planet] .w-a{animation-name:wTilt;animation-duration:6s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=sunrise] .w-a{animation-name:wRise;animation-duration:5s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=prism] .w-a{animation-name:wShimmer;animation-duration:2.4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=plane] .w-a{animation-name:wGlide;animation-duration:4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=plane] .w-b{animation-name:wFlash;animation-duration:4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=blob] .w-a{animation-name:wMorph;animation-duration:5s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=smile] .w-a{animation-name:wBlink;animation-duration:4.2s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=eye] .w-a{animation-name:wLook;animation-duration:5s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=eye] .w-b{animation-name:wBlink;animation-duration:5s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=spark] :is(.w-a,.w-b,.w-c){animation-name:wTwinkle;animation-duration:3s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=spark] .w-b{animation-delay:.5s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=spark] .w-c{animation-delay:1s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=overlap] .w-a{animation-name:wMeetL;animation-duration:4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=overlap] .w-b{animation-name:wMeetR;animation-duration:4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=stars] :is(.w-a,.w-b){animation-name:wTwinkle;animation-duration:2.8s;animation-delay:calc(var(--i,0) * .35s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pixel] .w-a{animation-name:wPop;animation-duration:3.4s;animation-delay:calc(var(--i,0) * .06s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=ribbon] .w-a{transform-origin:top;animation-name:wSway;animation-duration:4.6s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=code] .w-a{animation-name:wCaret;animation-duration:1.2s;animation-timing-function:steps(1)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=code] .w-b{animation-name:wOpenL}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=code] .w-c{animation-name:wOpenR}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) :is(svg[data-wild=badge],svg[data-wild=shield]) .w-a{animation-name:wShine;animation-duration:3.8s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=wave] .w-a{animation-name:wRoll;animation-duration:4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=wave] .w-b{animation-name:wSpray;animation-duration:4s;animation-delay:calc(var(--i,0) * .3s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=flame] .w-a{transform-origin:bottom;animation-name:wFlicker;animation-duration:1.8s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=leaf] .w-a{transform-origin:bottom;animation-name:wSway;animation-duration:4.6s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=moon] :is(.w-a,.w-b){animation-name:wTwinkle;animation-duration:3.2s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bolt] .w-a{animation-name:wZap;animation-duration:3.4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=headphones] .w-a rect{transform-box:fill-box;transform-origin:center;animation:wBeat .9s ease-in-out infinite;animation-delay:calc(var(--i,0) * .15s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=play] .w-a{animation-name:wPress;animation-duration:3s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=play] .w-b{animation-name:wRing;animation-duration:3s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=antenna] .w-a path{animation:wFlash 2s ease-in-out infinite;animation-delay:calc(var(--i,0) * .35s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=gem] .w-a{animation-name:wTwinkle;animation-duration:3.2s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=dice] .w-a{animation-name:wRoll90;animation-duration:5s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pin] .w-a{transform-origin:bottom;animation-name:wDrop;animation-duration:3.6s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pin] .w-b{animation-name:wShadow;animation-duration:3.6s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=heart] .w-a{animation-name:wHeart;animation-duration:2.2s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=chevrons] .w-a{animation-name:wCascade;animation-duration:2.4s;animation-delay:calc(var(--i,0) * .22s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=infinity] .w-a{animation:wLoop 3.2s linear infinite}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=kite] .w-a{transform-origin:50% 0;animation-name:wSway;animation-duration:4.4s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bird] :is(.w-a,.w-b){transform-origin:bottom;animation-name:wFlap;animation-duration:1.6s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bounce] .w-a{animation-name:wHop;animation-duration:2.8s;animation-delay:calc(var(--i,0) * .12s)}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=orbit] .w-a{transform-box:view-box;transform-origin:50px 50px;animation:wOrbit 6s linear infinite}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=cursor] .w-a{animation-name:wClick;animation-duration:3s}-- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=cursor] .w-b{animation-name:wRing;animation-duration:3s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion] :is(.w-a,.w-b,.w-c){animation-duration:3.6s;animation-iteration-count:infinite;animation-timing-function:ease-in-out;animation-delay:calc(var(--d,0s) + var(--i,0) * .09s)}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=draw] .w-a{stroke-dasharray:100;animation-name:wDraw;animation-duration:3.4s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=join] .w-a{animation-name:wJoinL}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=join] .w-b{animation-name:wJoinR}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=flicker] :is(.w-a,.w-b){animation-name:wFlick;animation-duration:3.2s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=flicker] .w-b{animation-delay:.35s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=tilt] .w-a{animation-name:wTilt;animation-duration:5s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=drip] .w-a{transform-origin:top;animation-name:wDrip;animation-duration:3s;animation-delay:calc(var(--i,0) * .4s)}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=wobble] .w-a{animation-name:wMorph;animation-duration:4.4s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=shine] .w-a{animation-name:wShine;animation-duration:3.8s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=drop] .w-a{transform-origin:bottom;animation-name:wDrop}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=drop] .w-b{animation-name:wShadow}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=wave] .w-a{transform-origin:bottom center;animation-name:wCheer;animation-duration:2.6s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=pulse] :is(.w-a,.w-b){animation-name:wFlash;animation-duration:2.4s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=float] .w-a{animation-name:wBob;animation-duration:3.4s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=glide] .w-a{animation-name:wGlide;animation-duration:4s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=rise] .w-a{animation-name:wRise;animation-duration:5s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=hop] .w-a{animation-name:wHop;animation-duration:2.4s;animation-delay:calc(var(--i,0) * .25s)}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=rocket] .w-a{animation-name:wBob;animation-duration:2.2s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=rocket] .w-b{transform-origin:top;animation-name:wFlame;animation-duration:.5s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=cascade] .w-a{animation-name:wCascade;animation-duration:1.6s;animation-delay:calc(var(--i,0) * .2s)}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=press] .w-a{animation-name:wPress;animation-duration:3s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=bars] .w-a{transform-origin:bottom;animation-name:wBar;animation-duration:1.4s;animation-delay:calc(var(--i,0) * .1s)}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=land] .w-a{animation-name:wLand;animation-duration:3.4s}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=pop] .w-a{animation-name:wPop;animation-duration:3.4s;animation-delay:calc(var(--i,0) * .045s)}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=twinkle] .w-a{animation-name:wTwinkle;animation-duration:2.8s;animation-delay:calc(var(--i,0) * .3s)}++ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=blink] .w-a{animation-name:wBlink;animation-duration:4.2s}}@keyframes wBar{0%,100%{transform:scaleY(1)}30%{transform:scaleY(.55)}60%{transform:scaleY(1.08)}}@keyframes wRipple{0%,60%,100%{opacity:1;transform:scale(1)}25%{opacity:.2;transform:scale(1.08)}}@@@ -577,3 +551,10 @@ html[data-ov-motion=reduced] .mb-btn *{animation:none!important}@keyframes wHop{0%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}}@keyframes wOrbit{to{transform:rotate(360deg)}}@keyframes wClick{0%,40%,100%{transform:scale(1)}10%{transform:scale(.88)}20%{transform:scale(1)}}++@keyframes wJoinL{0%,100%{transform:translateX(0)}20%,45%{transform:translateX(-4px)}}++@keyframes wJoinR{0%,100%{transform:translateX(0)}20%,45%{transform:translateX(4px)}}++@keyframes wFlick{0%,100%{opacity:1}4%{opacity:.25}7%{opacity:1}10%{opacity:.5}13%{opacity:1}}++@keyframes wDrip{0%,30%{transform:scaleY(.2);opacity:0}45%{opacity:1}75%,100%{transform:scaleY(1);opacity:1}}++@keyframes wCheer{0%,100%{transform:rotate(0)}20%{transform:rotate(-7deg)}40%{transform:rotate(7deg)}60%{transform:rotate(0)}}++@keyframes wFlame{0%,100%{transform:scaleY(1)}50%{transform:scaleY(.75)}}++@keyframes wLand{0%{transform:translate(14px,-14px);opacity:0}25%,85%{transform:none;opacity:1}100%{transform:none;opacity:1}}@@@ -619,13 +619,13 @@ function lockOf(kind, ref) {}}--function markCard(code, kind, [name, line_, spec, lock], i) {++function markCard(code, kind, [name, line_, spec, lock, motion], i) {if (kind === 'W') {const art = wildArt(spec, `m${++seq}`);const ref = `art-${code}`;return `<li class="mx-card mx-wild" id="${code}" data-code="${code}" style="${delay(i)}"><button type="button" class="mx-pick" data-pick="${code}" aria-pressed="false" aria-label="Pick ${code}"></button>--<div class="mx-stage"><svg viewBox="0 0 100 100" width="132" height="132" role="img" aria-label="${code} ${esc(name)}" data-idle="wild" data-wild="${spec}"><defs>${art.defs}</defs><g id="${ref}">${art.body}</g></svg></div>++<div class="mx-stage"><svg viewBox="0 0 100 100" width="132" height="132" role="img" aria-label="${code} ${esc(name)}" data-idle="wild" data-wild="${spec}" data-wild-motion="${motion || 'tilt'}"><defs>${art.defs}</defs><g id="${ref}">${art.body}</g></svg></div><div class="mx-lock lk-${lock || 'side'}">${lockOf(lock, ref)}</div><div class="mx-status" aria-label="As a status: live and new"><span class="mx-dark">${useOf(ref, 16)}${useOf(ref, 28)}</span><span class="st st-live" data-st="live" title="Live"></span><span class="st st-new" data-st="new" title="Something new"></span></div><p class="mx-name"><b>${code}</b> ${esc(name)}</p>@@@ -702,8 +702,8 @@ function panel(id, kind, rows, label, hidden) {return `<section class="mx-row"><h3>${esc(row.title)} <small>${kind}${pad(first)}–${kind}${pad(n)} · ${row.items.length}</small><span class="mx-swipe" aria-hidden="true">swipe →</span></h3><p class="mx-row-line">${esc(row.line)}</p><ul class="mx-grid">${cards}</ul></section>`;}).join('');const NEXT = {-- loop: [['pair', 'See the Pair', 'the second kind'], ['wild', 'See the Wildcards', 'forty other ideas'], ['menu', 'Next: menu button', 'step 2']],-- pair: [['loop', 'See the Loop', 'the first kind'], ['wild', 'See the Wildcards', 'forty other ideas'], ['menu', 'Next: menu button', 'step 2']],++ loop: [['pair', 'See the Pair', 'the second kind'], ['wild', 'See the Wildcards', 'OV, forty other ways'], ['menu', 'Next: menu button', 'step 2']],++ pair: [['loop', 'See the Loop', 'the first kind'], ['wild', 'See the Wildcards', 'OV, forty other ways'], ['menu', 'Next: menu button', 'step 2']],wild: [['loop', 'See the Loop', 'the first kind'], ['pair', 'See the Pair', 'the second kind'], ['menu', 'Next: menu button', 'step 2']],menu: [['loop', 'Back', 'step 1, the logo'], ['font', 'Next: font', 'step 3']],font: [['menu', 'Back', 'step 2, the menu button'], ['share', 'Done: share', 'your votes are in']],@@@ -714,7 +714,7 @@ function panel(id, kind, rows, label, hidden) {const STEP = {loop: [1, 'Pick a logo: the Loop', 'Tap the ones you like. Look at the Pair and the Wildcards too.'],pair: [1, 'Pick a logo: the Pair', 'Tap the ones you like. Look at the Loop and the Wildcards too.'],-- wild: [1, 'Pick a logo: the Wildcards', 'Forty other ideas. Each moves, shows a status, and wears the name its own way.'],++ wild: [1, 'Pick a logo: the Wildcards', 'The O and the V, forty other ways. Each moves, shows a status, and wears the name its own way.'],menu: [2, 'Pick a menu button', 'Tap one to see it open.'],font: [3, 'Pick a font', 'Tap the one that looks right.'],}[id];@@@ -794,7 +794,7 @@ ${tab('loop', 'The Loop', 'A V inside a ring', `<svg viewBox="0 0 100 100" width<span class="mx-or" aria-hidden="true">+</span>${tab('pair', 'The Pair', 'An O next to a V', `<svg viewBox="0 0 100 100" width="58" height="58" aria-hidden="true"><defs>${pairEx.defs}</defs>${pairEx.body}</svg>`, `P01–P${PAIRS.length} · ${PAIRS.length} takes`)}<span class="mx-or" aria-hidden="true">+</span>--${tab('wild', 'The Wildcards', 'Forty other ideas', `<svg viewBox="0 0 100 100" width="58" height="58" aria-hidden="true"><defs>${wildEx.defs}</defs>${wildEx.body}</svg>`, `W01–W${pad(WN)} · ${WN} ideas`)}++${tab('wild', 'The Wildcards', 'OV, forty other ways', `<svg viewBox="0 0 100 100" width="58" height="58" aria-hidden="true"><defs>${wildEx.defs}</defs>${wildEx.body}</svg>`, `W01–W${pad(WN)} · ${WN} ideas`)}</div><p class="mx-q" role="presentation"><span class="mx-qn">2</span> <b>Pick a menu button</b> <small>it sits next to the logo</small></p>${tab('menu', 'The menu button', 'The button next to the logo', `<span class="mb-btn mb-tabbtn">${menuSvg(m1[2], m1[3], m1[4], 30)}</span>`, 'M01–M50', false, true)}@@@ -1,359 +1,399 @@'use strict';/**-- * The third choice on /brand/explore (the owner, 2026-11-01: "an option 3… wildly different variations… be creative…-- * completely different ideas for OpenVibe branding… 40 of them… so you can choose the Loop, the Pair and/or these…-- * be thoughtful about animations and effects… status icons… how the label could be attached").++ * The third choice on /brand/explore, the Wildcards (the owner, 2026-11-01: "an option 3… wildly different variations…++ * 40 of them… be thoughtful about animations… status icons… how the label could be attached"; then, the same night:++ * "remake all the wildcards… they're not related to OpenVibe or OV… make them specific, branded to our vibe").*-- * Not an O and a V: forty other ideas of what OpenVibe is, in eight rows of five, each drawn by hand on the page's-- * 100-unit grid in the brand's blue to violet (with a warm accent where the idea needs light). Each one also says:-- * - its motion: parts of the drawing carry classes (w-a, w-b, w-c…) that css/marks.css animates for that idea (bars-- * bounce, the eye blinks, the sun rises, the plane glides), only while the card is on screen and never under-- * reduced motion;-- * - its key part (w-k): what lights up when the mark shows a status (live: red and pulsing; new: a count badge);-- * - its lockup: how the name attaches to it (side, pill, under, bubble, stack, tag, trail).-- * Every drawing is a function of an id prefix, so a mark can be drawn more than once on a page with ids that never clash.++ * Every one is the O and the V, forty ways: the letters made new (script, stencil, neon, blocks, graffiti), monograms++ * (woven, linked, a coin, an app tile, a crest), an O standing on a V (a map pin, an ice cream, someone cheering, a++ * microphone, a balloon), the two as a scene (a bird past the sun, a sun in a valley, a cocktail, a ball and a cup, a++ * rocket), signals (a speech bubble, play, a signal, a heartbeat, an equaliser), things that are an O and a V (a bulb, a++ * clock at ten past ten, a target, a record, an orbit), letters built from parts (pixels, stars, halftone dots, tiles,++ * an embroidered patch) and a feel (liquid, glass, a ribbon, a sticker, a face).++ *++ * Each drawing says:++ * - its motion (data-wild-motion on the card's SVG; parts w-a, w-b, w-c move), animated by css/marks.css only while++ * the card is on screen and never under reduced motion;++ * - its key part (w-k): what lights up when the mark shows a status (live: red and pulsing; new: a count);++ * - its lockup: how the name attaches (side, pill, under, bubble, stack, tag, trail).++ * Every drawing is a function of an id prefix, so a mark can be drawn more than once on a page with unique ids.*/const BV = [['0', '#4f7cff'], ['1', '#8b5cf6']];++const WARM = [['0', '#fde68a'], ['1', '#f97316']];const lin = (id, stops, x1 = 0, y1 = 0, x2 = 1, y2 = 1) => `<linearGradient id="${id}" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}">${stops.map(([o, c, a]) => `<stop offset="${o}" stop-color="${c}"${a != null ? ` stop-opacity="${a}"` : ''}/>`).join('')}</linearGradient>`;const rad = (id, stops) => `<radialGradient id="${id}" cx=".35" cy=".3" r=".8">${stops.map(([o, c, a]) => `<stop offset="${o}" stop-color="${c}"${a != null ? ` stop-opacity="${a}"` : ''}/>`).join('')}</radialGradient>`;const R = 'stroke-linecap="round" stroke-linejoin="round"';--const star4 = (cx, cy, r) => `M${cx} ${cy - r} C${cx + r * 0.08} ${cy - r * 0.3} ${cx + r * 0.3} ${cy - r * 0.08} ${cx + r} ${cy} C${cx + r * 0.3} ${cy + r * 0.08} ${cx + r * 0.08} ${cy + r * 0.3} ${cx} ${cy + r} C${cx - r * 0.08} ${cy + r * 0.3} ${cx - r * 0.3} ${cy + r * 0.08} ${cx - r} ${cy} C${cx - r * 0.3} ${cy - r * 0.08} ${cx - r * 0.08} ${cy - r * 0.3} ${cx} ${cy - r}Z`;++const g = (id) => lin(`${id}g`, BV);const ART = {-- // ── Sound and signal ──-- soundwave(id) {-- const xs = [22, 31, 40, 50, 60, 69, 78];-- const bars = xs.map((x, i) => { const h = 14 + 44 * (1 - Math.abs(x - 50) / 28); return `<rect class="w-a${x === 50 ? ' w-k' : ''}" style="--i:${i}" x="${x - 3.5}" y="20" width="7" height="${h.toFixed(1)}" rx="3.5"/>`; }).join('');-- return { defs: lin(`${id}g`, BV, 0, 0, 1, 1), body: `<g fill="url(#${id}g)">${bars}</g><rect x="14" y="82" width="72" height="4" rx="2" fill="url(#${id}g)" opacity=".35"/>` };-- },-- ripple(id) {-- const arc = (r, o, i) => `<path class="w-a" style="--i:${i}" d="M28 ${72 - r} A${r} ${r} 0 0 1 ${28 + r} 72" fill="none" stroke="url(#${id}g)" stroke-width="8" stroke-linecap="round" opacity="${o}"/>`;-- return { defs: lin(`${id}g`, BV, 0, 1, 1, 0), body: `${arc(24, 1, 0)}${arc(39, 0.72, 1)}${arc(54, 0.45, 2)}<circle class="w-k" cx="28" cy="72" r="10" fill="url(#${id}g)"/>` };++ // ── Letters, made new ──++ script(id) { // a handwritten "ov" in one stroke that draws itself++ return { defs: g(id), body: `<path class="w-a w-k" pathLength="100" d="M33 41 C22 40 14 48 14 57 C14 67 21 74 31 74 C40 74 47 66 47 57 C47 50 43 44 37 42 C44 45 51 46 57 41 L70 75 L87 31" fill="none" stroke="url(#${id}g)" stroke-width="8.5" ${R}/>` };},-- fork(id) {++ stencil(id) { // a stencil O and V, cut with bridges; the pieces close upreturn {-- defs: lin(`${id}g`, BV, 0, 0, 0, 1),-- body: `<g class="w-a"><path class="w-k" d="M32 14 L50 56 L68 14" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/></g><path d="M50 56 V88" stroke="url(#${id}g)" stroke-width="10" stroke-linecap="round"/>`-- + `<g class="w-b" fill="none" stroke="#8b5cf6" stroke-width="4" stroke-linecap="round" opacity=".65"><path d="M18 20 q-7 9 0 18"/><path d="M10 15 q-10 14 0 28"/><path d="M82 20 q7 9 0 18"/><path d="M90 15 q10 14 0 28"/></g>`,++ defs: g(id) + `<mask id="${id}m"><rect width="100" height="100" fill="#fff"/><path d="M48 51 H98" stroke="#000" stroke-width="4.5"/></mask>`,++ body: `<g class="w-a"><circle cx="31" cy="51" r="19" fill="none" stroke="url(#${id}g)" stroke-width="13" stroke-dasharray="54.7 5" transform="rotate(-92.4 31 51)"/></g>`++ + `<g class="w-b w-k" mask="url(#${id}m)"><path d="M53 25 H66 L72 50 L78 25 H91 L78 77 H66Z" fill="url(#${id}g)"/></g>`,};},-- pulse(id) {++ neon(id) { // neon tubes, pink and blue, flickering on++ const f = `<filter id="${id}f" x="-40%" y="-40%" width="180%" height="180%"><feGaussianBlur stdDeviation="2.6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>`;return {-- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.55', '#8b5cf6'], ['1', '#ec4899']], 0, 0, 1, 0),-- body: `<rect x="6" y="14" width="88" height="72" rx="22" fill="#4f7cff" opacity=".1"/><path class="w-a w-k" pathLength="100" d="M8 54 H28 L36 38 L48 74 L60 22 L68 54 H92" fill="none" stroke="url(#${id}g)" stroke-width="8" ${R}/>`,++ defs: f,++ body: `<g class="w-a" filter="url(#${id}f)"><circle class="w-k" cx="31" cy="52" r="18" fill="none" stroke="#f472b6" stroke-width="6"/><circle cx="31" cy="52" r="18" fill="none" stroke="#fdf2f8" stroke-width="1.8"/></g>`++ + `<g class="w-b" filter="url(#${id}f)"><path d="M57 33 L71 72 L85 33" fill="none" stroke="#22d3ee" stroke-width="6" ${R}/><path d="M57 33 L71 72 L85 33" fill="none" stroke="#ecfeff" stroke-width="1.8" ${R}/></g>`,};},-- bubble(id) {-- return {-- defs: lin(`${id}g`, BV),-- body: `<g class="w-a"><path class="w-k" d="M24 16 H76 a12 12 0 0 1 12 12 V60 a12 12 0 0 1 -12 12 H46 L28 88 V72 H24 a12 12 0 0 1 -12 -12 V28 a12 12 0 0 1 12 -12Z" fill="url(#${id}g)"/>`-- + `<path class="w-b" pathLength="100" d="M35 34 L50 56 L65 34" fill="none" stroke="#fff" stroke-width="8" ${R}/></g>`,-- };++ block3d(id) { // solid block letters with depth++ const shapes = (dx, dy, stroke) => `<circle cx="${31 + dx}" cy="${48 + dy}" r="17" fill="none" stroke="${stroke}" stroke-width="12"/><path d="M${55 + dx} ${30 + dy} L${69 + dx} ${68 + dy} L${83 + dx} ${30 + dy}" fill="none" stroke="${stroke}" stroke-width="12" ${R}/>`;++ let depth = '';++ for (let i = 6; i >= 1; i -= 1) depth += shapes(i * 0.9, i * 1.1, i > 3 ? '#1e1b4b' : '#312e81');++ return { defs: g(id), body: `<g class="w-a">${depth}<g class="w-k">${shapes(0, 0, `url(#${id}g)`)}</g></g>` };},-- // ── Places and worlds ──-- door(id) {++ tag(id) { // a graffiti tag, still wetreturn {-- defs: lin(`${id}l`, [['0', '#fff7d6'], ['1', '#fbbf24']], 0, 0, 0, 1) + lin(`${id}g`, BV, 0, 0, 1, 1),-- body: `<path class="w-k" d="M34 88 V42 a20 20 0 0 1 40 0 V88Z" fill="url(#${id}l)"/><path d="M34 88 V42 a20 20 0 0 1 40 0 V88" fill="none" stroke="url(#${id}g)" stroke-width="6" stroke-linejoin="round"/>`-- + `<g class="w-a"><path d="M34 34 L16 24 V96 L34 88Z" fill="url(#${id}g)"/><circle cx="21" cy="62" r="2.6" fill="#fff"/></g><path class="w-b" d="M40 88 L74 88 L92 96 H50Z" fill="#fbbf24" opacity=".35"/>`,++ defs: lin(`${id}g`, [['0', '#22d3ee'], ['.5', '#4f7cff'], ['1', '#a855f7']]),++ body: `<ellipse class="w-k" cx="31" cy="48" rx="18" ry="22" transform="rotate(-14 31 48)" fill="none" stroke="url(#${id}g)" stroke-width="11"/>`++ + `<path d="M52 24 Q60 50 67 74 Q76 46 91 21" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/>`++ + `<g stroke="#a855f7" stroke-linecap="round"><path class="w-a" d="M28 70 V82" stroke-width="5"/><path class="w-a" style="--i:1" d="M67 76 V90" stroke-width="5"/><path class="w-a" style="--i:2" d="M42 64 V71" stroke-width="3.5"/></g>`++ + `<path d="M20 36 Q24 30 30 29" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" opacity=".7"/>`,};},-- planet(id) {++ // ── Monograms ──++ crossed(id) { // a V woven through an Oreturn {-- defs: rad(`${id}p`, [['0', '#93c5fd'], ['.55', '#4f7cff'], ['1', '#6d28d9']]) + lin(`${id}r`, [['0', '#c4b5fd'], ['1', '#f0abfc']], 0, 0, 1, 0),-- body: `<g class="w-a"><g transform="rotate(-18 50 50)"><ellipse cx="50" cy="50" rx="42" ry="12" fill="none" stroke="url(#${id}r)" stroke-width="5" opacity=".5"/></g></g>`-- + `<circle class="w-k" cx="50" cy="50" r="25" fill="url(#${id}p)"/>`-- + `<g class="w-a"><g transform="rotate(-18 50 50)"><path d="M8 50 A42 12 0 0 0 92 50" fill="none" stroke="url(#${id}r)" stroke-width="5" stroke-linecap="round"/></g></g>`,++ defs: '',++ body: `<g class="w-a"><circle class="w-k" cx="50" cy="50" r="24" fill="none" stroke="#4f7cff" stroke-width="9"/>`++ + `<path d="M24 18 L50 86 L76 18" fill="none" stroke="#8b5cf6" stroke-width="9" ${R}/>`++ + `<path d="M58.2 72.6 A24 24 0 0 1 51.2 74" fill="none" stroke="#4f7cff" stroke-width="9"/>`++ + `<path d="M31.6 34.6 A24 24 0 0 1 36 30.5" fill="none" stroke="#4f7cff" stroke-width="9"/></g>`,};},-- sunrise(id) {++ chain(id) { // an O and a V linked like a chainreturn {-- defs: lin(`${id}s`, [['0', '#fde68a'], ['1', '#f97316']], 0, 0, 0, 1) + lin(`${id}m`, [['0', '#8b5cf6'], ['1', '#3b4cca']], 0, 0, 0, 1),-- body: `<circle class="w-a w-k" cx="50" cy="50" r="16" fill="url(#${id}s)"/><path d="M4 88 L27 30 L50 72 L73 30 L96 88Z" fill="url(#${id}m)"/><path d="M27 30 L35 50 L31 47 L27 52 L22 44Z" fill="#fff" opacity=".7"/><path d="M73 30 L81 50 L77 47 L73 52 L68 44Z" fill="#fff" opacity=".7"/>`,++ defs: '',++ body: `<g class="w-a"><circle class="w-k" cx="40" cy="54" r="19" fill="none" stroke="#4f7cff" stroke-width="8"/>`++ + `<path d="M50 26 H88 L69 78Z" fill="none" stroke="#8b5cf6" stroke-width="8" stroke-linejoin="round"/>`++ + `<path d="M55.4 39.4 A19 19 0 0 1 58.4 44.8" fill="none" stroke="#4f7cff" stroke-width="8"/></g>`,};},-- prism(id) {-- const rays = [['#ef4444', 24], ['#f59e0b', 36], ['#22c55e', 48], ['#22d3ee', 60], ['#8b5cf6', 72]];++ coin(id) { // an OpenVibe coin with OV struck on itreturn {-- defs: lin(`${id}g`, [['0', '#4f7cff', 0.55], ['1', '#8b5cf6', 0.85]], 0, 0, 1, 1),-- body: `<path d="M4 44 H34" stroke="#fff" stroke-width="5" stroke-linecap="round"/>${rays.map(([c, y], i) => `<path class="w-a" style="--i:${i}" d="M64 48 L96 ${y}" stroke="${c}" stroke-width="5" stroke-linecap="round"/>`).join('')}`-- + `<path class="w-k" d="M20 20 H80 L50 84Z" fill="url(#${id}g)" stroke="#c4b5fd" stroke-width="3" stroke-linejoin="round"/>`,++ defs: lin(`${id}c`, [['0', '#fef3c7'], ['.5', '#fbbf24'], ['1', '#d97706']]) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.55], ['1', '#fff', 0]], 0, 0, 1, 0)++ + `<clipPath id="${id}k"><circle cx="50" cy="50" r="40"/></clipPath>`,++ body: `<circle class="w-k" cx="50" cy="50" r="40" fill="url(#${id}c)"/><circle cx="50" cy="50" r="40" fill="none" stroke="#b45309" stroke-width="3" stroke-dasharray="2 2.2"/><circle cx="50" cy="50" r="32" fill="none" stroke="#fffbeb" stroke-width="1.6" opacity=".7"/>`++ + `<circle cx="38" cy="50" r="9.5" fill="none" stroke="#92400e" stroke-width="5.5"/><path d="M53 40 L60.5 60 L68 40" fill="none" stroke="#92400e" stroke-width="5.5" ${R}/>`++ + `<g clip-path="url(#${id}k)"><rect class="w-a" x="-40" y="0" width="26" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`,};},-- plane(id) {++ tile(id) { // ov on an app tile, with a shinereturn {-- defs: lin(`${id}a`, [['0', '#a5b4fc'], ['1', '#6366f1']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#4c1d95']]),-- body: `<g class="w-a"><path class="w-k" d="M90 12 L8 44 L42 56Z" fill="url(#${id}a)"/><path d="M90 12 L42 56 L58 88Z" fill="url(#${id}b)"/><path d="M42 56 L40 74 L50 66Z" fill="#4c1d95"/></g>`-- + `<g class="w-b" stroke="#8b5cf6" stroke-width="4" stroke-linecap="round" opacity=".5"><path d="M10 70 H26"/><path d="M16 82 H34"/></g>`,++ defs: g(id) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.45], ['1', '#fff', 0]], 0, 0, 1, 0) + `<clipPath id="${id}c"><rect x="10" y="10" width="80" height="80" rx="26"/></clipPath>`,++ body: `<rect class="w-k" x="10" y="10" width="80" height="80" rx="26" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="30" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`++ + `<circle cx="37" cy="55" r="11.5" fill="none" stroke="#fff" stroke-width="8.5"/><path d="M55 44 L65 67 L75 44" fill="none" stroke="#fff" stroke-width="8.5" ${R}/>`,};},-- // ── Friendly and alive ──-- blob(id) {++ crest(id) { // an O over a V on a shieldreturn {-- defs: lin(`${id}g`, [['0', '#22d3ee'], ['.5', '#4f7cff'], ['1', '#8b5cf6']]),-- body: `<g class="w-a"><path class="w-k" d="M54 10 C77 12 93 31 88 53 C84 75 66 92 45 88 C23 85 7 67 12 45 C16 25 32 8 54 10Z" fill="url(#${id}g)"/><ellipse cx="34" cy="28" rx="10" ry="6" fill="#fff" opacity=".28" transform="rotate(-30 34 28)"/>`-- + `<path d="M36 46 L50 64 L64 46" fill="none" stroke="#fff" stroke-width="8" ${R}/></g>`,++ defs: g(id) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.4], ['1', '#fff', 0]], 0, 0, 1, 0) + `<clipPath id="${id}c"><path d="M50 7 L85 19 V47 C85 70 69 85 50 93 C31 85 15 70 15 47 V19Z"/></clipPath>`,++ body: `<path class="w-k" d="M50 7 L85 19 V47 C85 70 69 85 50 93 C31 85 15 70 15 47 V19Z" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="24" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`++ + `<circle cx="50" cy="35" r="9" fill="none" stroke="#fff" stroke-width="5.5"/><path d="M37 50 L50 75 L63 50" fill="none" stroke="#fff" stroke-width="7" ${R}/>`,};},-- smile(id) {++ // ── An O standing on a V ──++ pin(id) { // a map pin is an O on a V: you are herereturn {-- defs: rad(`${id}g`, [['0', '#93c5fd'], ['.6', '#4f7cff'], ['1', '#7c3aed']]),-- body: `<circle class="w-k" cx="50" cy="50" r="40" fill="url(#${id}g)"/><g class="w-a" fill="#fff"><circle cx="37" cy="42" r="5.5"/><circle cx="63" cy="42" r="5.5"/></g><path d="M33 58 Q50 76 67 58" fill="none" stroke="#fff" stroke-width="6.5" stroke-linecap="round"/>`,++ defs: lin(`${id}g`, BV, 0, 0, 0, 1),++ body: `<ellipse class="w-b" cx="50" cy="92" rx="13" ry="3.5" fill="#4f7cff" opacity=".3"/><g class="w-a"><circle class="w-k" cx="50" cy="33" r="17" fill="none" stroke="url(#${id}g)" stroke-width="10"/><path d="M36 45 L50 87 L64 45" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/></g>`,};},-- eye(id) {++ cone(id) { // an O scoop on a V conereturn {-- defs: lin(`${id}g`, BV, 0, 0, 1, 0) + rad(`${id}i`, [['0', '#a5f3fc'], ['.6', '#4f7cff'], ['1', '#6d28d9']]),-- body: `<g class="w-b"><path d="M6 50 Q50 10 94 50 Q50 90 6 50Z" fill="#4f7cff" opacity=".12"/><path d="M6 50 Q50 10 94 50 Q50 90 6 50Z" fill="none" stroke="url(#${id}g)" stroke-width="6" stroke-linejoin="round"/>`-- + `<g class="w-a"><circle class="w-k" cx="50" cy="50" r="17" fill="url(#${id}i)"/><circle cx="50" cy="50" r="7" fill="#0b1020"/><circle cx="55" cy="44" r="3.4" fill="#fff"/></g></g>`,++ defs: lin(`${id}c`, [['0', '#fbbf24'], ['1', '#b45309']], 0, 0, 0, 1) + rad(`${id}s`, [['0', '#a5b4fc'], ['.55', '#6366f1'], ['1', '#7c3aed']]),++ body: `<path d="M27 50 H73 L50 93Z" fill="url(#${id}c)"/><path d="M33 56 L58 80 M43 52 L64 72 M67 56 L42 80 M57 52 L36 72" stroke="#92400e" stroke-width="2" opacity=".55"/>`++ + `<g class="w-a"><circle class="w-k" cx="50" cy="35" r="21" fill="url(#${id}s)"/><path d="M33 47 Q36 56 40 50 Q44 58 48 51 Q52 57 56 50 Q60 56 64 49" fill="none" stroke="#6366f1" stroke-width="4" stroke-linecap="round"/><circle cx="43" cy="27" r="4.5" fill="#fff" opacity=".45"/></g>`,};},-- spark(id) {++ cheer(id) { // someone cheering: an O for a head, a V for arms upreturn {-- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.6', '#8b5cf6'], ['1', '#f472b6']]),-- body: `<path class="w-a w-k" d="${star4(44, 54, 36)}" fill="url(#${id}g)"/><path class="w-b" d="${star4(80, 20, 12)}" fill="#f472b6"/><path class="w-c" d="${star4(82, 76, 7)}" fill="#8b5cf6" opacity=".8"/>`,++ defs: g(id),++ body: `<g class="w-a"><path d="M18 18 L50 60 L82 18" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/></g><circle class="w-k" cx="50" cy="31" r="11" fill="url(#${id}g)"/>`++ + `<path d="M50 60 V80 M50 80 L40 94 M50 80 L60 94" fill="none" stroke="url(#${id}g)" stroke-width="9" ${R}/>`,};},-- overlap() {++ mic(id) { // a microphone: the O head held in a Vreturn {-- defs: '',-- body: `<circle class="w-a" cx="37" cy="50" r="27" fill="#4f7cff" opacity=".9"/><circle class="w-b" cx="63" cy="50" r="27" fill="#8b5cf6" opacity=".9"/>`-- + `<path class="w-k" d="M50 26.4 A27 27 0 0 1 50 73.6 A27 27 0 0 1 50 26.4Z" fill="#e0e7ff"/>`,++ defs: g(id) + `<clipPath id="${id}c"><circle cx="50" cy="32" r="16"/></clipPath>`,++ body: `<circle class="w-k" cx="50" cy="32" r="16" fill="url(#${id}g)"/><g clip-path="url(#${id}c)" stroke="#fff" stroke-width="2" opacity=".35"><path d="M30 26 H70 M30 32 H70 M30 38 H70"/></g>`++ + `<path d="M31 30 L50 62 L69 30" fill="none" stroke="#c4b5fd" stroke-width="5" ${R}/><path d="M50 62 V84 M38 86 H62" fill="none" stroke="#8b5cf6" stroke-width="5" ${R}/>`++ + `<g class="w-b" fill="none" stroke="#8b5cf6" stroke-width="3.5" stroke-linecap="round"><path d="M20 22 q-6 10 0 20"/><path d="M80 22 q6 10 0 20"/></g>`,};},-- // ── Made of parts ──-- stars(id) {++ balloon(id) { // an O balloon on a V knotreturn {-- defs: lin(`${id}g`, BV, 0, 0, 1, 1),-- body: `<path d="M18 22 L34 50 L50 78 L66 50 L82 22" fill="none" stroke="url(#${id}g)" stroke-width="3.5" stroke-linejoin="round" opacity=".7"/>`-- + `<g fill="url(#${id}g)"><circle class="w-a" style="--i:0" cx="18" cy="22" r="8"/><circle class="w-a" style="--i:2" cx="82" cy="22" r="8"/><circle class="w-a w-k" style="--i:4" cx="50" cy="78" r="9"/><circle class="w-a" style="--i:1" cx="34" cy="50" r="5.5"/><circle class="w-a" style="--i:3" cx="66" cy="50" r="5.5"/></g><circle class="w-b" cx="50" cy="30" r="3" fill="#c4b5fd"/>`,++ defs: rad(`${id}g`, [['0', '#c7d2fe'], ['.5', '#4f7cff'], ['1', '#7c3aed']]),++ body: `<g class="w-a"><ellipse class="w-k" cx="50" cy="38" rx="23" ry="27" fill="url(#${id}g)"/><path d="M44 63 L50 72 L56 63" fill="#7c3aed" stroke="#7c3aed" stroke-width="3" stroke-linejoin="round"/>`++ + `<ellipse cx="41" cy="27" rx="5" ry="8" fill="#fff" opacity=".35" transform="rotate(-25 41 27)"/><path d="M50 72 C46 80 54 84 50 92" fill="none" stroke="#a5b4fc" stroke-width="2.5" stroke-linecap="round"/></g>`,};},-- pixel() {-- const rows = [[0, 1, 7, 8], [1, 2, 6, 7], [2, 3, 5, 6], [3, 4, 5], [4]];-- const cols = ['#4f7cff', '#5b74fb', '#6a6cf8', '#7a64f7', '#8b5cf6'];-- let k = 0;-- const px = rows.map((cs, r) => cs.map((c) => `<rect class="w-a${r === 4 ? ' w-k' : ''}" style="--i:${k++}" x="${(9 + c * 9.2).toFixed(1)}" y="${18 + r * 13}" width="8.2" height="11" rx="1.6" fill="${cols[r]}"/>`).join('')).join('');-- return { defs: '', body: px };-- },-- ribbon(id) {++ // ── An O and a V in a scene ──++ bird(id) { // a V bird gliding past an O sunreturn {-- defs: lin(`${id}a`, [['0', '#6366f1'], ['1', '#4c1d95']], 0, 0, 0, 1) + lin(`${id}b`, [['0', '#93c5fd'], ['1', '#4f7cff']], 0, 0, 0, 1),-- body: `<g class="w-a"><path d="M86 16 H66 L44 78 L54 90Z" fill="url(#${id}a)"/><path class="w-k" d="M14 16 H34 L58 80 L46 90Z" fill="url(#${id}b)"/><path d="M44 78 L46 90 L54 90Z" fill="#3b0764" opacity=".6"/></g>`,++ defs: lin(`${id}s`, WARM, 0, 0, 0, 1) + g(id),++ body: `<circle class="w-k" cx="40" cy="46" r="25" fill="url(#${id}s)"/><g class="w-a"><path d="M46 56 Q57 42 67 50 Q74 38 90 34" fill="none" stroke="url(#${id}g)" stroke-width="7" ${R}/></g>`++ + `<path d="M18 80 H62" stroke="#8b5cf6" stroke-width="3" stroke-linecap="round" opacity=".4"/>`,};},-- code(id) {++ valley(id) { // an O sun rising in a V valleyreturn {-- defs: lin(`${id}g`, BV, 0, 0, 1, 0),-- body: `<g fill="none" stroke="#94a3b8" stroke-width="7" ${R}><path class="w-b" d="M28 28 L10 50 L28 72"/><path class="w-c" d="M72 28 L90 50 L72 72"/></g><path class="w-a w-k" d="M37 32 L50 68 L63 32" fill="none" stroke="url(#${id}g)" stroke-width="9" ${R}/>`,++ defs: lin(`${id}s`, WARM, 0, 0, 0, 1) + lin(`${id}m`, [['0', '#8b5cf6'], ['1', '#3b4cca']], 0, 0, 0, 1),++ body: `<circle class="w-a w-k" cx="50" cy="48" r="16" fill="url(#${id}s)"/><path d="M4 90 L28 30 L50 72 L72 30 L96 90Z" fill="url(#${id}m)"/><path d="M28 30 L35 47 L31 45 L28 50 L23 43Z M72 30 L79 47 L75 45 L72 50 L67 43Z" fill="#fff" opacity=".7"/>`,};},-- badge(id) {++ cocktail(id) { // a V glass with an O olivereturn {-- defs: lin(`${id}g`, BV) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.45], ['1', '#fff', 0]], 0, 0, 1, 0),-- body: `<clipPath id="${id}c"><rect x="10" y="10" width="80" height="80" rx="26"/></clipPath><rect class="w-k" x="10" y="10" width="80" height="80" rx="26" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="30" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`-- + `<circle cx="37" cy="55" r="11.5" fill="none" stroke="#fff" stroke-width="8.5"/><path d="M55 44 L65 67 L75 44" fill="none" stroke="#fff" stroke-width="8.5" ${R}/>`,++ defs: lin(`${id}g`, [['0', '#4f7cff', 0.55], ['1', '#8b5cf6', 0.9]], 0, 0, 0, 1),++ body: `<path class="w-k" d="M16 22 H84 L50 60Z" fill="url(#${id}g)" stroke="#c4b5fd" stroke-width="3" stroke-linejoin="round"/><path d="M50 60 V84 M34 86 H66" fill="none" stroke="#c4b5fd" stroke-width="4" ${R}/>`++ + `<g class="w-a"><path d="M74 8 L54 34" stroke="#fde68a" stroke-width="2.5" stroke-linecap="round"/><circle cx="59" cy="28" r="8" fill="#84cc16"/><circle cx="60.5" cy="26.5" r="3" fill="#ef4444"/></g>`,};},-- // ── Nature ──-- wave(id) {++ catch(id) { // an O ball dropping into a V cupreturn {-- defs: lin(`${id}g`, [['0', '#22d3ee'], ['1', '#4f7cff']], 0, 0, 1, 1) + lin(`${id}d`, [['0', '#6366f1'], ['1', '#4c1d95']], 0, 0, 0, 1),-- body: `<g class="w-a"><path class="w-k" d="M8 70 C20 70 26 40 46 30 C62 22 80 30 82 46 C70 38 58 44 58 56 C58 66 68 70 92 70 V86 H8Z" fill="url(#${id}g)"/><path d="M8 80 C30 76 60 84 92 78 V90 H8Z" fill="url(#${id}d)"/></g><circle class="w-b" cx="80" cy="24" r="3" fill="#a5f3fc"/><circle class="w-b" style="--i:1" cx="88" cy="34" r="2" fill="#a5f3fc"/>`,++ defs: g(id) + rad(`${id}b`, [['0', '#fde68a'], ['.6', '#f59e0b'], ['1', '#ea580c']]),++ body: `<path class="w-k" d="M18 44 L50 88 L82 44" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/><g class="w-a"><circle cx="50" cy="28" r="12" fill="url(#${id}b)"/></g>`,};},-- flame(id) {++ rocket(id) { // a rocket: an O window, a V flamereturn {-- defs: lin(`${id}g`, [['0', '#fbbf24'], ['.45', '#f97316'], ['1', '#8b5cf6']], 0, 0, 0, 1),-- body: `<g class="w-a"><path class="w-k" d="M50 8 C58 26 76 34 76 58 C76 76 64 90 50 90 C36 90 24 76 24 58 C24 44 32 36 38 28 C40 40 44 44 48 46 C46 32 46 20 50 8Z" fill="url(#${id}g)"/>`-- + `<path d="M40 58 L50 76 L60 58" fill="none" stroke="#fff" stroke-width="6" ${R} opacity=".9"/></g>`,++ defs: lin(`${id}g`, [['0', '#e0e7ff'], ['1', '#8b5cf6']], 0, 0, 1, 0) + lin(`${id}f`, [['0', '#fde68a'], ['1', '#f97316']], 0, 0, 0, 1),++ body: `<g class="w-a"><path d="M50 8 C65 20 69 44 64 64 H36 C31 44 35 20 50 8Z" fill="url(#${id}g)"/><path d="M36 50 L24 70 L37 66Z M64 50 L76 70 L63 66Z" fill="#6366f1"/>`++ + `<circle class="w-k" cx="50" cy="36" r="8.5" fill="#0b1020" stroke="#4f7cff" stroke-width="3.5"/></g><path class="w-b" d="M41 66 L50 92 L59 66Z" fill="url(#${id}f)"/>`,};},-- leaf(id) {++ // ── Signals ──++ bubble(id) { // an O speech bubble with a V tail, typingreturn {-- defs: lin(`${id}g`, [['0', '#34d399'], ['1', '#4f7cff']], 0, 0, 1, 1),-- body: `<g class="w-a"><path class="w-k" d="M50 90 C22 76 14 46 24 18 C42 24 82 30 82 58 C82 76 66 90 50 90Z" fill="url(#${id}g)"/><path d="M50 90 L50 70 M50 70 L36 48 M50 70 L64 50" fill="none" stroke="#fff" stroke-width="4.5" ${R} opacity=".85"/></g>`,++ defs: g(id),++ body: `<circle class="w-k" cx="52" cy="44" r="30" fill="url(#${id}g)"/><path d="M30 64 L18 88 L44 72Z" fill="#6d5ff7"/>`++ + `<g fill="#fff"><circle class="w-a" style="--i:0" cx="40" cy="44" r="4.5"/><circle class="w-a" style="--i:1" cx="52" cy="44" r="4.5"/><circle class="w-a" style="--i:2" cx="64" cy="44" r="4.5"/></g>`,};},-- moon(id) {++ play(id) { // an O and a V turned on its side: record and playreturn {-- defs: lin(`${id}g`, [['0', '#c4b5fd'], ['1', '#4f7cff']], 0, 0, 1, 1),-- body: `<path class="w-k" d="M58 12 A38 38 0 1 0 88 66 A30 30 0 1 1 58 12Z" fill="url(#${id}g)"/><path class="w-a" d="${star4(70, 30, 9)}" fill="#fde68a"/><circle class="w-b" cx="84" cy="44" r="2.5" fill="#fde68a"/>`,++ defs: g(id),++ body: `<circle class="w-k" cx="29" cy="50" r="20" fill="url(#${id}g)"/><circle cx="29" cy="50" r="5" fill="#0b1020"/><g class="w-a"><path d="M58 28 L90 50 L58 72Z" fill="#8b5cf6" stroke="#8b5cf6" stroke-width="7" stroke-linejoin="round"/></g>`,};},-- bolt(id) {-- return {-- defs: lin(`${id}g`, [['0', '#fde68a'], ['.5', '#f59e0b'], ['1', '#8b5cf6']], 0, 0, 1, 1),-- body: `<path class="w-a w-k" d="M58 6 L22 54 H46 L38 94 L78 40 H54 Z" fill="url(#${id}g)" stroke="#fff" stroke-opacity=".25" stroke-width="2" stroke-linejoin="round"/>`,-- };++ signal(id) { // V waves arriving at an O++ const v = (y, i, o) => `<path class="w-a" style="--i:${i}" d="M28 ${y} L50 ${y + 16} L72 ${y}" fill="none" stroke="url(#${id}g)" stroke-width="7" ${R} opacity="${o}"/>`;++ return { defs: g(id), body: `${v(12, 0, 0.35)}${v(30, 1, 0.65)}${v(48, 2, 1)}<circle class="w-k" cx="50" cy="82" r="9" fill="none" stroke="url(#${id}g)" stroke-width="6"/>` };},-- // ── Things you hold ──-- headphones(id) {++ beat(id) { // a heartbeat that writes an o and a vreturn {-- defs: lin(`${id}g`, BV, 0, 0, 1, 0),-- body: `<path d="M18 60 V50 a32 32 0 0 1 64 0 V60" fill="none" stroke="url(#${id}g)" stroke-width="8" stroke-linecap="round"/><rect class="w-k" x="12" y="54" width="18" height="30" rx="8" fill="url(#${id}g)"/><rect x="70" y="54" width="18" height="30" rx="8" fill="url(#${id}g)"/>`-- + `<g class="w-a" fill="#8b5cf6"><rect style="--i:0" x="39" y="58" width="5" height="18" rx="2.5"/><rect style="--i:1" x="47.5" y="52" width="5" height="30" rx="2.5"/><rect style="--i:2" x="56" y="58" width="5" height="18" rx="2.5"/></g>`,++ defs: lin(`${id}g`, [['0', '#4f7cff'], ['.6', '#8b5cf6'], ['1', '#ec4899']], 0, 0, 1, 0),++ body: `<path class="w-a w-k" pathLength="100" d="M5 56 H20 C20 43 37 43 37 55 C37 66 22 66 22 55 C22 49 32 48 44 55 L54 77 L64 28 L72 56 H95" fill="none" stroke="url(#${id}g)" stroke-width="7" ${R}/>`,};},-- play(id) {++ eqring(id) { // an O with an equaliser inside, its bars in a V++ const xs = [29, 36, 43, 50, 57, 64, 71];++ const bars = xs.map((x, i) => { const h = 8 + 28 * (Math.abs(x - 50) / 21); return `<rect class="w-a" style="--i:${i}" x="${x - 2.6}" y="${(70 - h).toFixed(1)}" width="5.2" height="${h.toFixed(1)}" rx="2.6"/>`; }).join('');++ return { defs: g(id), body: `<circle class="w-k" cx="50" cy="50" r="38" fill="none" stroke="url(#${id}g)" stroke-width="6"/><g fill="url(#${id}g)">${bars}</g>` };++ },++ // ── Things that are an O and a V ──++ bulb(id) { // a bulb with a V filament: ideasreturn {-- defs: lin(`${id}g`, BV),-- body: `<circle class="w-k" cx="50" cy="50" r="40" fill="url(#${id}g)"/><circle class="w-b" cx="50" cy="50" r="40" fill="none" stroke="#8b5cf6" stroke-width="3"/><path class="w-a" d="M42 32 Q40 30 40 34 V66 Q40 70 43 68 L68 52 Q71 50 68 48Z" fill="#fff"/>`,++ defs: rad(`${id}l`, [['0', '#fef3c7'], ['.5', '#fde68a', 0.6], ['1', '#fde68a', 0]]) + g(id),++ body: `<circle class="w-a" cx="50" cy="42" r="34" fill="url(#${id}l)"/><path class="w-k" d="M50 12 C68 12 80 26 80 42 C80 54 72 60 68 68 H32 C28 60 20 54 20 42 C20 26 32 12 50 12Z" fill="#fff" fill-opacity=".08" stroke="url(#${id}g)" stroke-width="5"/>`++ + `<path d="M40 38 L50 58 L60 38" fill="none" stroke="#f59e0b" stroke-width="5" ${R}/><path d="M34 74 H66 M36 81 H64 M42 88 H58" stroke="#8b5cf6" stroke-width="4.5" stroke-linecap="round"/>`,};},-- antenna(id) {++ clock(id) { // an O clock at ten past ten: the hands make a Vreturn {-- defs: lin(`${id}g`, BV, 0, 0, 0, 1),-- body: `<path d="M50 40 L34 90 M50 40 L66 90 M40 72 H60" fill="none" stroke="url(#${id}g)" stroke-width="6" ${R}/><circle class="w-k" cx="50" cy="36" r="7" fill="url(#${id}g)"/>`-- + `<g class="w-a" fill="none" stroke="#8b5cf6" stroke-width="5" stroke-linecap="round"><path style="--i:0" d="M36 22 a20 20 0 0 0 0 28"/><path style="--i:0" d="M64 22 a20 20 0 0 1 0 28"/><path style="--i:1" d="M24 12 a34 34 0 0 0 0 48" opacity=".6"/><path style="--i:1" d="M76 12 a34 34 0 0 1 0 48" opacity=".6"/></g>`,++ defs: g(id),++ body: `<circle class="w-k" cx="50" cy="50" r="38" fill="none" stroke="url(#${id}g)" stroke-width="7"/><path d="M50 16 V22 M84 50 H78 M50 84 V78 M16 50 H22" stroke="#8b5cf6" stroke-width="4" stroke-linecap="round"/>`++ + `<g class="w-a"><path d="M28 36 L50 50 L75 34" fill="none" stroke="#fff" stroke-width="6" ${R}/></g><circle cx="50" cy="50" r="4.5" fill="#fff"/>`,};},-- gem(id) {++ target(id) { // an O target and an arrow with a V tailreturn {-- defs: lin(`${id}a`, [['0', '#a5b4fc'], ['1', '#4f7cff']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#4c1d95']]),-- body: `<path d="M26 22 H74 L90 40 L50 88 L10 40Z" fill="url(#${id}b)"/><path class="w-k" d="M26 22 L38 40 H62 L74 22Z" fill="url(#${id}a)"/><path d="M10 40 H90 M38 40 L50 88 L62 40" fill="none" stroke="#fff" stroke-width="2.5" stroke-linejoin="round" opacity=".55"/>`-- + `<path class="w-a" d="${star4(70, 30, 8)}" fill="#fff"/>`,++ defs: '',++ body: `<circle class="w-k" cx="42" cy="58" r="32" fill="#4f7cff"/><circle cx="42" cy="58" r="22" fill="#0b1020"/><circle cx="42" cy="58" r="12" fill="#8b5cf6"/>`++ + `<g class="w-a"><path d="M42 58 L84 16" stroke="#fde68a" stroke-width="4" stroke-linecap="round"/><path d="M78 12 L86 14 L88 22 M74 16 L82 18 L84 26" fill="none" stroke="#f472b6" stroke-width="3.5" ${R}/></g>`,};},-- dice(id) {-- const pip = (x, y, i) => `<circle class="w-b" style="--i:${i}" cx="${x}" cy="${y}" r="6.5" fill="#fff"/>`;++ vinyl(id) { // an O record with a V on its labelreturn {-- defs: lin(`${id}g`, BV),-- body: `<g class="w-a"><rect class="w-k" x="14" y="14" width="72" height="72" rx="18" fill="url(#${id}g)"/>${pip(32, 32, 0)}${pip(68, 32, 1)}${pip(41, 50, 2)}${pip(59, 50, 3)}${pip(50, 68, 4)}</g>`,++ defs: g(id),++ body: `<circle class="w-k" cx="50" cy="50" r="40" fill="#111827"/><g class="w-a" fill="none" stroke="#374151" stroke-width="1.6"><circle cx="50" cy="50" r="34"/><circle cx="50" cy="50" r="28"/><circle cx="50" cy="50" r="22"/></g>`++ + `<path d="M22 36 A32 32 0 0 1 40 20" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" opacity=".35"/><circle cx="50" cy="50" r="15" fill="url(#${id}g)"/><path d="M44 45 L50 58 L56 45" fill="none" stroke="#fff" stroke-width="3.5" ${R}/>`,};},-- // ── Signs and symbols ──-- pin(id) {++ orbit(id) { // a V with an O orbit round itreturn {-- defs: lin(`${id}g`, BV, 0, 0, 0, 1),-- body: `<ellipse class="w-b" cx="50" cy="91" rx="14" ry="3.5" fill="#4f7cff" opacity=".3"/><g class="w-a"><path class="w-k" d="M50 88 C40 72 20 58 20 38 a30 30 0 0 1 60 0 C80 58 60 72 50 88Z" fill="url(#${id}g)"/><circle cx="50" cy="38" r="11" fill="#fff"/></g>`,++ defs: g(id),++ body: `<g class="w-a"><g transform="rotate(-16 50 54)"><path d="M10 54 A40 14 0 0 1 90 54" fill="none" stroke="#c4b5fd" stroke-width="4" opacity=".55"/></g></g>`++ + `<path class="w-k" d="M30 26 L50 78 L70 26" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/>`++ + `<g class="w-a"><g transform="rotate(-16 50 54)"><path d="M90 54 A40 14 0 0 1 10 54" fill="none" stroke="#c4b5fd" stroke-width="4"/><circle cx="84" cy="62" r="5" fill="#f472b6"/></g></g>`,};},-- heart(id) {++ // ── Built from parts ──++ pixel8(id) { // O and V in big pixels++ const O = ['.###.', '#...#', '#...#', '#...#', '#...#', '.###.'];++ const V = ['#...#', '#...#', '#...#', '.#.#.', '.#.#.', '..#..'];++ const cols = ['#4f7cff', '#5975fb', '#646ef9', '#6f67f8', '#7b61f7', '#8b5cf6'];++ let k = 0;++ const draw = (rows, ox) => rows.map((row, r) => [...row].map((c, ci) => (c === '#' ? `<rect class="w-a${ox > 50 && r === 5 ? ' w-k' : ''}" style="--i:${k++}" x="${ox + ci * 8}" y="${27 + r * 8}" width="7" height="7" rx="1.2" fill="${cols[r]}"/>` : '')).join('')).join('');++ return { defs: '', body: draw(O, 7) + draw(V, 53) };++ },++ stars2(id) { // OV in stars, joined++ const ring = Array.from({ length: 6 }, (_, i) => { const a = (i / 6) * Math.PI * 2 - Math.PI / 2; return [30 + 18 * Math.cos(a), 50 + 18 * Math.sin(a)]; });++ const vee = [[58, 30], [71, 72], [84, 30]];++ const pts = ring.concat(vee);return {-- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.6', '#8b5cf6'], ['1', '#ec4899']]),-- body: `<path class="w-a w-k" d="M50 86 L18 52 C6 38 12 16 30 16 C40 16 46 22 50 30 C54 22 60 16 70 16 C88 16 94 38 82 52Z" fill="url(#${id}g)"/>`,++ defs: g(id),++ body: `<path d="M${ring.map((p) => p.map((n) => n.toFixed(1)).join(' ')).join(' L')}Z M58 30 L71 72 L84 30" fill="none" stroke="url(#${id}g)" stroke-width="2.2" opacity=".6"/>`++ + pts.map(([x, y], i) => `<circle class="w-a${i === 7 ? ' w-k' : ''}" style="--i:${i}" cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="${i === 7 ? 5.5 : 4}" fill="#e0e7ff"/>`).join(''),};},-- shield(id) {++ halftone(id) { // OV printed in halftone dots++ const dO = (x, y) => Math.abs(Math.hypot(x - 30, y - 50) - 18);++ const seg = (px, py, ax, ay, bx, by) => { const dx = bx - ax, dy = by - ay; const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy))); return Math.hypot(px - ax - t * dx, py - ay - t * dy); };++ const dV = (x, y) => Math.min(seg(x, y, 57, 28, 71, 72), seg(x, y, 71, 72, 85, 28));++ let out = '';++ let i = 0;++ for (let y = 20; y <= 80; y += 5) {++ for (let x = 8; x <= 92; x += 5) {++ const d = Math.min(dO(x, y), dV(x, y));++ const r = d < 6.8 ? 2.4 : 0;++ if (r) out += `<circle class="w-a" style="--i:${Math.round(x / 10)}" cx="${x}" cy="${y}" r="${r}"/>`;++ i += 1;++ }++ }++ return { defs: g(id), body: `<g class="w-k" fill="url(#${id}g)">${out}</g>` };++ },++ tiles(id) { // an O tile and a V tilereturn {-- defs: lin(`${id}g`, BV, 0, 0, 0, 1) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.4], ['1', '#fff', 0]], 0, 0, 1, 0),-- body: `<clipPath id="${id}c"><path d="M50 8 L84 20 V48 C84 70 68 84 50 92 C32 84 16 70 16 48 V20Z"/></clipPath><path class="w-k" d="M50 8 L84 20 V48 C84 70 68 84 50 92 C32 84 16 70 16 48 V20Z" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="26" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`-- + `<path d="M36 36 L50 64 L64 36" fill="none" stroke="#fff" stroke-width="8" ${R}/>`,++ defs: g(id),++ body: `<g class="w-a" style="--i:0"><rect class="w-k" x="8" y="28" width="40" height="40" rx="9" fill="#4f7cff" transform="rotate(-6 28 48)"/><circle cx="28" cy="48" r="9.5" fill="none" stroke="#fff" stroke-width="6" transform="rotate(-6 28 48)"/></g>`++ + `<g class="w-a" style="--i:1"><rect x="52" y="32" width="40" height="40" rx="9" fill="#8b5cf6" transform="rotate(7 72 52)"/><path d="M63 42 L72 62 L81 42" fill="none" stroke="#fff" stroke-width="6" ${R} transform="rotate(7 72 52)"/></g>`,};},-- chevrons(id) {-- const v = (y, i, o) => `<path class="w-a${i === 2 ? ' w-k' : ''}" style="--i:${i}" d="M22 ${y} L50 ${y + 22} L78 ${y}" fill="none" stroke="url(#${id}g)" stroke-width="9" ${R} opacity="${o}"/>`;-- return { defs: lin(`${id}g`, BV, 0, 0, 1, 0), body: `${v(14, 0, 0.35)}${v(36, 1, 0.65)}${v(58, 2, 1)}` };-- },-- infinity(id) {++ patch(id) { // an embroidered OV patchreturn {-- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.5', '#8b5cf6'], ['1', '#4f7cff']], 0, 0, 1, 0),-- body: `<path class="w-k" d="M50 50 C40 34 14 30 14 50 C14 70 40 66 50 50 C60 34 86 30 86 50 C86 70 60 66 50 50Z" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/>`-- + `<path class="w-a" pathLength="100" d="M50 50 C40 34 14 30 14 50 C14 70 40 66 50 50 C60 34 86 30 86 50 C86 70 60 66 50 50Z" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-dasharray="8 92" opacity=".85"/>`,++ defs: '',++ body: `<g class="w-a"><circle class="w-k" cx="50" cy="50" r="41" fill="#3b2a8f"/><circle cx="50" cy="50" r="41" fill="none" stroke="#8b5cf6" stroke-width="4"/><circle cx="50" cy="50" r="35" fill="none" stroke="#e0e7ff" stroke-width="1.8" stroke-dasharray="3 3"/>`++ + `<circle cx="36" cy="50" r="11" fill="none" stroke="#93c5fd" stroke-width="7"/><path d="M53 39 L61 62 L69 39" fill="none" stroke="#c4b5fd" stroke-width="7" ${R}/>`++ + `<g fill="none" stroke="#fff" stroke-width=".9" opacity=".35"><circle cx="36" cy="50" r="11" stroke-dasharray="1.5 1.5"/><path d="M53 39 L61 62 L69 39" stroke-dasharray="1.5 1.5"/></g></g>`,};},-- // ── Play and motion ──-- kite(id) {++ // ── A feel ──++ liquid(id) { // O and V as one drop of liquidreturn {-- defs: lin(`${id}a`, [['0', '#93c5fd'], ['1', '#4f7cff']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#6d28d9']]),-- body: `<g class="w-a"><path class="w-k" d="M50 8 L74 36 L50 64 Z" fill="url(#${id}a)"/><path d="M50 8 L26 36 L50 64Z" fill="url(#${id}b)"/><path d="M26 36 H74" stroke="#fff" stroke-width="2" opacity=".5"/>`-- + `<path class="w-b" d="M50 64 C46 72 56 76 50 84 C46 88 52 92 48 96" fill="none" stroke="#8b5cf6" stroke-width="3" stroke-linecap="round"/><path d="M47 76 l-6 -3 m6 3 l-4 5 M51 88 l6 -2 m-6 2 l4 5" stroke="#f472b6" stroke-width="3" stroke-linecap="round"/></g>`,++ defs: lin(`${id}g`, [['0', '#22d3ee'], ['.5', '#4f7cff'], ['1', '#8b5cf6']]),++ body: `<g class="w-a"><g class="w-k" fill="none" stroke="url(#${id}g)" stroke-width="13" ${R}><circle cx="31" cy="50" r="16"/><path d="M58 32 L71 70 L84 32"/></g>`++ + `<path d="M44 46 C50 44 54 44 59 41 L62 50 C56 52 51 54 45 55Z" fill="url(#${id}g)"/><path d="M71 76 C68 82 74 86 71 92" fill="none" stroke="#8b5cf6" stroke-width="5" stroke-linecap="round"/><ellipse cx="24" cy="42" rx="3.5" ry="2" fill="#fff" opacity=".5" transform="rotate(-35 24 42)"/></g>`,};},-- bird(id) {++ glass(id) { // frosted glass: an O pane over a Vreturn {-- defs: lin(`${id}a`, [['0', '#a5b4fc'], ['1', '#4f7cff']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#4c1d95']]),-- body: `<path class="w-a" d="M44 52 L18 22 L58 46Z" fill="url(#${id}a)"/><path class="w-k" d="M14 58 L58 46 L88 30 L74 56 L48 72Z" fill="url(#${id}b)"/><path class="w-b" d="M58 46 L82 18 L64 54Z" fill="url(#${id}a)" opacity=".85"/><path d="M88 30 L96 30 L86 36Z" fill="#f59e0b"/>`,++ defs: g(id),++ body: `<path class="w-k" d="M48 22 H64 L72 52 L80 22 H96 L80 80 H64Z" fill="url(#${id}g)"/>`++ + `<g class="w-a"><circle cx="38" cy="50" r="27" fill="#fff" fill-opacity=".14" stroke="#fff" stroke-opacity=".55" stroke-width="2"/><circle cx="38" cy="50" r="12" fill="#0b1020" fill-opacity=".55"/><path d="M20 38 A20 20 0 0 1 32 26" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" opacity=".7"/></g>`,};},-- bounce(id) {-- const dots = [[16, 20], [27, 40], [38, 62], [50, 80], [62, 62], [73, 40], [84, 20]];++ ribbon2(id) { // a ribbon that loops an O and folds a Vreturn {-- defs: lin(`${id}g`, BV, 0, 0, 1, 0),-- body: `<path d="M16 20 Q34 70 50 82 Q66 70 84 20" fill="none" stroke="url(#${id}g)" stroke-width="2.5" stroke-dasharray="2 6" stroke-linecap="round" opacity=".55"/>${dots.map(([x, y], i) => `<circle class="w-a" style="--i:${i}" cx="${x}" cy="${y}" r="${i === 3 ? 10 : 5}" fill="url(#${id}g)"${i === 3 ? '' : ' opacity=".55"'}/>`).join('')}<ellipse class="w-k" cx="50" cy="93" rx="12" ry="3" fill="#4f7cff" opacity=".35"/>`,++ defs: lin(`${id}g`, [['0', '#4f7cff'], ['1', '#a855f7']], 0, 0, 1, 0),++ body: `<path class="w-a w-k" pathLength="100" d="M8 70 C8 42 42 32 42 54 C42 72 14 72 18 52 C22 36 46 32 56 38 L68 74 L90 24" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/>`++ + `<path d="M8 70 C8 42 42 32 42 54 C42 72 14 72 18 52 C22 36 46 32 56 38 L68 74 L90 24" fill="none" stroke="#fff" stroke-width="2" ${R} opacity=".35"/>`,};},-- orbit(id) {++ sticker(id) { // a die-cut OV sticker, a corner peelingreturn {-- defs: lin(`${id}g`, BV),-- body: `<circle cx="50" cy="50" r="30" fill="none" stroke="url(#${id}g)" stroke-width="10"/><circle cx="50" cy="50" r="42" fill="none" stroke="#8b5cf6" stroke-width="2" stroke-dasharray="3 6" opacity=".55"/>`-- + `<g class="w-a"><circle class="w-k" cx="50" cy="8" r="7" fill="#f472b6"/></g>`,++ defs: g(id),++ body: `<g class="w-a" transform="rotate(-8 50 50)"><g fill="none" stroke="#000" stroke-opacity=".35" stroke-width="22" ${R} transform="translate(2 3)"><circle cx="31" cy="50" r="15"/><path d="M57 32 L70 70 L83 32"/></g>`++ + `<g fill="none" stroke="#fff" stroke-width="22" ${R}><circle cx="31" cy="50" r="15"/><path d="M57 32 L70 70 L83 32"/></g>`++ + `<g class="w-k" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}><circle cx="31" cy="50" r="15"/><path d="M57 32 L70 70 L83 32"/></g>`++ + `<path d="M80 72 L90 62 L86 74Z" fill="#e5e7eb"/></g>`,};},-- cursor(id) {++ mascot(id) { // a round O face with a V grinreturn {-- defs: lin(`${id}g`, BV),-- body: `<circle class="w-b" cx="30" cy="28" r="14" fill="none" stroke="#8b5cf6" stroke-width="3"/><g class="w-a"><path class="w-k" d="M30 28 L78 52 L56 58 L68 82 L58 86 L46 62 L30 76Z" fill="url(#${id}g)" stroke="#fff" stroke-width="3" stroke-linejoin="round"/></g><path d="${star4(80, 22, 9)}" fill="#f472b6"/>`,++ defs: rad(`${id}g`, [['0', '#93c5fd'], ['.6', '#4f7cff'], ['1', '#7c3aed']]),++ body: `<circle class="w-k" cx="50" cy="52" r="38" fill="url(#${id}g)"/><g class="w-a" fill="#fff"><circle cx="37" cy="42" r="5.5"/><circle cx="63" cy="42" r="5.5"/></g>`++ + `<path d="M36 56 L50 76 L64 56Z" fill="#0b1020" stroke="#0b1020" stroke-width="4" stroke-linejoin="round"/><path d="M44 69 Q50 64 56 69 L50 76Z" fill="#f472b6"/>`,};},};--/** [name, what the idea is, key, lockup] in eight rows of five. */++/** [name, what the idea is, key, lockup, motion] in eight rows of five. */const WILD_ROWS = [-- { title: 'Sound and signal', line: 'vibe as in vibration: sound, waves, a heartbeat', items: [-- ['Soundwave', 'a V drawn in sound bars: the bars bounce', 'soundwave', 'under'],-- ['Ripple', 'a dot and its waves going out', 'ripple', 'side'],-- ['Tuning fork', 'a V that rings when it is struck', 'fork', 'stack'],-- ['Pulse', 'a heartbeat line that draws itself: alive, live', 'pulse', 'under'],-- ['Speech bubble', 'a chat bubble the name sits in', 'bubble', 'bubble'],++ { title: 'The letters, made new', line: 'OV written five other ways', items: [++ ['Script', 'ov in one handwritten stroke that writes itself', 'script', 'under', 'draw'],++ ['Stencil', 'a stencil O and V; the pieces close up', 'stencil', 'side', 'join'],++ ['Neon', 'OV in neon tubes, flickering on', 'neon', 'side', 'flicker'],++ ['Blocks', 'OV as solid blocks with depth', 'block3d', 'stack', 'tilt'],++ ['Tag', 'OV sprayed as a tag, still dripping', 'tag', 'side', 'drip'],] },-- { title: 'Places and worlds', line: 'somewhere to go: open, wide, bright', items: [-- ['Open door', 'a door that swings open, light coming through', 'door', 'stack'],-- ['Planet', 'a world with a ring: many sites, one place', 'planet', 'side'],-- ['Sunrise', 'a sun rising between two hills: an O in a V', 'sunrise', 'stack'],-- ['Prism', 'a V of glass splitting light into colour', 'prism', 'side'],-- ['Paper plane', 'it glides, and pulls the name behind it', 'plane', 'trail'],++ { title: 'Monograms', line: 'the O and the V in one sign', items: [++ ['Woven', 'a V woven through an O', 'crossed', 'side', 'tilt'],++ ['Linked', 'an O and a V linked like a chain', 'chain', 'side', 'wobble'],++ ['Coin', 'OV struck on an OpenVibe coin', 'coin', 'pill', 'shine'],++ ['App tile', 'ov on an app tile, with a shine', 'tile', 'pill', 'shine'],++ ['Crest', 'an O over a V on a shield', 'crest', 'stack', 'shine'],] },-- { title: 'Friendly and alive', line: 'a character more than a logo', items: [-- ['Blob', 'a soft shape that breathes and smiles', 'blob', 'pill'],-- ['Smile', 'a friendly face that blinks', 'smile', 'side'],-- ['Eye', 'an open eye that looks around: watch, stream', 'eye', 'side'],-- ['Spark', 'a spark that twinkles: making things', 'spark', 'tag'],-- ['Overlap', 'two circles that meet: people together', 'overlap', 'side'],++ { title: 'An O standing on a V', line: 'put an O on a V and it becomes a thing', items: [++ ['Pin', 'a map pin is an O on a V: you are here', 'pin', 'tag', 'drop'],++ ['Ice cream', 'an O scoop on a V cone', 'cone', 'side', 'wobble'],++ ['Cheer', 'someone cheering: an O head, V arms up', 'cheer', 'stack', 'wave'],++ ['Mic', 'a microphone: the O head held in a V', 'mic', 'side', 'pulse'],++ ['Balloon', 'an O balloon on a V knot, floating', 'balloon', 'trail', 'float'],] },-- { title: 'Made of parts', line: 'built from pieces: stars, pixels, code', items: [-- ['Constellation', 'stars joined into a V, twinkling', 'stars', 'stack'],-- ['Pixel V', 'a V in big pixels that pop in: play', 'pixel', 'side'],-- ['Ribbon', 'a ribbon folded into a V', 'ribbon', 'tag'],-- ['Code', 'a V between brackets: open source', 'code', 'side'],-- ['Badge', 'ov on an app tile, with a shine', 'badge', 'pill'],++ { title: 'The O and the V in a scene', line: 'two letters, one little picture', items: [++ ['Bird and sun', 'a V bird gliding past an O sun', 'bird', 'side', 'glide'],++ ['Valley', 'an O sun rising in a V valley', 'valley', 'stack', 'rise'],++ ['Cocktail', 'a V glass with an O olive', 'cocktail', 'side', 'float'],++ ['Catch', 'an O ball dropping into a V cup', 'catch', 'side', 'hop'],++ ['Rocket', 'an O window, a V flame: launch', 'rocket', 'trail', 'rocket'],] },-- { title: 'Nature', line: 'things that move on their own', items: [-- ['Wave', 'a wave that rolls: ride the vibe', 'wave', 'under'],-- ['Flame', 'a flame with a V in its heart, flickering', 'flame', 'side'],-- ['Leaf', 'a leaf with a V vein, swaying', 'leaf', 'side'],-- ['Moon', 'a moon and a star for late streams', 'moon', 'tag'],-- ['Bolt', 'a bolt of energy that flashes', 'bolt', 'side'],++ { title: 'Signals', line: 'OpenVibe talking, playing, live', items: [++ ['Speech bubble', 'an O bubble with a V tail, typing', 'bubble', 'bubble', 'cascade'],++ ['Play', 'an O and a V turned on its side: ● ▶', 'play', 'pill', 'press'],++ ['Signal', 'V waves arriving at an O', 'signal', 'stack', 'cascade'],++ ['Heartbeat', 'a heartbeat that writes an o and a v', 'beat', 'under', 'draw'],++ ['Equaliser', 'an O with bars inside, in a V', 'eqring', 'side', 'bars'],] },-- { title: 'Things you hold', line: 'objects that say what OpenVibe does', items: [-- ['Headphones', 'headphones with a beat between them', 'headphones', 'stack'],-- ['Play', 'a play button: watch, stream, go', 'play', 'pill'],-- ['Antenna', 'a tower sending out a signal: live', 'antenna', 'stack'],-- ['Gem', 'a gem with a V cut and a glint', 'gem', 'side'],-- ['Dice', 'five dots in a V on a die: play', 'dice', 'side'],++ { title: 'Things that are an O and a V', line: 'look twice', items: [++ ['Bulb', 'a bulb with a V filament: ideas', 'bulb', 'side', 'flicker'],++ ['Ten past ten', 'an O clock whose hands make a V', 'clock', 'side', 'tilt'],++ ['Target', 'an O target, an arrow with a V tail', 'target', 'tag', 'land'],++ ['Record', 'an O record with a V on its label', 'vinyl', 'side', 'pulse'],++ ['Orbit', 'a V with an O orbit round it', 'orbit', 'side', 'tilt'],] },-- { title: 'Signs and symbols', line: 'shapes everyone already reads', items: [-- ['Pin', 'a map pin is an O on a V: you are here', 'pin', 'tag'],-- ['Heart', 'a heart whose point is the V, beating', 'heart', 'side'],-- ['Shield', 'a V on a shield: safe, yours', 'shield', 'pill'],-- ['Chevrons', 'three Vs that cascade', 'chevrons', 'stack'],-- ['Infinity', 'a loop with a light running round it: open forever', 'infinity', 'under'],++ { title: 'Built from parts', line: 'OV made of pieces', items: [++ ['Pixels', 'O and V in big pixels that pop in: play', 'pixel8', 'side', 'pop'],++ ['Stars', 'OV in stars, joined, twinkling', 'stars2', 'stack', 'twinkle'],++ ['Halftone', 'OV printed in dots', 'halftone', 'side', 'pop'],++ ['Tiles', 'an O tile and a V tile that hop', 'tiles', 'side', 'hop'],++ ['Patch', 'an embroidered OV patch', 'patch', 'pill', 'tilt'],] },-- { title: 'Play and motion', line: 'caught mid-move', items: [-- ['Kite', 'a kite on the wind, its tail waving', 'kite', 'trail'],-- ['Paper bird', 'a folded bird that flaps', 'bird', 'trail'],-- ['Bounce', 'a ball bouncing a V into the air', 'bounce', 'stack'],-- ['Orbit', 'a moon going round an O', 'orbit', 'side'],-- ['Cursor', 'a pointer that clicks: agents doing things', 'cursor', 'tag'],++ { title: 'A feel', line: 'the same two letters, five moods', items: [++ ['Liquid', 'O and V as one drop of liquid', 'liquid', 'side', 'wobble'],++ ['Glass', 'a frosted O pane over a V', 'glass', 'side', 'glide'],++ ['Ribbon', 'a ribbon that loops an O and folds a V', 'ribbon2', 'under', 'draw'],++ ['Sticker', 'a die-cut OV sticker, a corner peeling', 'sticker', 'tag', 'wobble'],++ ['Face', 'a round O face with a V grin, blinking', 'mascot', 'side', 'blink'],] },];++const MOTIONS = ['draw', 'join', 'flicker', 'tilt', 'drip', 'wobble', 'shine', 'drop', 'wave', 'pulse', 'float', 'glide', 'rise', 'hop', 'rocket', 'cascade', 'press', 'bars', 'land', 'pop', 'twinkle', 'blink'];++/** The drawing for one key: { defs, body }, every id starting with `id`. */function wildArt(key, id) {const draw = ART[key];return draw ? draw(id) : { defs: '', body: '' };}--module.exports = { WILD_ROWS, wildArt, KEYS: Object.keys(ART) };++module.exports = { WILD_ROWS, wildArt, KEYS: Object.keys(ART), MOTIONS };@@@ -114,14 +114,16 @@ const explore = require('../server/views/marks-explore');// Each wildcard moves its own way, attaches the name its own way, and shows itself as a status.const wilds = r.text.match(/<li class="mx-card mx-wild" id="W\d\d"[\s\S]*?<\/li>/g) || [];assert.strictEqual(wilds.length, 40);-- assert.strictEqual(new Set(wilds.map((c) => /data-wild="([a-z]+)"/.exec(c)[1])).size, 40, 'forty different ideas');++ assert.strictEqual(new Set(wilds.map((c) => /data-wild="([a-z0-9]+)"/.exec(c)[1])).size, 40, 'forty different ideas');assert.ok(wilds.every((c) => /<div class="mx-lock lk-(side|pill|under|bubble|stack|tag|trail)">/.test(c)), 'a lockup each');assert.ok(wilds.every((c) => /class="[^"]*\bw-k\b/.test(c)), 'a key part each, for the live status');assert.ok(wilds.every((c) => /data-st="live"[\s\S]*data-st="new"/.test(c)), 'the live and new slots');assert.ok(new Set(wilds.map((c) => /lk-([a-z]+)/.exec(c)[1])).size >= 6, 'the name attaches in several ways');const css = require('fs').readFileSync(require('path').join(__dirname, '..', 'public', 'css', 'marks.css'), 'utf8');-- const { KEYS } = require('../server/views/wild-marks');-- assert.deepStrictEqual(KEYS.filter((k) => !css.includes(`svg[data-wild=${k}]`) && !css.includes(`svg[data-wild=${k}],`)), [], 'every idea has its motion');++ const { MOTIONS, WILD_ROWS } = require('../server/views/wild-marks');++ assert.deepStrictEqual(MOTIONS.filter((m) => !css.includes(`svg[data-wild-motion=${m}]`)), [], 'every kind of motion is in the CSS');++ assert.ok(WILD_ROWS.every((r) => r.items.every((it) => MOTIONS.includes(it[4]))), 'every wildcard has one');++ assert.ok(wilds.every((c) => /data-wild-motion="[a-z]+"/.test(c)));});await check('the files at /brand/:file still answer', async () => {
@@ -478,63 +478,37 @@ html[data-ov-motion=reduced] .mb-btn *{animation:none!important}
@@ -478,63 +478,37 @@ html[data-ov-motion=reduced] .mb-btn *{animation:none!important}
@keyframes stDot{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.35);opacity:.6}}
@keyframes stDot{0%,100%{transform:scale(1);opacity:1}50%{transform:scale(1.35);opacity:.6}}
.mx-wrap[data-density=sheet] .mx-status{display:none}
.mx-wrap[data-density=sheet] .mx-status{display:none}
-/* Each idea's own motion: only while its card is on screen, with long rests, and never under reduced motion. */
+/* Each wildcard moves by its kind of motion (data-wild-motion): only while its card is on screen, with long rests,
+ and never under reduced motion. The parts that move are w-a, w-b and w-c. */
.mx-wild svg :is(.w-a,.w-b,.w-c){transform-box:fill-box;transform-origin:center}
.mx-wild svg :is(.w-a,.w-b,.w-c){transform-box:fill-box;transform-origin:center}
@media (prefers-reduced-motion:no-preference){
@media (prefers-reduced-motion:no-preference){
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild] :is(.w-a,.w-b,.w-c){animation-duration:3.6s;animation-iteration-count:infinite;animation-timing-function:ease-in-out;animation-delay:calc(var(--d,0s) + var(--i,0) * .09s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=soundwave] .w-a{transform-origin:top;animation-name:wBar;animation-duration:1.6s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=ripple] .w-a{animation-name:wRipple;animation-duration:2.4s;animation-delay:calc(var(--i,0) * .25s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=fork] .w-a{transform-origin:bottom;animation-name:wHum}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=fork] .w-b{animation-name:wFlash}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pulse] .w-a{stroke-dasharray:100;animation-name:wDraw;animation-duration:3s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bubble] .w-a{animation-name:wBob}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bubble] .w-b{stroke-dasharray:100;animation-name:wDraw}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=door] .w-a{transform-origin:right center;animation-name:wSwing;animation-duration:4.4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=door] .w-b{animation-name:wGlow;animation-duration:4.4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=planet] .w-a{animation-name:wTilt;animation-duration:6s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=sunrise] .w-a{animation-name:wRise;animation-duration:5s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=prism] .w-a{animation-name:wShimmer;animation-duration:2.4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=plane] .w-a{animation-name:wGlide;animation-duration:4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=plane] .w-b{animation-name:wFlash;animation-duration:4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=blob] .w-a{animation-name:wMorph;animation-duration:5s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=smile] .w-a{animation-name:wBlink;animation-duration:4.2s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=eye] .w-a{animation-name:wLook;animation-duration:5s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=eye] .w-b{animation-name:wBlink;animation-duration:5s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=spark] :is(.w-a,.w-b,.w-c){animation-name:wTwinkle;animation-duration:3s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=spark] .w-b{animation-delay:.5s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=spark] .w-c{animation-delay:1s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=overlap] .w-a{animation-name:wMeetL;animation-duration:4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=overlap] .w-b{animation-name:wMeetR;animation-duration:4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=stars] :is(.w-a,.w-b){animation-name:wTwinkle;animation-duration:2.8s;animation-delay:calc(var(--i,0) * .35s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pixel] .w-a{animation-name:wPop;animation-duration:3.4s;animation-delay:calc(var(--i,0) * .06s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=ribbon] .w-a{transform-origin:top;animation-name:wSway;animation-duration:4.6s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=code] .w-a{animation-name:wCaret;animation-duration:1.2s;animation-timing-function:steps(1)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=code] .w-b{animation-name:wOpenL}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=code] .w-c{animation-name:wOpenR}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) :is(svg[data-wild=badge],svg[data-wild=shield]) .w-a{animation-name:wShine;animation-duration:3.8s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=wave] .w-a{animation-name:wRoll;animation-duration:4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=wave] .w-b{animation-name:wSpray;animation-duration:4s;animation-delay:calc(var(--i,0) * .3s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=flame] .w-a{transform-origin:bottom;animation-name:wFlicker;animation-duration:1.8s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=leaf] .w-a{transform-origin:bottom;animation-name:wSway;animation-duration:4.6s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=moon] :is(.w-a,.w-b){animation-name:wTwinkle;animation-duration:3.2s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bolt] .w-a{animation-name:wZap;animation-duration:3.4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=headphones] .w-a rect{transform-box:fill-box;transform-origin:center;animation:wBeat .9s ease-in-out infinite;animation-delay:calc(var(--i,0) * .15s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=play] .w-a{animation-name:wPress;animation-duration:3s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=play] .w-b{animation-name:wRing;animation-duration:3s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=antenna] .w-a path{animation:wFlash 2s ease-in-out infinite;animation-delay:calc(var(--i,0) * .35s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=gem] .w-a{animation-name:wTwinkle;animation-duration:3.2s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=dice] .w-a{animation-name:wRoll90;animation-duration:5s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pin] .w-a{transform-origin:bottom;animation-name:wDrop;animation-duration:3.6s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=pin] .w-b{animation-name:wShadow;animation-duration:3.6s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=heart] .w-a{animation-name:wHeart;animation-duration:2.2s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=chevrons] .w-a{animation-name:wCascade;animation-duration:2.4s;animation-delay:calc(var(--i,0) * .22s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=infinity] .w-a{animation:wLoop 3.2s linear infinite}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=kite] .w-a{transform-origin:50% 0;animation-name:wSway;animation-duration:4.4s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bird] :is(.w-a,.w-b){transform-origin:bottom;animation-name:wFlap;animation-duration:1.6s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=bounce] .w-a{animation-name:wHop;animation-duration:2.8s;animation-delay:calc(var(--i,0) * .12s)}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=orbit] .w-a{transform-box:view-box;transform-origin:50px 50px;animation:wOrbit 6s linear infinite}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=cursor] .w-a{animation-name:wClick;animation-duration:3s}
- .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild=cursor] .w-b{animation-name:wRing;animation-duration:3s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion] :is(.w-a,.w-b,.w-c){animation-duration:3.6s;animation-iteration-count:infinite;animation-timing-function:ease-in-out;animation-delay:calc(var(--d,0s) + var(--i,0) * .09s)}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=draw] .w-a{stroke-dasharray:100;animation-name:wDraw;animation-duration:3.4s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=join] .w-a{animation-name:wJoinL}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=join] .w-b{animation-name:wJoinR}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=flicker] :is(.w-a,.w-b){animation-name:wFlick;animation-duration:3.2s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=flicker] .w-b{animation-delay:.35s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=tilt] .w-a{animation-name:wTilt;animation-duration:5s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=drip] .w-a{transform-origin:top;animation-name:wDrip;animation-duration:3s;animation-delay:calc(var(--i,0) * .4s)}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=wobble] .w-a{animation-name:wMorph;animation-duration:4.4s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=shine] .w-a{animation-name:wShine;animation-duration:3.8s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=drop] .w-a{transform-origin:bottom;animation-name:wDrop}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=drop] .w-b{animation-name:wShadow}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=wave] .w-a{transform-origin:bottom center;animation-name:wCheer;animation-duration:2.6s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=pulse] :is(.w-a,.w-b){animation-name:wFlash;animation-duration:2.4s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=float] .w-a{animation-name:wBob;animation-duration:3.4s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=glide] .w-a{animation-name:wGlide;animation-duration:4s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=rise] .w-a{animation-name:wRise;animation-duration:5s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=hop] .w-a{animation-name:wHop;animation-duration:2.4s;animation-delay:calc(var(--i,0) * .25s)}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=rocket] .w-a{animation-name:wBob;animation-duration:2.2s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=rocket] .w-b{transform-origin:top;animation-name:wFlame;animation-duration:.5s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=cascade] .w-a{animation-name:wCascade;animation-duration:1.6s;animation-delay:calc(var(--i,0) * .2s)}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=press] .w-a{animation-name:wPress;animation-duration:3s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=bars] .w-a{transform-origin:bottom;animation-name:wBar;animation-duration:1.4s;animation-delay:calc(var(--i,0) * .1s)}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=land] .w-a{animation-name:wLand;animation-duration:3.4s}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=pop] .w-a{animation-name:wPop;animation-duration:3.4s;animation-delay:calc(var(--i,0) * .045s)}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=twinkle] .w-a{animation-name:wTwinkle;animation-duration:2.8s;animation-delay:calc(var(--i,0) * .3s)}
+ .mx-wrap.mx-live:not(.mx-still) :is(.mx-card.is-on,.st) svg[data-wild-motion=blink] .w-a{animation-name:wBlink;animation-duration:4.2s}
}
}
@keyframes wBar{0%,100%{transform:scaleY(1)}30%{transform:scaleY(.55)}60%{transform:scaleY(1.08)}}
@keyframes wBar{0%,100%{transform:scaleY(1)}30%{transform:scaleY(.55)}60%{transform:scaleY(1.08)}}
@keyframes wRipple{0%,60%,100%{opacity:1;transform:scale(1)}25%{opacity:.2;transform:scale(1.08)}}
@keyframes wRipple{0%,60%,100%{opacity:1;transform:scale(1)}25%{opacity:.2;transform:scale(1.08)}}
@@ -577,3 +551,10 @@ html[data-ov-motion=reduced] .mb-btn *{animation:none!important}
@@ -577,3 +551,10 @@ html[data-ov-motion=reduced] .mb-btn *{animation:none!important}
@keyframes wHop{0%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}}
@keyframes wHop{0%,100%{transform:translateY(0)}30%{transform:translateY(-6px)}}
@keyframes wOrbit{to{transform:rotate(360deg)}}
@keyframes wOrbit{to{transform:rotate(360deg)}}
@keyframes wClick{0%,40%,100%{transform:scale(1)}10%{transform:scale(.88)}20%{transform:scale(1)}}
@keyframes wClick{0%,40%,100%{transform:scale(1)}10%{transform:scale(.88)}20%{transform:scale(1)}}
+@keyframes wJoinL{0%,100%{transform:translateX(0)}20%,45%{transform:translateX(-4px)}}
+@keyframes wJoinR{0%,100%{transform:translateX(0)}20%,45%{transform:translateX(4px)}}
+@keyframes wFlick{0%,100%{opacity:1}4%{opacity:.25}7%{opacity:1}10%{opacity:.5}13%{opacity:1}}
+@keyframes wDrip{0%,30%{transform:scaleY(.2);opacity:0}45%{opacity:1}75%,100%{transform:scaleY(1);opacity:1}}
+@keyframes wCheer{0%,100%{transform:rotate(0)}20%{transform:rotate(-7deg)}40%{transform:rotate(7deg)}60%{transform:rotate(0)}}
+@keyframes wFlame{0%,100%{transform:scaleY(1)}50%{transform:scaleY(.75)}}
+@keyframes wLand{0%{transform:translate(14px,-14px);opacity:0}25%,85%{transform:none;opacity:1}100%{transform:none;opacity:1}}
@@ -619,13 +619,13 @@ function lockOf(kind, ref) {
@@ -619,13 +619,13 @@ function lockOf(kind, ref) {
}
}
}
}
-function markCard(code, kind, [name, line_, spec, lock], i) {
+function markCard(code, kind, [name, line_, spec, lock, motion], i) {
if (kind === 'W') {
if (kind === 'W') {
const art = wildArt(spec, `m${++seq}`);
const art = wildArt(spec, `m${++seq}`);
const ref = `art-${code}`;
const ref = `art-${code}`;
return `<li class="mx-card mx-wild" id="${code}" data-code="${code}" style="${delay(i)}">
return `<li class="mx-card mx-wild" id="${code}" data-code="${code}" style="${delay(i)}">
<button type="button" class="mx-pick" data-pick="${code}" aria-pressed="false" aria-label="Pick ${code}"></button>
<button type="button" class="mx-pick" data-pick="${code}" aria-pressed="false" aria-label="Pick ${code}"></button>
-<div class="mx-stage"><svg viewBox="0 0 100 100" width="132" height="132" role="img" aria-label="${code} ${esc(name)}" data-idle="wild" data-wild="${spec}"><defs>${art.defs}</defs><g id="${ref}">${art.body}</g></svg></div>
+<div class="mx-stage"><svg viewBox="0 0 100 100" width="132" height="132" role="img" aria-label="${code} ${esc(name)}" data-idle="wild" data-wild="${spec}" data-wild-motion="${motion || 'tilt'}"><defs>${art.defs}</defs><g id="${ref}">${art.body}</g></svg></div>
<div class="mx-lock lk-${lock || 'side'}">${lockOf(lock, ref)}</div>
<div class="mx-lock lk-${lock || 'side'}">${lockOf(lock, ref)}</div>
<div class="mx-status" aria-label="As a status: live and new"><span class="mx-dark">${useOf(ref, 16)}${useOf(ref, 28)}</span><span class="st st-live" data-st="live" title="Live"></span><span class="st st-new" data-st="new" title="Something new"></span></div>
<div class="mx-status" aria-label="As a status: live and new"><span class="mx-dark">${useOf(ref, 16)}${useOf(ref, 28)}</span><span class="st st-live" data-st="live" title="Live"></span><span class="st st-new" data-st="new" title="Something new"></span></div>
<p class="mx-name"><b>${code}</b> ${esc(name)}</p>
<p class="mx-name"><b>${code}</b> ${esc(name)}</p>
@@ -702,8 +702,8 @@ function panel(id, kind, rows, label, hidden) {
@@ -702,8 +702,8 @@ function panel(id, kind, rows, label, hidden) {
return `<section class="mx-row"><h3>${esc(row.title)} <small>${kind}${pad(first)}–${kind}${pad(n)} · ${row.items.length}</small><span class="mx-swipe" aria-hidden="true">swipe →</span></h3><p class="mx-row-line">${esc(row.line)}</p><ul class="mx-grid">${cards}</ul></section>`;
return `<section class="mx-row"><h3>${esc(row.title)} <small>${kind}${pad(first)}–${kind}${pad(n)} · ${row.items.length}</small><span class="mx-swipe" aria-hidden="true">swipe →</span></h3><p class="mx-row-line">${esc(row.line)}</p><ul class="mx-grid">${cards}</ul></section>`;
}).join('');
}).join('');
const NEXT = {
const NEXT = {
- loop: [['pair', 'See the Pair', 'the second kind'], ['wild', 'See the Wildcards', 'forty other ideas'], ['menu', 'Next: menu button', 'step 2']],
- pair: [['loop', 'See the Loop', 'the first kind'], ['wild', 'See the Wildcards', 'forty other ideas'], ['menu', 'Next: menu button', 'step 2']],
+ loop: [['pair', 'See the Pair', 'the second kind'], ['wild', 'See the Wildcards', 'OV, forty other ways'], ['menu', 'Next: menu button', 'step 2']],
+ pair: [['loop', 'See the Loop', 'the first kind'], ['wild', 'See the Wildcards', 'OV, forty other ways'], ['menu', 'Next: menu button', 'step 2']],
wild: [['loop', 'See the Loop', 'the first kind'], ['pair', 'See the Pair', 'the second kind'], ['menu', 'Next: menu button', 'step 2']],
wild: [['loop', 'See the Loop', 'the first kind'], ['pair', 'See the Pair', 'the second kind'], ['menu', 'Next: menu button', 'step 2']],
menu: [['loop', 'Back', 'step 1, the logo'], ['font', 'Next: font', 'step 3']],
menu: [['loop', 'Back', 'step 1, the logo'], ['font', 'Next: font', 'step 3']],
font: [['menu', 'Back', 'step 2, the menu button'], ['share', 'Done: share', 'your votes are in']],
font: [['menu', 'Back', 'step 2, the menu button'], ['share', 'Done: share', 'your votes are in']],
@@ -714,7 +714,7 @@ function panel(id, kind, rows, label, hidden) {
@@ -714,7 +714,7 @@ function panel(id, kind, rows, label, hidden) {
const STEP = {
const STEP = {
loop: [1, 'Pick a logo: the Loop', 'Tap the ones you like. Look at the Pair and the Wildcards too.'],
loop: [1, 'Pick a logo: the Loop', 'Tap the ones you like. Look at the Pair and the Wildcards too.'],
pair: [1, 'Pick a logo: the Pair', 'Tap the ones you like. Look at the Loop and the Wildcards too.'],
pair: [1, 'Pick a logo: the Pair', 'Tap the ones you like. Look at the Loop and the Wildcards too.'],
- wild: [1, 'Pick a logo: the Wildcards', 'Forty other ideas. Each moves, shows a status, and wears the name its own way.'],
+ wild: [1, 'Pick a logo: the Wildcards', 'The O and the V, forty other ways. Each moves, shows a status, and wears the name its own way.'],
menu: [2, 'Pick a menu button', 'Tap one to see it open.'],
menu: [2, 'Pick a menu button', 'Tap one to see it open.'],
font: [3, 'Pick a font', 'Tap the one that looks right.'],
font: [3, 'Pick a font', 'Tap the one that looks right.'],
}[id];
}[id];
@@ -794,7 +794,7 @@ ${tab('loop', 'The Loop', 'A V inside a ring', `<svg viewBox="0 0 100 100" width
@@ -794,7 +794,7 @@ ${tab('loop', 'The Loop', 'A V inside a ring', `<svg viewBox="0 0 100 100" width
<span class="mx-or" aria-hidden="true">+</span>
<span class="mx-or" aria-hidden="true">+</span>
${tab('pair', 'The Pair', 'An O next to a V', `<svg viewBox="0 0 100 100" width="58" height="58" aria-hidden="true"><defs>${pairEx.defs}</defs>${pairEx.body}</svg>`, `P01–P${PAIRS.length} · ${PAIRS.length} takes`)}
${tab('pair', 'The Pair', 'An O next to a V', `<svg viewBox="0 0 100 100" width="58" height="58" aria-hidden="true"><defs>${pairEx.defs}</defs>${pairEx.body}</svg>`, `P01–P${PAIRS.length} · ${PAIRS.length} takes`)}
<span class="mx-or" aria-hidden="true">+</span>
<span class="mx-or" aria-hidden="true">+</span>
-${tab('wild', 'The Wildcards', 'Forty other ideas', `<svg viewBox="0 0 100 100" width="58" height="58" aria-hidden="true"><defs>${wildEx.defs}</defs>${wildEx.body}</svg>`, `W01–W${pad(WN)} · ${WN} ideas`)}
+${tab('wild', 'The Wildcards', 'OV, forty other ways', `<svg viewBox="0 0 100 100" width="58" height="58" aria-hidden="true"><defs>${wildEx.defs}</defs>${wildEx.body}</svg>`, `W01–W${pad(WN)} · ${WN} ideas`)}
</div>
</div>
<p class="mx-q" role="presentation"><span class="mx-qn">2</span> <b>Pick a menu button</b> <small>it sits next to the logo</small></p>
<p class="mx-q" role="presentation"><span class="mx-qn">2</span> <b>Pick a menu button</b> <small>it sits next to the logo</small></p>
${tab('menu', 'The menu button', 'The button next to the logo', `<span class="mb-btn mb-tabbtn">${menuSvg(m1[2], m1[3], m1[4], 30)}</span>`, 'M01–M50', false, true)}
${tab('menu', 'The menu button', 'The button next to the logo', `<span class="mb-btn mb-tabbtn">${menuSvg(m1[2], m1[3], m1[4], 30)}</span>`, 'M01–M50', false, true)}
@@ -1,359 +1,399 @@
@@ -1,359 +1,399 @@
'use strict';
'use strict';
/**
/**
- * The third choice on /brand/explore (the owner, 2026-11-01: "an option 3… wildly different variations… be creative…
- * completely different ideas for OpenVibe branding… 40 of them… so you can choose the Loop, the Pair and/or these…
- * be thoughtful about animations and effects… status icons… how the label could be attached").
+ * The third choice on /brand/explore, the Wildcards (the owner, 2026-11-01: "an option 3… wildly different variations…
+ * 40 of them… be thoughtful about animations… status icons… how the label could be attached"; then, the same night:
+ * "remake all the wildcards… they're not related to OpenVibe or OV… make them specific, branded to our vibe").
*
*
- * Not an O and a V: forty other ideas of what OpenVibe is, in eight rows of five, each drawn by hand on the page's
- * 100-unit grid in the brand's blue to violet (with a warm accent where the idea needs light). Each one also says:
- * - its motion: parts of the drawing carry classes (w-a, w-b, w-c…) that css/marks.css animates for that idea (bars
- * bounce, the eye blinks, the sun rises, the plane glides), only while the card is on screen and never under
- * reduced motion;
- * - its key part (w-k): what lights up when the mark shows a status (live: red and pulsing; new: a count badge);
- * - its lockup: how the name attaches to it (side, pill, under, bubble, stack, tag, trail).
- * Every drawing is a function of an id prefix, so a mark can be drawn more than once on a page with ids that never clash.
+ * Every one is the O and the V, forty ways: the letters made new (script, stencil, neon, blocks, graffiti), monograms
+ * (woven, linked, a coin, an app tile, a crest), an O standing on a V (a map pin, an ice cream, someone cheering, a
+ * microphone, a balloon), the two as a scene (a bird past the sun, a sun in a valley, a cocktail, a ball and a cup, a
+ * rocket), signals (a speech bubble, play, a signal, a heartbeat, an equaliser), things that are an O and a V (a bulb, a
+ * clock at ten past ten, a target, a record, an orbit), letters built from parts (pixels, stars, halftone dots, tiles,
+ * an embroidered patch) and a feel (liquid, glass, a ribbon, a sticker, a face).
+ *
+ * Each drawing says:
+ * - its motion (data-wild-motion on the card's SVG; parts w-a, w-b, w-c move), animated by css/marks.css only while
+ * the card is on screen and never under reduced motion;
+ * - its key part (w-k): what lights up when the mark shows a status (live: red and pulsing; new: a count);
+ * - its lockup: how the name attaches (side, pill, under, bubble, stack, tag, trail).
+ * Every drawing is a function of an id prefix, so a mark can be drawn more than once on a page with unique ids.
*/
*/
const BV = [['0', '#4f7cff'], ['1', '#8b5cf6']];
const BV = [['0', '#4f7cff'], ['1', '#8b5cf6']];
+const WARM = [['0', '#fde68a'], ['1', '#f97316']];
const lin = (id, stops, x1 = 0, y1 = 0, x2 = 1, y2 = 1) => `<linearGradient id="${id}" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}">${stops.map(([o, c, a]) => `<stop offset="${o}" stop-color="${c}"${a != null ? ` stop-opacity="${a}"` : ''}/>`).join('')}</linearGradient>`;
const lin = (id, stops, x1 = 0, y1 = 0, x2 = 1, y2 = 1) => `<linearGradient id="${id}" x1="${x1}" y1="${y1}" x2="${x2}" y2="${y2}">${stops.map(([o, c, a]) => `<stop offset="${o}" stop-color="${c}"${a != null ? ` stop-opacity="${a}"` : ''}/>`).join('')}</linearGradient>`;
const rad = (id, stops) => `<radialGradient id="${id}" cx=".35" cy=".3" r=".8">${stops.map(([o, c, a]) => `<stop offset="${o}" stop-color="${c}"${a != null ? ` stop-opacity="${a}"` : ''}/>`).join('')}</radialGradient>`;
const rad = (id, stops) => `<radialGradient id="${id}" cx=".35" cy=".3" r=".8">${stops.map(([o, c, a]) => `<stop offset="${o}" stop-color="${c}"${a != null ? ` stop-opacity="${a}"` : ''}/>`).join('')}</radialGradient>`;
const R = 'stroke-linecap="round" stroke-linejoin="round"';
const R = 'stroke-linecap="round" stroke-linejoin="round"';
-const star4 = (cx, cy, r) => `M${cx} ${cy - r} C${cx + r * 0.08} ${cy - r * 0.3} ${cx + r * 0.3} ${cy - r * 0.08} ${cx + r} ${cy} C${cx + r * 0.3} ${cy + r * 0.08} ${cx + r * 0.08} ${cy + r * 0.3} ${cx} ${cy + r} C${cx - r * 0.08} ${cy + r * 0.3} ${cx - r * 0.3} ${cy + r * 0.08} ${cx - r} ${cy} C${cx - r * 0.3} ${cy - r * 0.08} ${cx - r * 0.08} ${cy - r * 0.3} ${cx} ${cy - r}Z`;
+const g = (id) => lin(`${id}g`, BV);
const ART = {
const ART = {
- // ── Sound and signal ──
- soundwave(id) {
- const xs = [22, 31, 40, 50, 60, 69, 78];
- const bars = xs.map((x, i) => { const h = 14 + 44 * (1 - Math.abs(x - 50) / 28); return `<rect class="w-a${x === 50 ? ' w-k' : ''}" style="--i:${i}" x="${x - 3.5}" y="20" width="7" height="${h.toFixed(1)}" rx="3.5"/>`; }).join('');
- return { defs: lin(`${id}g`, BV, 0, 0, 1, 1), body: `<g fill="url(#${id}g)">${bars}</g><rect x="14" y="82" width="72" height="4" rx="2" fill="url(#${id}g)" opacity=".35"/>` };
- },
- ripple(id) {
- const arc = (r, o, i) => `<path class="w-a" style="--i:${i}" d="M28 ${72 - r} A${r} ${r} 0 0 1 ${28 + r} 72" fill="none" stroke="url(#${id}g)" stroke-width="8" stroke-linecap="round" opacity="${o}"/>`;
- return { defs: lin(`${id}g`, BV, 0, 1, 1, 0), body: `${arc(24, 1, 0)}${arc(39, 0.72, 1)}${arc(54, 0.45, 2)}<circle class="w-k" cx="28" cy="72" r="10" fill="url(#${id}g)"/>` };
+ // ── Letters, made new ──
+ script(id) { // a handwritten "ov" in one stroke that draws itself
+ return { defs: g(id), body: `<path class="w-a w-k" pathLength="100" d="M33 41 C22 40 14 48 14 57 C14 67 21 74 31 74 C40 74 47 66 47 57 C47 50 43 44 37 42 C44 45 51 46 57 41 L70 75 L87 31" fill="none" stroke="url(#${id}g)" stroke-width="8.5" ${R}/>` };
},
},
- fork(id) {
+ stencil(id) { // a stencil O and V, cut with bridges; the pieces close up
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 0, 1),
- body: `<g class="w-a"><path class="w-k" d="M32 14 L50 56 L68 14" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/></g><path d="M50 56 V88" stroke="url(#${id}g)" stroke-width="10" stroke-linecap="round"/>`
- + `<g class="w-b" fill="none" stroke="#8b5cf6" stroke-width="4" stroke-linecap="round" opacity=".65"><path d="M18 20 q-7 9 0 18"/><path d="M10 15 q-10 14 0 28"/><path d="M82 20 q7 9 0 18"/><path d="M90 15 q10 14 0 28"/></g>`,
+ defs: g(id) + `<mask id="${id}m"><rect width="100" height="100" fill="#fff"/><path d="M48 51 H98" stroke="#000" stroke-width="4.5"/></mask>`,
+ body: `<g class="w-a"><circle cx="31" cy="51" r="19" fill="none" stroke="url(#${id}g)" stroke-width="13" stroke-dasharray="54.7 5" transform="rotate(-92.4 31 51)"/></g>`
+ + `<g class="w-b w-k" mask="url(#${id}m)"><path d="M53 25 H66 L72 50 L78 25 H91 L78 77 H66Z" fill="url(#${id}g)"/></g>`,
};
};
},
},
- pulse(id) {
+ neon(id) { // neon tubes, pink and blue, flickering on
+ const f = `<filter id="${id}f" x="-40%" y="-40%" width="180%" height="180%"><feGaussianBlur stdDeviation="2.6" result="b"/><feMerge><feMergeNode in="b"/><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge></filter>`;
return {
return {
- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.55', '#8b5cf6'], ['1', '#ec4899']], 0, 0, 1, 0),
- body: `<rect x="6" y="14" width="88" height="72" rx="22" fill="#4f7cff" opacity=".1"/><path class="w-a w-k" pathLength="100" d="M8 54 H28 L36 38 L48 74 L60 22 L68 54 H92" fill="none" stroke="url(#${id}g)" stroke-width="8" ${R}/>`,
+ defs: f,
+ body: `<g class="w-a" filter="url(#${id}f)"><circle class="w-k" cx="31" cy="52" r="18" fill="none" stroke="#f472b6" stroke-width="6"/><circle cx="31" cy="52" r="18" fill="none" stroke="#fdf2f8" stroke-width="1.8"/></g>`
+ + `<g class="w-b" filter="url(#${id}f)"><path d="M57 33 L71 72 L85 33" fill="none" stroke="#22d3ee" stroke-width="6" ${R}/><path d="M57 33 L71 72 L85 33" fill="none" stroke="#ecfeff" stroke-width="1.8" ${R}/></g>`,
};
};
},
},
- bubble(id) {
- return {
- defs: lin(`${id}g`, BV),
- body: `<g class="w-a"><path class="w-k" d="M24 16 H76 a12 12 0 0 1 12 12 V60 a12 12 0 0 1 -12 12 H46 L28 88 V72 H24 a12 12 0 0 1 -12 -12 V28 a12 12 0 0 1 12 -12Z" fill="url(#${id}g)"/>`
- + `<path class="w-b" pathLength="100" d="M35 34 L50 56 L65 34" fill="none" stroke="#fff" stroke-width="8" ${R}/></g>`,
- };
+ block3d(id) { // solid block letters with depth
+ const shapes = (dx, dy, stroke) => `<circle cx="${31 + dx}" cy="${48 + dy}" r="17" fill="none" stroke="${stroke}" stroke-width="12"/><path d="M${55 + dx} ${30 + dy} L${69 + dx} ${68 + dy} L${83 + dx} ${30 + dy}" fill="none" stroke="${stroke}" stroke-width="12" ${R}/>`;
+ let depth = '';
+ for (let i = 6; i >= 1; i -= 1) depth += shapes(i * 0.9, i * 1.1, i > 3 ? '#1e1b4b' : '#312e81');
+ return { defs: g(id), body: `<g class="w-a">${depth}<g class="w-k">${shapes(0, 0, `url(#${id}g)`)}</g></g>` };
},
},
- // ── Places and worlds ──
- door(id) {
+ tag(id) { // a graffiti tag, still wet
return {
return {
- defs: lin(`${id}l`, [['0', '#fff7d6'], ['1', '#fbbf24']], 0, 0, 0, 1) + lin(`${id}g`, BV, 0, 0, 1, 1),
- body: `<path class="w-k" d="M34 88 V42 a20 20 0 0 1 40 0 V88Z" fill="url(#${id}l)"/><path d="M34 88 V42 a20 20 0 0 1 40 0 V88" fill="none" stroke="url(#${id}g)" stroke-width="6" stroke-linejoin="round"/>`
- + `<g class="w-a"><path d="M34 34 L16 24 V96 L34 88Z" fill="url(#${id}g)"/><circle cx="21" cy="62" r="2.6" fill="#fff"/></g><path class="w-b" d="M40 88 L74 88 L92 96 H50Z" fill="#fbbf24" opacity=".35"/>`,
+ defs: lin(`${id}g`, [['0', '#22d3ee'], ['.5', '#4f7cff'], ['1', '#a855f7']]),
+ body: `<ellipse class="w-k" cx="31" cy="48" rx="18" ry="22" transform="rotate(-14 31 48)" fill="none" stroke="url(#${id}g)" stroke-width="11"/>`
+ + `<path d="M52 24 Q60 50 67 74 Q76 46 91 21" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/>`
+ + `<g stroke="#a855f7" stroke-linecap="round"><path class="w-a" d="M28 70 V82" stroke-width="5"/><path class="w-a" style="--i:1" d="M67 76 V90" stroke-width="5"/><path class="w-a" style="--i:2" d="M42 64 V71" stroke-width="3.5"/></g>`
+ + `<path d="M20 36 Q24 30 30 29" fill="none" stroke="#fff" stroke-width="3" stroke-linecap="round" opacity=".7"/>`,
};
};
},
},
- planet(id) {
+ // ── Monograms ──
+ crossed(id) { // a V woven through an O
return {
return {
- defs: rad(`${id}p`, [['0', '#93c5fd'], ['.55', '#4f7cff'], ['1', '#6d28d9']]) + lin(`${id}r`, [['0', '#c4b5fd'], ['1', '#f0abfc']], 0, 0, 1, 0),
- body: `<g class="w-a"><g transform="rotate(-18 50 50)"><ellipse cx="50" cy="50" rx="42" ry="12" fill="none" stroke="url(#${id}r)" stroke-width="5" opacity=".5"/></g></g>`
- + `<circle class="w-k" cx="50" cy="50" r="25" fill="url(#${id}p)"/>`
- + `<g class="w-a"><g transform="rotate(-18 50 50)"><path d="M8 50 A42 12 0 0 0 92 50" fill="none" stroke="url(#${id}r)" stroke-width="5" stroke-linecap="round"/></g></g>`,
+ defs: '',
+ body: `<g class="w-a"><circle class="w-k" cx="50" cy="50" r="24" fill="none" stroke="#4f7cff" stroke-width="9"/>`
+ + `<path d="M24 18 L50 86 L76 18" fill="none" stroke="#8b5cf6" stroke-width="9" ${R}/>`
+ + `<path d="M58.2 72.6 A24 24 0 0 1 51.2 74" fill="none" stroke="#4f7cff" stroke-width="9"/>`
+ + `<path d="M31.6 34.6 A24 24 0 0 1 36 30.5" fill="none" stroke="#4f7cff" stroke-width="9"/></g>`,
};
};
},
},
- sunrise(id) {
+ chain(id) { // an O and a V linked like a chain
return {
return {
- defs: lin(`${id}s`, [['0', '#fde68a'], ['1', '#f97316']], 0, 0, 0, 1) + lin(`${id}m`, [['0', '#8b5cf6'], ['1', '#3b4cca']], 0, 0, 0, 1),
- body: `<circle class="w-a w-k" cx="50" cy="50" r="16" fill="url(#${id}s)"/><path d="M4 88 L27 30 L50 72 L73 30 L96 88Z" fill="url(#${id}m)"/><path d="M27 30 L35 50 L31 47 L27 52 L22 44Z" fill="#fff" opacity=".7"/><path d="M73 30 L81 50 L77 47 L73 52 L68 44Z" fill="#fff" opacity=".7"/>`,
+ defs: '',
+ body: `<g class="w-a"><circle class="w-k" cx="40" cy="54" r="19" fill="none" stroke="#4f7cff" stroke-width="8"/>`
+ + `<path d="M50 26 H88 L69 78Z" fill="none" stroke="#8b5cf6" stroke-width="8" stroke-linejoin="round"/>`
+ + `<path d="M55.4 39.4 A19 19 0 0 1 58.4 44.8" fill="none" stroke="#4f7cff" stroke-width="8"/></g>`,
};
};
},
},
- prism(id) {
- const rays = [['#ef4444', 24], ['#f59e0b', 36], ['#22c55e', 48], ['#22d3ee', 60], ['#8b5cf6', 72]];
+ coin(id) { // an OpenVibe coin with OV struck on it
return {
return {
- defs: lin(`${id}g`, [['0', '#4f7cff', 0.55], ['1', '#8b5cf6', 0.85]], 0, 0, 1, 1),
- body: `<path d="M4 44 H34" stroke="#fff" stroke-width="5" stroke-linecap="round"/>${rays.map(([c, y], i) => `<path class="w-a" style="--i:${i}" d="M64 48 L96 ${y}" stroke="${c}" stroke-width="5" stroke-linecap="round"/>`).join('')}`
- + `<path class="w-k" d="M20 20 H80 L50 84Z" fill="url(#${id}g)" stroke="#c4b5fd" stroke-width="3" stroke-linejoin="round"/>`,
+ defs: lin(`${id}c`, [['0', '#fef3c7'], ['.5', '#fbbf24'], ['1', '#d97706']]) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.55], ['1', '#fff', 0]], 0, 0, 1, 0)
+ + `<clipPath id="${id}k"><circle cx="50" cy="50" r="40"/></clipPath>`,
+ body: `<circle class="w-k" cx="50" cy="50" r="40" fill="url(#${id}c)"/><circle cx="50" cy="50" r="40" fill="none" stroke="#b45309" stroke-width="3" stroke-dasharray="2 2.2"/><circle cx="50" cy="50" r="32" fill="none" stroke="#fffbeb" stroke-width="1.6" opacity=".7"/>`
+ + `<circle cx="38" cy="50" r="9.5" fill="none" stroke="#92400e" stroke-width="5.5"/><path d="M53 40 L60.5 60 L68 40" fill="none" stroke="#92400e" stroke-width="5.5" ${R}/>`
+ + `<g clip-path="url(#${id}k)"><rect class="w-a" x="-40" y="0" width="26" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`,
};
};
},
},
- plane(id) {
+ tile(id) { // ov on an app tile, with a shine
return {
return {
- defs: lin(`${id}a`, [['0', '#a5b4fc'], ['1', '#6366f1']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#4c1d95']]),
- body: `<g class="w-a"><path class="w-k" d="M90 12 L8 44 L42 56Z" fill="url(#${id}a)"/><path d="M90 12 L42 56 L58 88Z" fill="url(#${id}b)"/><path d="M42 56 L40 74 L50 66Z" fill="#4c1d95"/></g>`
- + `<g class="w-b" stroke="#8b5cf6" stroke-width="4" stroke-linecap="round" opacity=".5"><path d="M10 70 H26"/><path d="M16 82 H34"/></g>`,
+ defs: g(id) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.45], ['1', '#fff', 0]], 0, 0, 1, 0) + `<clipPath id="${id}c"><rect x="10" y="10" width="80" height="80" rx="26"/></clipPath>`,
+ body: `<rect class="w-k" x="10" y="10" width="80" height="80" rx="26" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="30" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`
+ + `<circle cx="37" cy="55" r="11.5" fill="none" stroke="#fff" stroke-width="8.5"/><path d="M55 44 L65 67 L75 44" fill="none" stroke="#fff" stroke-width="8.5" ${R}/>`,
};
};
},
},
- // ── Friendly and alive ──
- blob(id) {
+ crest(id) { // an O over a V on a shield
return {
return {
- defs: lin(`${id}g`, [['0', '#22d3ee'], ['.5', '#4f7cff'], ['1', '#8b5cf6']]),
- body: `<g class="w-a"><path class="w-k" d="M54 10 C77 12 93 31 88 53 C84 75 66 92 45 88 C23 85 7 67 12 45 C16 25 32 8 54 10Z" fill="url(#${id}g)"/><ellipse cx="34" cy="28" rx="10" ry="6" fill="#fff" opacity=".28" transform="rotate(-30 34 28)"/>`
- + `<path d="M36 46 L50 64 L64 46" fill="none" stroke="#fff" stroke-width="8" ${R}/></g>`,
+ defs: g(id) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.4], ['1', '#fff', 0]], 0, 0, 1, 0) + `<clipPath id="${id}c"><path d="M50 7 L85 19 V47 C85 70 69 85 50 93 C31 85 15 70 15 47 V19Z"/></clipPath>`,
+ body: `<path class="w-k" d="M50 7 L85 19 V47 C85 70 69 85 50 93 C31 85 15 70 15 47 V19Z" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="24" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`
+ + `<circle cx="50" cy="35" r="9" fill="none" stroke="#fff" stroke-width="5.5"/><path d="M37 50 L50 75 L63 50" fill="none" stroke="#fff" stroke-width="7" ${R}/>`,
};
};
},
},
- smile(id) {
+ // ── An O standing on a V ──
+ pin(id) { // a map pin is an O on a V: you are here
return {
return {
- defs: rad(`${id}g`, [['0', '#93c5fd'], ['.6', '#4f7cff'], ['1', '#7c3aed']]),
- body: `<circle class="w-k" cx="50" cy="50" r="40" fill="url(#${id}g)"/><g class="w-a" fill="#fff"><circle cx="37" cy="42" r="5.5"/><circle cx="63" cy="42" r="5.5"/></g><path d="M33 58 Q50 76 67 58" fill="none" stroke="#fff" stroke-width="6.5" stroke-linecap="round"/>`,
+ defs: lin(`${id}g`, BV, 0, 0, 0, 1),
+ body: `<ellipse class="w-b" cx="50" cy="92" rx="13" ry="3.5" fill="#4f7cff" opacity=".3"/><g class="w-a"><circle class="w-k" cx="50" cy="33" r="17" fill="none" stroke="url(#${id}g)" stroke-width="10"/><path d="M36 45 L50 87 L64 45" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/></g>`,
};
};
},
},
- eye(id) {
+ cone(id) { // an O scoop on a V cone
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 1, 0) + rad(`${id}i`, [['0', '#a5f3fc'], ['.6', '#4f7cff'], ['1', '#6d28d9']]),
- body: `<g class="w-b"><path d="M6 50 Q50 10 94 50 Q50 90 6 50Z" fill="#4f7cff" opacity=".12"/><path d="M6 50 Q50 10 94 50 Q50 90 6 50Z" fill="none" stroke="url(#${id}g)" stroke-width="6" stroke-linejoin="round"/>`
- + `<g class="w-a"><circle class="w-k" cx="50" cy="50" r="17" fill="url(#${id}i)"/><circle cx="50" cy="50" r="7" fill="#0b1020"/><circle cx="55" cy="44" r="3.4" fill="#fff"/></g></g>`,
+ defs: lin(`${id}c`, [['0', '#fbbf24'], ['1', '#b45309']], 0, 0, 0, 1) + rad(`${id}s`, [['0', '#a5b4fc'], ['.55', '#6366f1'], ['1', '#7c3aed']]),
+ body: `<path d="M27 50 H73 L50 93Z" fill="url(#${id}c)"/><path d="M33 56 L58 80 M43 52 L64 72 M67 56 L42 80 M57 52 L36 72" stroke="#92400e" stroke-width="2" opacity=".55"/>`
+ + `<g class="w-a"><circle class="w-k" cx="50" cy="35" r="21" fill="url(#${id}s)"/><path d="M33 47 Q36 56 40 50 Q44 58 48 51 Q52 57 56 50 Q60 56 64 49" fill="none" stroke="#6366f1" stroke-width="4" stroke-linecap="round"/><circle cx="43" cy="27" r="4.5" fill="#fff" opacity=".45"/></g>`,
};
};
},
},
- spark(id) {
+ cheer(id) { // someone cheering: an O for a head, a V for arms up
return {
return {
- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.6', '#8b5cf6'], ['1', '#f472b6']]),
- body: `<path class="w-a w-k" d="${star4(44, 54, 36)}" fill="url(#${id}g)"/><path class="w-b" d="${star4(80, 20, 12)}" fill="#f472b6"/><path class="w-c" d="${star4(82, 76, 7)}" fill="#8b5cf6" opacity=".8"/>`,
+ defs: g(id),
+ body: `<g class="w-a"><path d="M18 18 L50 60 L82 18" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/></g><circle class="w-k" cx="50" cy="31" r="11" fill="url(#${id}g)"/>`
+ + `<path d="M50 60 V80 M50 80 L40 94 M50 80 L60 94" fill="none" stroke="url(#${id}g)" stroke-width="9" ${R}/>`,
};
};
},
},
- overlap() {
+ mic(id) { // a microphone: the O head held in a V
return {
return {
- defs: '',
- body: `<circle class="w-a" cx="37" cy="50" r="27" fill="#4f7cff" opacity=".9"/><circle class="w-b" cx="63" cy="50" r="27" fill="#8b5cf6" opacity=".9"/>`
- + `<path class="w-k" d="M50 26.4 A27 27 0 0 1 50 73.6 A27 27 0 0 1 50 26.4Z" fill="#e0e7ff"/>`,
+ defs: g(id) + `<clipPath id="${id}c"><circle cx="50" cy="32" r="16"/></clipPath>`,
+ body: `<circle class="w-k" cx="50" cy="32" r="16" fill="url(#${id}g)"/><g clip-path="url(#${id}c)" stroke="#fff" stroke-width="2" opacity=".35"><path d="M30 26 H70 M30 32 H70 M30 38 H70"/></g>`
+ + `<path d="M31 30 L50 62 L69 30" fill="none" stroke="#c4b5fd" stroke-width="5" ${R}/><path d="M50 62 V84 M38 86 H62" fill="none" stroke="#8b5cf6" stroke-width="5" ${R}/>`
+ + `<g class="w-b" fill="none" stroke="#8b5cf6" stroke-width="3.5" stroke-linecap="round"><path d="M20 22 q-6 10 0 20"/><path d="M80 22 q6 10 0 20"/></g>`,
};
};
},
},
- // ── Made of parts ──
- stars(id) {
+ balloon(id) { // an O balloon on a V knot
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 1, 1),
- body: `<path d="M18 22 L34 50 L50 78 L66 50 L82 22" fill="none" stroke="url(#${id}g)" stroke-width="3.5" stroke-linejoin="round" opacity=".7"/>`
- + `<g fill="url(#${id}g)"><circle class="w-a" style="--i:0" cx="18" cy="22" r="8"/><circle class="w-a" style="--i:2" cx="82" cy="22" r="8"/><circle class="w-a w-k" style="--i:4" cx="50" cy="78" r="9"/><circle class="w-a" style="--i:1" cx="34" cy="50" r="5.5"/><circle class="w-a" style="--i:3" cx="66" cy="50" r="5.5"/></g><circle class="w-b" cx="50" cy="30" r="3" fill="#c4b5fd"/>`,
+ defs: rad(`${id}g`, [['0', '#c7d2fe'], ['.5', '#4f7cff'], ['1', '#7c3aed']]),
+ body: `<g class="w-a"><ellipse class="w-k" cx="50" cy="38" rx="23" ry="27" fill="url(#${id}g)"/><path d="M44 63 L50 72 L56 63" fill="#7c3aed" stroke="#7c3aed" stroke-width="3" stroke-linejoin="round"/>`
+ + `<ellipse cx="41" cy="27" rx="5" ry="8" fill="#fff" opacity=".35" transform="rotate(-25 41 27)"/><path d="M50 72 C46 80 54 84 50 92" fill="none" stroke="#a5b4fc" stroke-width="2.5" stroke-linecap="round"/></g>`,
};
};
},
},
- pixel() {
- const rows = [[0, 1, 7, 8], [1, 2, 6, 7], [2, 3, 5, 6], [3, 4, 5], [4]];
- const cols = ['#4f7cff', '#5b74fb', '#6a6cf8', '#7a64f7', '#8b5cf6'];
- let k = 0;
- const px = rows.map((cs, r) => cs.map((c) => `<rect class="w-a${r === 4 ? ' w-k' : ''}" style="--i:${k++}" x="${(9 + c * 9.2).toFixed(1)}" y="${18 + r * 13}" width="8.2" height="11" rx="1.6" fill="${cols[r]}"/>`).join('')).join('');
- return { defs: '', body: px };
- },
- ribbon(id) {
+ // ── An O and a V in a scene ──
+ bird(id) { // a V bird gliding past an O sun
return {
return {
- defs: lin(`${id}a`, [['0', '#6366f1'], ['1', '#4c1d95']], 0, 0, 0, 1) + lin(`${id}b`, [['0', '#93c5fd'], ['1', '#4f7cff']], 0, 0, 0, 1),
- body: `<g class="w-a"><path d="M86 16 H66 L44 78 L54 90Z" fill="url(#${id}a)"/><path class="w-k" d="M14 16 H34 L58 80 L46 90Z" fill="url(#${id}b)"/><path d="M44 78 L46 90 L54 90Z" fill="#3b0764" opacity=".6"/></g>`,
+ defs: lin(`${id}s`, WARM, 0, 0, 0, 1) + g(id),
+ body: `<circle class="w-k" cx="40" cy="46" r="25" fill="url(#${id}s)"/><g class="w-a"><path d="M46 56 Q57 42 67 50 Q74 38 90 34" fill="none" stroke="url(#${id}g)" stroke-width="7" ${R}/></g>`
+ + `<path d="M18 80 H62" stroke="#8b5cf6" stroke-width="3" stroke-linecap="round" opacity=".4"/>`,
};
};
},
},
- code(id) {
+ valley(id) { // an O sun rising in a V valley
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 1, 0),
- body: `<g fill="none" stroke="#94a3b8" stroke-width="7" ${R}><path class="w-b" d="M28 28 L10 50 L28 72"/><path class="w-c" d="M72 28 L90 50 L72 72"/></g><path class="w-a w-k" d="M37 32 L50 68 L63 32" fill="none" stroke="url(#${id}g)" stroke-width="9" ${R}/>`,
+ defs: lin(`${id}s`, WARM, 0, 0, 0, 1) + lin(`${id}m`, [['0', '#8b5cf6'], ['1', '#3b4cca']], 0, 0, 0, 1),
+ body: `<circle class="w-a w-k" cx="50" cy="48" r="16" fill="url(#${id}s)"/><path d="M4 90 L28 30 L50 72 L72 30 L96 90Z" fill="url(#${id}m)"/><path d="M28 30 L35 47 L31 45 L28 50 L23 43Z M72 30 L79 47 L75 45 L72 50 L67 43Z" fill="#fff" opacity=".7"/>`,
};
};
},
},
- badge(id) {
+ cocktail(id) { // a V glass with an O olive
return {
return {
- defs: lin(`${id}g`, BV) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.45], ['1', '#fff', 0]], 0, 0, 1, 0),
- body: `<clipPath id="${id}c"><rect x="10" y="10" width="80" height="80" rx="26"/></clipPath><rect class="w-k" x="10" y="10" width="80" height="80" rx="26" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="30" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`
- + `<circle cx="37" cy="55" r="11.5" fill="none" stroke="#fff" stroke-width="8.5"/><path d="M55 44 L65 67 L75 44" fill="none" stroke="#fff" stroke-width="8.5" ${R}/>`,
+ defs: lin(`${id}g`, [['0', '#4f7cff', 0.55], ['1', '#8b5cf6', 0.9]], 0, 0, 0, 1),
+ body: `<path class="w-k" d="M16 22 H84 L50 60Z" fill="url(#${id}g)" stroke="#c4b5fd" stroke-width="3" stroke-linejoin="round"/><path d="M50 60 V84 M34 86 H66" fill="none" stroke="#c4b5fd" stroke-width="4" ${R}/>`
+ + `<g class="w-a"><path d="M74 8 L54 34" stroke="#fde68a" stroke-width="2.5" stroke-linecap="round"/><circle cx="59" cy="28" r="8" fill="#84cc16"/><circle cx="60.5" cy="26.5" r="3" fill="#ef4444"/></g>`,
};
};
},
},
- // ── Nature ──
- wave(id) {
+ catch(id) { // an O ball dropping into a V cup
return {
return {
- defs: lin(`${id}g`, [['0', '#22d3ee'], ['1', '#4f7cff']], 0, 0, 1, 1) + lin(`${id}d`, [['0', '#6366f1'], ['1', '#4c1d95']], 0, 0, 0, 1),
- body: `<g class="w-a"><path class="w-k" d="M8 70 C20 70 26 40 46 30 C62 22 80 30 82 46 C70 38 58 44 58 56 C58 66 68 70 92 70 V86 H8Z" fill="url(#${id}g)"/><path d="M8 80 C30 76 60 84 92 78 V90 H8Z" fill="url(#${id}d)"/></g><circle class="w-b" cx="80" cy="24" r="3" fill="#a5f3fc"/><circle class="w-b" style="--i:1" cx="88" cy="34" r="2" fill="#a5f3fc"/>`,
+ defs: g(id) + rad(`${id}b`, [['0', '#fde68a'], ['.6', '#f59e0b'], ['1', '#ea580c']]),
+ body: `<path class="w-k" d="M18 44 L50 88 L82 44" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/><g class="w-a"><circle cx="50" cy="28" r="12" fill="url(#${id}b)"/></g>`,
};
};
},
},
- flame(id) {
+ rocket(id) { // a rocket: an O window, a V flame
return {
return {
- defs: lin(`${id}g`, [['0', '#fbbf24'], ['.45', '#f97316'], ['1', '#8b5cf6']], 0, 0, 0, 1),
- body: `<g class="w-a"><path class="w-k" d="M50 8 C58 26 76 34 76 58 C76 76 64 90 50 90 C36 90 24 76 24 58 C24 44 32 36 38 28 C40 40 44 44 48 46 C46 32 46 20 50 8Z" fill="url(#${id}g)"/>`
- + `<path d="M40 58 L50 76 L60 58" fill="none" stroke="#fff" stroke-width="6" ${R} opacity=".9"/></g>`,
+ defs: lin(`${id}g`, [['0', '#e0e7ff'], ['1', '#8b5cf6']], 0, 0, 1, 0) + lin(`${id}f`, [['0', '#fde68a'], ['1', '#f97316']], 0, 0, 0, 1),
+ body: `<g class="w-a"><path d="M50 8 C65 20 69 44 64 64 H36 C31 44 35 20 50 8Z" fill="url(#${id}g)"/><path d="M36 50 L24 70 L37 66Z M64 50 L76 70 L63 66Z" fill="#6366f1"/>`
+ + `<circle class="w-k" cx="50" cy="36" r="8.5" fill="#0b1020" stroke="#4f7cff" stroke-width="3.5"/></g><path class="w-b" d="M41 66 L50 92 L59 66Z" fill="url(#${id}f)"/>`,
};
};
},
},
- leaf(id) {
+ // ── Signals ──
+ bubble(id) { // an O speech bubble with a V tail, typing
return {
return {
- defs: lin(`${id}g`, [['0', '#34d399'], ['1', '#4f7cff']], 0, 0, 1, 1),
- body: `<g class="w-a"><path class="w-k" d="M50 90 C22 76 14 46 24 18 C42 24 82 30 82 58 C82 76 66 90 50 90Z" fill="url(#${id}g)"/><path d="M50 90 L50 70 M50 70 L36 48 M50 70 L64 50" fill="none" stroke="#fff" stroke-width="4.5" ${R} opacity=".85"/></g>`,
+ defs: g(id),
+ body: `<circle class="w-k" cx="52" cy="44" r="30" fill="url(#${id}g)"/><path d="M30 64 L18 88 L44 72Z" fill="#6d5ff7"/>`
+ + `<g fill="#fff"><circle class="w-a" style="--i:0" cx="40" cy="44" r="4.5"/><circle class="w-a" style="--i:1" cx="52" cy="44" r="4.5"/><circle class="w-a" style="--i:2" cx="64" cy="44" r="4.5"/></g>`,
};
};
},
},
- moon(id) {
+ play(id) { // an O and a V turned on its side: record and play
return {
return {
- defs: lin(`${id}g`, [['0', '#c4b5fd'], ['1', '#4f7cff']], 0, 0, 1, 1),
- body: `<path class="w-k" d="M58 12 A38 38 0 1 0 88 66 A30 30 0 1 1 58 12Z" fill="url(#${id}g)"/><path class="w-a" d="${star4(70, 30, 9)}" fill="#fde68a"/><circle class="w-b" cx="84" cy="44" r="2.5" fill="#fde68a"/>`,
+ defs: g(id),
+ body: `<circle class="w-k" cx="29" cy="50" r="20" fill="url(#${id}g)"/><circle cx="29" cy="50" r="5" fill="#0b1020"/><g class="w-a"><path d="M58 28 L90 50 L58 72Z" fill="#8b5cf6" stroke="#8b5cf6" stroke-width="7" stroke-linejoin="round"/></g>`,
};
};
},
},
- bolt(id) {
- return {
- defs: lin(`${id}g`, [['0', '#fde68a'], ['.5', '#f59e0b'], ['1', '#8b5cf6']], 0, 0, 1, 1),
- body: `<path class="w-a w-k" d="M58 6 L22 54 H46 L38 94 L78 40 H54 Z" fill="url(#${id}g)" stroke="#fff" stroke-opacity=".25" stroke-width="2" stroke-linejoin="round"/>`,
- };
+ signal(id) { // V waves arriving at an O
+ const v = (y, i, o) => `<path class="w-a" style="--i:${i}" d="M28 ${y} L50 ${y + 16} L72 ${y}" fill="none" stroke="url(#${id}g)" stroke-width="7" ${R} opacity="${o}"/>`;
+ return { defs: g(id), body: `${v(12, 0, 0.35)}${v(30, 1, 0.65)}${v(48, 2, 1)}<circle class="w-k" cx="50" cy="82" r="9" fill="none" stroke="url(#${id}g)" stroke-width="6"/>` };
},
},
- // ── Things you hold ──
- headphones(id) {
+ beat(id) { // a heartbeat that writes an o and a v
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 1, 0),
- body: `<path d="M18 60 V50 a32 32 0 0 1 64 0 V60" fill="none" stroke="url(#${id}g)" stroke-width="8" stroke-linecap="round"/><rect class="w-k" x="12" y="54" width="18" height="30" rx="8" fill="url(#${id}g)"/><rect x="70" y="54" width="18" height="30" rx="8" fill="url(#${id}g)"/>`
- + `<g class="w-a" fill="#8b5cf6"><rect style="--i:0" x="39" y="58" width="5" height="18" rx="2.5"/><rect style="--i:1" x="47.5" y="52" width="5" height="30" rx="2.5"/><rect style="--i:2" x="56" y="58" width="5" height="18" rx="2.5"/></g>`,
+ defs: lin(`${id}g`, [['0', '#4f7cff'], ['.6', '#8b5cf6'], ['1', '#ec4899']], 0, 0, 1, 0),
+ body: `<path class="w-a w-k" pathLength="100" d="M5 56 H20 C20 43 37 43 37 55 C37 66 22 66 22 55 C22 49 32 48 44 55 L54 77 L64 28 L72 56 H95" fill="none" stroke="url(#${id}g)" stroke-width="7" ${R}/>`,
};
};
},
},
- play(id) {
+ eqring(id) { // an O with an equaliser inside, its bars in a V
+ const xs = [29, 36, 43, 50, 57, 64, 71];
+ const bars = xs.map((x, i) => { const h = 8 + 28 * (Math.abs(x - 50) / 21); return `<rect class="w-a" style="--i:${i}" x="${x - 2.6}" y="${(70 - h).toFixed(1)}" width="5.2" height="${h.toFixed(1)}" rx="2.6"/>`; }).join('');
+ return { defs: g(id), body: `<circle class="w-k" cx="50" cy="50" r="38" fill="none" stroke="url(#${id}g)" stroke-width="6"/><g fill="url(#${id}g)">${bars}</g>` };
+ },
+ // ── Things that are an O and a V ──
+ bulb(id) { // a bulb with a V filament: ideas
return {
return {
- defs: lin(`${id}g`, BV),
- body: `<circle class="w-k" cx="50" cy="50" r="40" fill="url(#${id}g)"/><circle class="w-b" cx="50" cy="50" r="40" fill="none" stroke="#8b5cf6" stroke-width="3"/><path class="w-a" d="M42 32 Q40 30 40 34 V66 Q40 70 43 68 L68 52 Q71 50 68 48Z" fill="#fff"/>`,
+ defs: rad(`${id}l`, [['0', '#fef3c7'], ['.5', '#fde68a', 0.6], ['1', '#fde68a', 0]]) + g(id),
+ body: `<circle class="w-a" cx="50" cy="42" r="34" fill="url(#${id}l)"/><path class="w-k" d="M50 12 C68 12 80 26 80 42 C80 54 72 60 68 68 H32 C28 60 20 54 20 42 C20 26 32 12 50 12Z" fill="#fff" fill-opacity=".08" stroke="url(#${id}g)" stroke-width="5"/>`
+ + `<path d="M40 38 L50 58 L60 38" fill="none" stroke="#f59e0b" stroke-width="5" ${R}/><path d="M34 74 H66 M36 81 H64 M42 88 H58" stroke="#8b5cf6" stroke-width="4.5" stroke-linecap="round"/>`,
};
};
},
},
- antenna(id) {
+ clock(id) { // an O clock at ten past ten: the hands make a V
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 0, 1),
- body: `<path d="M50 40 L34 90 M50 40 L66 90 M40 72 H60" fill="none" stroke="url(#${id}g)" stroke-width="6" ${R}/><circle class="w-k" cx="50" cy="36" r="7" fill="url(#${id}g)"/>`
- + `<g class="w-a" fill="none" stroke="#8b5cf6" stroke-width="5" stroke-linecap="round"><path style="--i:0" d="M36 22 a20 20 0 0 0 0 28"/><path style="--i:0" d="M64 22 a20 20 0 0 1 0 28"/><path style="--i:1" d="M24 12 a34 34 0 0 0 0 48" opacity=".6"/><path style="--i:1" d="M76 12 a34 34 0 0 1 0 48" opacity=".6"/></g>`,
+ defs: g(id),
+ body: `<circle class="w-k" cx="50" cy="50" r="38" fill="none" stroke="url(#${id}g)" stroke-width="7"/><path d="M50 16 V22 M84 50 H78 M50 84 V78 M16 50 H22" stroke="#8b5cf6" stroke-width="4" stroke-linecap="round"/>`
+ + `<g class="w-a"><path d="M28 36 L50 50 L75 34" fill="none" stroke="#fff" stroke-width="6" ${R}/></g><circle cx="50" cy="50" r="4.5" fill="#fff"/>`,
};
};
},
},
- gem(id) {
+ target(id) { // an O target and an arrow with a V tail
return {
return {
- defs: lin(`${id}a`, [['0', '#a5b4fc'], ['1', '#4f7cff']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#4c1d95']]),
- body: `<path d="M26 22 H74 L90 40 L50 88 L10 40Z" fill="url(#${id}b)"/><path class="w-k" d="M26 22 L38 40 H62 L74 22Z" fill="url(#${id}a)"/><path d="M10 40 H90 M38 40 L50 88 L62 40" fill="none" stroke="#fff" stroke-width="2.5" stroke-linejoin="round" opacity=".55"/>`
- + `<path class="w-a" d="${star4(70, 30, 8)}" fill="#fff"/>`,
+ defs: '',
+ body: `<circle class="w-k" cx="42" cy="58" r="32" fill="#4f7cff"/><circle cx="42" cy="58" r="22" fill="#0b1020"/><circle cx="42" cy="58" r="12" fill="#8b5cf6"/>`
+ + `<g class="w-a"><path d="M42 58 L84 16" stroke="#fde68a" stroke-width="4" stroke-linecap="round"/><path d="M78 12 L86 14 L88 22 M74 16 L82 18 L84 26" fill="none" stroke="#f472b6" stroke-width="3.5" ${R}/></g>`,
};
};
},
},
- dice(id) {
- const pip = (x, y, i) => `<circle class="w-b" style="--i:${i}" cx="${x}" cy="${y}" r="6.5" fill="#fff"/>`;
+ vinyl(id) { // an O record with a V on its label
return {
return {
- defs: lin(`${id}g`, BV),
- body: `<g class="w-a"><rect class="w-k" x="14" y="14" width="72" height="72" rx="18" fill="url(#${id}g)"/>${pip(32, 32, 0)}${pip(68, 32, 1)}${pip(41, 50, 2)}${pip(59, 50, 3)}${pip(50, 68, 4)}</g>`,
+ defs: g(id),
+ body: `<circle class="w-k" cx="50" cy="50" r="40" fill="#111827"/><g class="w-a" fill="none" stroke="#374151" stroke-width="1.6"><circle cx="50" cy="50" r="34"/><circle cx="50" cy="50" r="28"/><circle cx="50" cy="50" r="22"/></g>`
+ + `<path d="M22 36 A32 32 0 0 1 40 20" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" opacity=".35"/><circle cx="50" cy="50" r="15" fill="url(#${id}g)"/><path d="M44 45 L50 58 L56 45" fill="none" stroke="#fff" stroke-width="3.5" ${R}/>`,
};
};
},
},
- // ── Signs and symbols ──
- pin(id) {
+ orbit(id) { // a V with an O orbit round it
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 0, 1),
- body: `<ellipse class="w-b" cx="50" cy="91" rx="14" ry="3.5" fill="#4f7cff" opacity=".3"/><g class="w-a"><path class="w-k" d="M50 88 C40 72 20 58 20 38 a30 30 0 0 1 60 0 C80 58 60 72 50 88Z" fill="url(#${id}g)"/><circle cx="50" cy="38" r="11" fill="#fff"/></g>`,
+ defs: g(id),
+ body: `<g class="w-a"><g transform="rotate(-16 50 54)"><path d="M10 54 A40 14 0 0 1 90 54" fill="none" stroke="#c4b5fd" stroke-width="4" opacity=".55"/></g></g>`
+ + `<path class="w-k" d="M30 26 L50 78 L70 26" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/>`
+ + `<g class="w-a"><g transform="rotate(-16 50 54)"><path d="M90 54 A40 14 0 0 1 10 54" fill="none" stroke="#c4b5fd" stroke-width="4"/><circle cx="84" cy="62" r="5" fill="#f472b6"/></g></g>`,
};
};
},
},
- heart(id) {
+ // ── Built from parts ──
+ pixel8(id) { // O and V in big pixels
+ const O = ['.###.', '#...#', '#...#', '#...#', '#...#', '.###.'];
+ const V = ['#...#', '#...#', '#...#', '.#.#.', '.#.#.', '..#..'];
+ const cols = ['#4f7cff', '#5975fb', '#646ef9', '#6f67f8', '#7b61f7', '#8b5cf6'];
+ let k = 0;
+ const draw = (rows, ox) => rows.map((row, r) => [...row].map((c, ci) => (c === '#' ? `<rect class="w-a${ox > 50 && r === 5 ? ' w-k' : ''}" style="--i:${k++}" x="${ox + ci * 8}" y="${27 + r * 8}" width="7" height="7" rx="1.2" fill="${cols[r]}"/>` : '')).join('')).join('');
+ return { defs: '', body: draw(O, 7) + draw(V, 53) };
+ },
+ stars2(id) { // OV in stars, joined
+ const ring = Array.from({ length: 6 }, (_, i) => { const a = (i / 6) * Math.PI * 2 - Math.PI / 2; return [30 + 18 * Math.cos(a), 50 + 18 * Math.sin(a)]; });
+ const vee = [[58, 30], [71, 72], [84, 30]];
+ const pts = ring.concat(vee);
return {
return {
- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.6', '#8b5cf6'], ['1', '#ec4899']]),
- body: `<path class="w-a w-k" d="M50 86 L18 52 C6 38 12 16 30 16 C40 16 46 22 50 30 C54 22 60 16 70 16 C88 16 94 38 82 52Z" fill="url(#${id}g)"/>`,
+ defs: g(id),
+ body: `<path d="M${ring.map((p) => p.map((n) => n.toFixed(1)).join(' ')).join(' L')}Z M58 30 L71 72 L84 30" fill="none" stroke="url(#${id}g)" stroke-width="2.2" opacity=".6"/>`
+ + pts.map(([x, y], i) => `<circle class="w-a${i === 7 ? ' w-k' : ''}" style="--i:${i}" cx="${x.toFixed(1)}" cy="${y.toFixed(1)}" r="${i === 7 ? 5.5 : 4}" fill="#e0e7ff"/>`).join(''),
};
};
},
},
- shield(id) {
+ halftone(id) { // OV printed in halftone dots
+ const dO = (x, y) => Math.abs(Math.hypot(x - 30, y - 50) - 18);
+ const seg = (px, py, ax, ay, bx, by) => { const dx = bx - ax, dy = by - ay; const t = Math.max(0, Math.min(1, ((px - ax) * dx + (py - ay) * dy) / (dx * dx + dy * dy))); return Math.hypot(px - ax - t * dx, py - ay - t * dy); };
+ const dV = (x, y) => Math.min(seg(x, y, 57, 28, 71, 72), seg(x, y, 71, 72, 85, 28));
+ let out = '';
+ let i = 0;
+ for (let y = 20; y <= 80; y += 5) {
+ for (let x = 8; x <= 92; x += 5) {
+ const d = Math.min(dO(x, y), dV(x, y));
+ const r = d < 6.8 ? 2.4 : 0;
+ if (r) out += `<circle class="w-a" style="--i:${Math.round(x / 10)}" cx="${x}" cy="${y}" r="${r}"/>`;
+ i += 1;
+ }
+ }
+ return { defs: g(id), body: `<g class="w-k" fill="url(#${id}g)">${out}</g>` };
+ },
+ tiles(id) { // an O tile and a V tile
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 0, 1) + lin(`${id}s`, [['0', '#fff', 0], ['.5', '#fff', 0.4], ['1', '#fff', 0]], 0, 0, 1, 0),
- body: `<clipPath id="${id}c"><path d="M50 8 L84 20 V48 C84 70 68 84 50 92 C32 84 16 70 16 48 V20Z"/></clipPath><path class="w-k" d="M50 8 L84 20 V48 C84 70 68 84 50 92 C32 84 16 70 16 48 V20Z" fill="url(#${id}g)"/><g clip-path="url(#${id}c)"><rect class="w-a" x="-40" y="0" width="26" height="100" fill="url(#${id}s)" transform="skewX(-20)"/></g>`
- + `<path d="M36 36 L50 64 L64 36" fill="none" stroke="#fff" stroke-width="8" ${R}/>`,
+ defs: g(id),
+ body: `<g class="w-a" style="--i:0"><rect class="w-k" x="8" y="28" width="40" height="40" rx="9" fill="#4f7cff" transform="rotate(-6 28 48)"/><circle cx="28" cy="48" r="9.5" fill="none" stroke="#fff" stroke-width="6" transform="rotate(-6 28 48)"/></g>`
+ + `<g class="w-a" style="--i:1"><rect x="52" y="32" width="40" height="40" rx="9" fill="#8b5cf6" transform="rotate(7 72 52)"/><path d="M63 42 L72 62 L81 42" fill="none" stroke="#fff" stroke-width="6" ${R} transform="rotate(7 72 52)"/></g>`,
};
};
},
},
- chevrons(id) {
- const v = (y, i, o) => `<path class="w-a${i === 2 ? ' w-k' : ''}" style="--i:${i}" d="M22 ${y} L50 ${y + 22} L78 ${y}" fill="none" stroke="url(#${id}g)" stroke-width="9" ${R} opacity="${o}"/>`;
- return { defs: lin(`${id}g`, BV, 0, 0, 1, 0), body: `${v(14, 0, 0.35)}${v(36, 1, 0.65)}${v(58, 2, 1)}` };
- },
- infinity(id) {
+ patch(id) { // an embroidered OV patch
return {
return {
- defs: lin(`${id}g`, [['0', '#4f7cff'], ['.5', '#8b5cf6'], ['1', '#4f7cff']], 0, 0, 1, 0),
- body: `<path class="w-k" d="M50 50 C40 34 14 30 14 50 C14 70 40 66 50 50 C60 34 86 30 86 50 C86 70 60 66 50 50Z" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}/>`
- + `<path class="w-a" pathLength="100" d="M50 50 C40 34 14 30 14 50 C14 70 40 66 50 50 C60 34 86 30 86 50 C86 70 60 66 50 50Z" fill="none" stroke="#fff" stroke-width="4" stroke-linecap="round" stroke-dasharray="8 92" opacity=".85"/>`,
+ defs: '',
+ body: `<g class="w-a"><circle class="w-k" cx="50" cy="50" r="41" fill="#3b2a8f"/><circle cx="50" cy="50" r="41" fill="none" stroke="#8b5cf6" stroke-width="4"/><circle cx="50" cy="50" r="35" fill="none" stroke="#e0e7ff" stroke-width="1.8" stroke-dasharray="3 3"/>`
+ + `<circle cx="36" cy="50" r="11" fill="none" stroke="#93c5fd" stroke-width="7"/><path d="M53 39 L61 62 L69 39" fill="none" stroke="#c4b5fd" stroke-width="7" ${R}/>`
+ + `<g fill="none" stroke="#fff" stroke-width=".9" opacity=".35"><circle cx="36" cy="50" r="11" stroke-dasharray="1.5 1.5"/><path d="M53 39 L61 62 L69 39" stroke-dasharray="1.5 1.5"/></g></g>`,
};
};
},
},
- // ── Play and motion ──
- kite(id) {
+ // ── A feel ──
+ liquid(id) { // O and V as one drop of liquid
return {
return {
- defs: lin(`${id}a`, [['0', '#93c5fd'], ['1', '#4f7cff']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#6d28d9']]),
- body: `<g class="w-a"><path class="w-k" d="M50 8 L74 36 L50 64 Z" fill="url(#${id}a)"/><path d="M50 8 L26 36 L50 64Z" fill="url(#${id}b)"/><path d="M26 36 H74" stroke="#fff" stroke-width="2" opacity=".5"/>`
- + `<path class="w-b" d="M50 64 C46 72 56 76 50 84 C46 88 52 92 48 96" fill="none" stroke="#8b5cf6" stroke-width="3" stroke-linecap="round"/><path d="M47 76 l-6 -3 m6 3 l-4 5 M51 88 l6 -2 m-6 2 l4 5" stroke="#f472b6" stroke-width="3" stroke-linecap="round"/></g>`,
+ defs: lin(`${id}g`, [['0', '#22d3ee'], ['.5', '#4f7cff'], ['1', '#8b5cf6']]),
+ body: `<g class="w-a"><g class="w-k" fill="none" stroke="url(#${id}g)" stroke-width="13" ${R}><circle cx="31" cy="50" r="16"/><path d="M58 32 L71 70 L84 32"/></g>`
+ + `<path d="M44 46 C50 44 54 44 59 41 L62 50 C56 52 51 54 45 55Z" fill="url(#${id}g)"/><path d="M71 76 C68 82 74 86 71 92" fill="none" stroke="#8b5cf6" stroke-width="5" stroke-linecap="round"/><ellipse cx="24" cy="42" rx="3.5" ry="2" fill="#fff" opacity=".5" transform="rotate(-35 24 42)"/></g>`,
};
};
},
},
- bird(id) {
+ glass(id) { // frosted glass: an O pane over a V
return {
return {
- defs: lin(`${id}a`, [['0', '#a5b4fc'], ['1', '#4f7cff']]) + lin(`${id}b`, [['0', '#8b5cf6'], ['1', '#4c1d95']]),
- body: `<path class="w-a" d="M44 52 L18 22 L58 46Z" fill="url(#${id}a)"/><path class="w-k" d="M14 58 L58 46 L88 30 L74 56 L48 72Z" fill="url(#${id}b)"/><path class="w-b" d="M58 46 L82 18 L64 54Z" fill="url(#${id}a)" opacity=".85"/><path d="M88 30 L96 30 L86 36Z" fill="#f59e0b"/>`,
+ defs: g(id),
+ body: `<path class="w-k" d="M48 22 H64 L72 52 L80 22 H96 L80 80 H64Z" fill="url(#${id}g)"/>`
+ + `<g class="w-a"><circle cx="38" cy="50" r="27" fill="#fff" fill-opacity=".14" stroke="#fff" stroke-opacity=".55" stroke-width="2"/><circle cx="38" cy="50" r="12" fill="#0b1020" fill-opacity=".55"/><path d="M20 38 A20 20 0 0 1 32 26" fill="none" stroke="#fff" stroke-width="2.5" stroke-linecap="round" opacity=".7"/></g>`,
};
};
},
},
- bounce(id) {
- const dots = [[16, 20], [27, 40], [38, 62], [50, 80], [62, 62], [73, 40], [84, 20]];
+ ribbon2(id) { // a ribbon that loops an O and folds a V
return {
return {
- defs: lin(`${id}g`, BV, 0, 0, 1, 0),
- body: `<path d="M16 20 Q34 70 50 82 Q66 70 84 20" fill="none" stroke="url(#${id}g)" stroke-width="2.5" stroke-dasharray="2 6" stroke-linecap="round" opacity=".55"/>${dots.map(([x, y], i) => `<circle class="w-a" style="--i:${i}" cx="${x}" cy="${y}" r="${i === 3 ? 10 : 5}" fill="url(#${id}g)"${i === 3 ? '' : ' opacity=".55"'}/>`).join('')}<ellipse class="w-k" cx="50" cy="93" rx="12" ry="3" fill="#4f7cff" opacity=".35"/>`,
+ defs: lin(`${id}g`, [['0', '#4f7cff'], ['1', '#a855f7']], 0, 0, 1, 0),
+ body: `<path class="w-a w-k" pathLength="100" d="M8 70 C8 42 42 32 42 54 C42 72 14 72 18 52 C22 36 46 32 56 38 L68 74 L90 24" fill="none" stroke="url(#${id}g)" stroke-width="11" ${R}/>`
+ + `<path d="M8 70 C8 42 42 32 42 54 C42 72 14 72 18 52 C22 36 46 32 56 38 L68 74 L90 24" fill="none" stroke="#fff" stroke-width="2" ${R} opacity=".35"/>`,
};
};
},
},
- orbit(id) {
+ sticker(id) { // a die-cut OV sticker, a corner peeling
return {
return {
- defs: lin(`${id}g`, BV),
- body: `<circle cx="50" cy="50" r="30" fill="none" stroke="url(#${id}g)" stroke-width="10"/><circle cx="50" cy="50" r="42" fill="none" stroke="#8b5cf6" stroke-width="2" stroke-dasharray="3 6" opacity=".55"/>`
- + `<g class="w-a"><circle class="w-k" cx="50" cy="8" r="7" fill="#f472b6"/></g>`,
+ defs: g(id),
+ body: `<g class="w-a" transform="rotate(-8 50 50)"><g fill="none" stroke="#000" stroke-opacity=".35" stroke-width="22" ${R} transform="translate(2 3)"><circle cx="31" cy="50" r="15"/><path d="M57 32 L70 70 L83 32"/></g>`
+ + `<g fill="none" stroke="#fff" stroke-width="22" ${R}><circle cx="31" cy="50" r="15"/><path d="M57 32 L70 70 L83 32"/></g>`
+ + `<g class="w-k" fill="none" stroke="url(#${id}g)" stroke-width="10" ${R}><circle cx="31" cy="50" r="15"/><path d="M57 32 L70 70 L83 32"/></g>`
+ + `<path d="M80 72 L90 62 L86 74Z" fill="#e5e7eb"/></g>`,
};
};
},
},
- cursor(id) {
+ mascot(id) { // a round O face with a V grin
return {
return {
- defs: lin(`${id}g`, BV),
- body: `<circle class="w-b" cx="30" cy="28" r="14" fill="none" stroke="#8b5cf6" stroke-width="3"/><g class="w-a"><path class="w-k" d="M30 28 L78 52 L56 58 L68 82 L58 86 L46 62 L30 76Z" fill="url(#${id}g)" stroke="#fff" stroke-width="3" stroke-linejoin="round"/></g><path d="${star4(80, 22, 9)}" fill="#f472b6"/>`,
+ defs: rad(`${id}g`, [['0', '#93c5fd'], ['.6', '#4f7cff'], ['1', '#7c3aed']]),
+ body: `<circle class="w-k" cx="50" cy="52" r="38" fill="url(#${id}g)"/><g class="w-a" fill="#fff"><circle cx="37" cy="42" r="5.5"/><circle cx="63" cy="42" r="5.5"/></g>`
+ + `<path d="M36 56 L50 76 L64 56Z" fill="#0b1020" stroke="#0b1020" stroke-width="4" stroke-linejoin="round"/><path d="M44 69 Q50 64 56 69 L50 76Z" fill="#f472b6"/>`,
};
};
},
},
};
};
-/** [name, what the idea is, key, lockup] in eight rows of five. */
+/** [name, what the idea is, key, lockup, motion] in eight rows of five. */
const WILD_ROWS = [
const WILD_ROWS = [
- { title: 'Sound and signal', line: 'vibe as in vibration: sound, waves, a heartbeat', items: [
- ['Soundwave', 'a V drawn in sound bars: the bars bounce', 'soundwave', 'under'],
- ['Ripple', 'a dot and its waves going out', 'ripple', 'side'],
- ['Tuning fork', 'a V that rings when it is struck', 'fork', 'stack'],
- ['Pulse', 'a heartbeat line that draws itself: alive, live', 'pulse', 'under'],
- ['Speech bubble', 'a chat bubble the name sits in', 'bubble', 'bubble'],
+ { title: 'The letters, made new', line: 'OV written five other ways', items: [
+ ['Script', 'ov in one handwritten stroke that writes itself', 'script', 'under', 'draw'],
+ ['Stencil', 'a stencil O and V; the pieces close up', 'stencil', 'side', 'join'],
+ ['Neon', 'OV in neon tubes, flickering on', 'neon', 'side', 'flicker'],
+ ['Blocks', 'OV as solid blocks with depth', 'block3d', 'stack', 'tilt'],
+ ['Tag', 'OV sprayed as a tag, still dripping', 'tag', 'side', 'drip'],
] },
] },
- { title: 'Places and worlds', line: 'somewhere to go: open, wide, bright', items: [
- ['Open door', 'a door that swings open, light coming through', 'door', 'stack'],
- ['Planet', 'a world with a ring: many sites, one place', 'planet', 'side'],
- ['Sunrise', 'a sun rising between two hills: an O in a V', 'sunrise', 'stack'],
- ['Prism', 'a V of glass splitting light into colour', 'prism', 'side'],
- ['Paper plane', 'it glides, and pulls the name behind it', 'plane', 'trail'],
+ { title: 'Monograms', line: 'the O and the V in one sign', items: [
+ ['Woven', 'a V woven through an O', 'crossed', 'side', 'tilt'],
+ ['Linked', 'an O and a V linked like a chain', 'chain', 'side', 'wobble'],
+ ['Coin', 'OV struck on an OpenVibe coin', 'coin', 'pill', 'shine'],
+ ['App tile', 'ov on an app tile, with a shine', 'tile', 'pill', 'shine'],
+ ['Crest', 'an O over a V on a shield', 'crest', 'stack', 'shine'],
] },
] },
- { title: 'Friendly and alive', line: 'a character more than a logo', items: [
- ['Blob', 'a soft shape that breathes and smiles', 'blob', 'pill'],
- ['Smile', 'a friendly face that blinks', 'smile', 'side'],
- ['Eye', 'an open eye that looks around: watch, stream', 'eye', 'side'],
- ['Spark', 'a spark that twinkles: making things', 'spark', 'tag'],
- ['Overlap', 'two circles that meet: people together', 'overlap', 'side'],
+ { title: 'An O standing on a V', line: 'put an O on a V and it becomes a thing', items: [
+ ['Pin', 'a map pin is an O on a V: you are here', 'pin', 'tag', 'drop'],
+ ['Ice cream', 'an O scoop on a V cone', 'cone', 'side', 'wobble'],
+ ['Cheer', 'someone cheering: an O head, V arms up', 'cheer', 'stack', 'wave'],
+ ['Mic', 'a microphone: the O head held in a V', 'mic', 'side', 'pulse'],
+ ['Balloon', 'an O balloon on a V knot, floating', 'balloon', 'trail', 'float'],
] },
] },
- { title: 'Made of parts', line: 'built from pieces: stars, pixels, code', items: [
- ['Constellation', 'stars joined into a V, twinkling', 'stars', 'stack'],
- ['Pixel V', 'a V in big pixels that pop in: play', 'pixel', 'side'],
- ['Ribbon', 'a ribbon folded into a V', 'ribbon', 'tag'],
- ['Code', 'a V between brackets: open source', 'code', 'side'],
- ['Badge', 'ov on an app tile, with a shine', 'badge', 'pill'],
+ { title: 'The O and the V in a scene', line: 'two letters, one little picture', items: [
+ ['Bird and sun', 'a V bird gliding past an O sun', 'bird', 'side', 'glide'],
+ ['Valley', 'an O sun rising in a V valley', 'valley', 'stack', 'rise'],
+ ['Cocktail', 'a V glass with an O olive', 'cocktail', 'side', 'float'],
+ ['Catch', 'an O ball dropping into a V cup', 'catch', 'side', 'hop'],
+ ['Rocket', 'an O window, a V flame: launch', 'rocket', 'trail', 'rocket'],
] },
] },
- { title: 'Nature', line: 'things that move on their own', items: [
- ['Wave', 'a wave that rolls: ride the vibe', 'wave', 'under'],
- ['Flame', 'a flame with a V in its heart, flickering', 'flame', 'side'],
- ['Leaf', 'a leaf with a V vein, swaying', 'leaf', 'side'],
- ['Moon', 'a moon and a star for late streams', 'moon', 'tag'],
- ['Bolt', 'a bolt of energy that flashes', 'bolt', 'side'],
+ { title: 'Signals', line: 'OpenVibe talking, playing, live', items: [
+ ['Speech bubble', 'an O bubble with a V tail, typing', 'bubble', 'bubble', 'cascade'],
+ ['Play', 'an O and a V turned on its side: ● ▶', 'play', 'pill', 'press'],
+ ['Signal', 'V waves arriving at an O', 'signal', 'stack', 'cascade'],
+ ['Heartbeat', 'a heartbeat that writes an o and a v', 'beat', 'under', 'draw'],
+ ['Equaliser', 'an O with bars inside, in a V', 'eqring', 'side', 'bars'],
] },
] },
- { title: 'Things you hold', line: 'objects that say what OpenVibe does', items: [
- ['Headphones', 'headphones with a beat between them', 'headphones', 'stack'],
- ['Play', 'a play button: watch, stream, go', 'play', 'pill'],
- ['Antenna', 'a tower sending out a signal: live', 'antenna', 'stack'],
- ['Gem', 'a gem with a V cut and a glint', 'gem', 'side'],
- ['Dice', 'five dots in a V on a die: play', 'dice', 'side'],
+ { title: 'Things that are an O and a V', line: 'look twice', items: [
+ ['Bulb', 'a bulb with a V filament: ideas', 'bulb', 'side', 'flicker'],
+ ['Ten past ten', 'an O clock whose hands make a V', 'clock', 'side', 'tilt'],
+ ['Target', 'an O target, an arrow with a V tail', 'target', 'tag', 'land'],
+ ['Record', 'an O record with a V on its label', 'vinyl', 'side', 'pulse'],
+ ['Orbit', 'a V with an O orbit round it', 'orbit', 'side', 'tilt'],
] },
] },
- { title: 'Signs and symbols', line: 'shapes everyone already reads', items: [
- ['Pin', 'a map pin is an O on a V: you are here', 'pin', 'tag'],
- ['Heart', 'a heart whose point is the V, beating', 'heart', 'side'],
- ['Shield', 'a V on a shield: safe, yours', 'shield', 'pill'],
- ['Chevrons', 'three Vs that cascade', 'chevrons', 'stack'],
- ['Infinity', 'a loop with a light running round it: open forever', 'infinity', 'under'],
+ { title: 'Built from parts', line: 'OV made of pieces', items: [
+ ['Pixels', 'O and V in big pixels that pop in: play', 'pixel8', 'side', 'pop'],
+ ['Stars', 'OV in stars, joined, twinkling', 'stars2', 'stack', 'twinkle'],
+ ['Halftone', 'OV printed in dots', 'halftone', 'side', 'pop'],
+ ['Tiles', 'an O tile and a V tile that hop', 'tiles', 'side', 'hop'],
+ ['Patch', 'an embroidered OV patch', 'patch', 'pill', 'tilt'],
] },
] },
- { title: 'Play and motion', line: 'caught mid-move', items: [
- ['Kite', 'a kite on the wind, its tail waving', 'kite', 'trail'],
- ['Paper bird', 'a folded bird that flaps', 'bird', 'trail'],
- ['Bounce', 'a ball bouncing a V into the air', 'bounce', 'stack'],
- ['Orbit', 'a moon going round an O', 'orbit', 'side'],
- ['Cursor', 'a pointer that clicks: agents doing things', 'cursor', 'tag'],
+ { title: 'A feel', line: 'the same two letters, five moods', items: [
+ ['Liquid', 'O and V as one drop of liquid', 'liquid', 'side', 'wobble'],
+ ['Glass', 'a frosted O pane over a V', 'glass', 'side', 'glide'],
+ ['Ribbon', 'a ribbon that loops an O and folds a V', 'ribbon2', 'under', 'draw'],
+ ['Sticker', 'a die-cut OV sticker, a corner peeling', 'sticker', 'tag', 'wobble'],
+ ['Face', 'a round O face with a V grin, blinking', 'mascot', 'side', 'blink'],
] },
] },
];
];
+const MOTIONS = ['draw', 'join', 'flicker', 'tilt', 'drip', 'wobble', 'shine', 'drop', 'wave', 'pulse', 'float', 'glide', 'rise', 'hop', 'rocket', 'cascade', 'press', 'bars', 'land', 'pop', 'twinkle', 'blink'];
+
/** The drawing for one key: { defs, body }, every id starting with `id`. */
/** The drawing for one key: { defs, body }, every id starting with `id`. */
function wildArt(key, id) {
function wildArt(key, id) {
const draw = ART[key];
const draw = ART[key];
return draw ? draw(id) : { defs: '', body: '' };
return draw ? draw(id) : { defs: '', body: '' };
}
}
-module.exports = { WILD_ROWS, wildArt, KEYS: Object.keys(ART) };
+module.exports = { WILD_ROWS, wildArt, KEYS: Object.keys(ART), MOTIONS };
@@ -114,14 +114,16 @@ const explore = require('../server/views/marks-explore');
@@ -114,14 +114,16 @@ const explore = require('../server/views/marks-explore');
// Each wildcard moves its own way, attaches the name its own way, and shows itself as a status.
// Each wildcard moves its own way, attaches the name its own way, and shows itself as a status.
const wilds = r.text.match(/<li class="mx-card mx-wild" id="W\d\d"[\s\S]*?<\/li>/g) || [];
const wilds = r.text.match(/<li class="mx-card mx-wild" id="W\d\d"[\s\S]*?<\/li>/g) || [];
assert.strictEqual(wilds.length, 40);
assert.strictEqual(wilds.length, 40);
- assert.strictEqual(new Set(wilds.map((c) => /data-wild="([a-z]+)"/.exec(c)[1])).size, 40, 'forty different ideas');
+ assert.strictEqual(new Set(wilds.map((c) => /data-wild="([a-z0-9]+)"/.exec(c)[1])).size, 40, 'forty different ideas');
assert.ok(wilds.every((c) => /<div class="mx-lock lk-(side|pill|under|bubble|stack|tag|trail)">/.test(c)), 'a lockup each');
assert.ok(wilds.every((c) => /<div class="mx-lock lk-(side|pill|under|bubble|stack|tag|trail)">/.test(c)), 'a lockup each');
assert.ok(wilds.every((c) => /class="[^"]*\bw-k\b/.test(c)), 'a key part each, for the live status');
assert.ok(wilds.every((c) => /class="[^"]*\bw-k\b/.test(c)), 'a key part each, for the live status');
assert.ok(wilds.every((c) => /data-st="live"[\s\S]*data-st="new"/.test(c)), 'the live and new slots');
assert.ok(wilds.every((c) => /data-st="live"[\s\S]*data-st="new"/.test(c)), 'the live and new slots');
assert.ok(new Set(wilds.map((c) => /lk-([a-z]+)/.exec(c)[1])).size >= 6, 'the name attaches in several ways');
assert.ok(new Set(wilds.map((c) => /lk-([a-z]+)/.exec(c)[1])).size >= 6, 'the name attaches in several ways');
const css = require('fs').readFileSync(require('path').join(__dirname, '..', 'public', 'css', 'marks.css'), 'utf8');
const css = require('fs').readFileSync(require('path').join(__dirname, '..', 'public', 'css', 'marks.css'), 'utf8');
- const { KEYS } = require('../server/views/wild-marks');
- assert.deepStrictEqual(KEYS.filter((k) => !css.includes(`svg[data-wild=${k}]`) && !css.includes(`svg[data-wild=${k}],`)), [], 'every idea has its motion');
+ const { MOTIONS, WILD_ROWS } = require('../server/views/wild-marks');
+ assert.deepStrictEqual(MOTIONS.filter((m) => !css.includes(`svg[data-wild-motion=${m}]`)), [], 'every kind of motion is in the CSS');
+ assert.ok(WILD_ROWS.every((r) => r.items.every((it) => MOTIONS.includes(it[4]))), 'every wildcard has one');
+ assert.ok(wilds.every((c) => /data-wild-motion="[a-z]+"/.test(c)));
});
});
await check('the files at /brand/:file still answer', async () => {
await check('the files at /brand/:file still answer', async () => {