diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-16 09:11:29 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-16 09:11:29 -0500 |
| commit | 6189466dd21f3e19b6bdae9bf74e38f6211ae542 (patch) | |
| tree | 0ca48eb533345dcd71a2e8933b21070cf1f5bd56 | |
| parent | 910993b25ba1736fc7f5958c53a7cdc523f98cf2 (diff) | |
| download | archsetup-6189466dd21f3e19b6bdae9bf74e38f6211ae542.tar.gz archsetup-6189466dd21f3e19b6bdae9bf74e38f6211ae542.zip | |
feat(gallery): rework the console-key and slide-toggle colour vocabulary
Console keys read SCAN before LIVE, and LIVE is green. The palette already defines --pass as "run lamps, gear greens, monitor bars, LIVE lamps", so the widget contradicted the language it draws from. Its note asserted the old colours too. The per-key `red` boolean became a `tone` field, since a boolean can't express three states.
The slide toggle gains red and warn on its `on` axis, so the engaged state can be the notable one (mute, record, airplane mode). Red was reachable only on the `off` axis before, which paints the disengaged state: the opposite claim. A new onText axis makes the ON ink reachable at all. `on` had bundled background and ink together, so "dark pill, green legend" couldn't be expressed.
Presets name a combination (panel, run, armed, caution, dark), which is what a consumer reaches for. The axes are how one gets built.
| -rw-r--r-- | docs/prototypes/panel-widget-gallery.html | 44 | ||||
| -rw-r--r-- | docs/prototypes/widgets.js | 84 | ||||
| -rw-r--r-- | tests/gallery-probes/probe.mjs | 140 |
3 files changed, 251 insertions, 17 deletions
diff --git a/docs/prototypes/panel-widget-gallery.html b/docs/prototypes/panel-widget-gallery.html index 2a2910b..e64ddd3 100644 --- a/docs/prototypes/panel-widget-gallery.html +++ b/docs/prototypes/panel-widget-gallery.html @@ -1187,7 +1187,7 @@ card(C,'01','Slide toggle', '<b>on / off, the touchscreen way.</b> The one born-digital control in the kit — the smartphone pill toggle the live net/bt/audio panels use. Click to flip; readout shows the state. Physical cousins: the bat-handle (R03) and the rocker (N01).'); card(C,'02','Console key', (st,rd)=>GW.consoleKeys(st,{onChange:(i,t)=>rd(t)}), - '<b>physical push button.</b> Mutually exclusive — click one to engage. Gold = live, terracotta = muted.'); + '<b>physical push button.</b> Mutually exclusive — click one to engage. Green = live, terracotta = muted, gold = any other engaged key.'); card(C,'03','Horizontal fader', (st,rd)=>GW.faderH(st,{value:68,onChange:(v,t)=>rd(t)}), '<b>continuous 0-100.</b> Per-device volume, brightness. Drag the gold cap; readout tracks.'); @@ -1657,25 +1657,57 @@ BOOST.forEach(no=>{const rd=document.getElementById('rd-'+no); m=>ids.has(m)?`<a class="xref" href="#card-${m}">${m}</a>`:m);});})(); /* style chips: shared demo rig for builders with constructor style opts — - reads a builder's STYLES table and drives setStyle on the live card instance */ -function styleChips(no,STYLES,AXES){ + reads a builder's STYLES table and drives setStyle on the live card instance. + PRESETS (optional) adds a leading group of named combinations: a preset chip + applies all axes at once and resyncs the axis chips beneath it, and changing + any axis afterwards clears the preset — the chips must never claim a preset + the widget has since diverged from. */ +function styleChips(no,STYLES,AXES,PRESETS,defPreset){ const h=$('card-'+no)?.gw; if(!h)return; const cardEl=h.el.closest('.card'); const row=document.createElement('div'); row.className='famchips'; + const sel={}, sync={}, groups=[]; let clearPreset=()=>{}; + /* Re-apply every axis on any change rather than just the one clicked: axes are + not independent (onText overrides the ink `on` sets), so a lone setStyle can + drop a selection the chips still show as lit — the card would then display a + combination the widget isn't in. Applying all of them in AXES order makes + the outcome independent of click order. */ + const applyAll=()=>{for(const [,axis] of AXES) if(sel[axis]) h.setStyle(axis,sel[axis]);}; for(const [label,axis,def] of AXES){ + sel[axis]=def; const g=document.createElement('span'); g.className='fgroup'; const lab=document.createElement('span'); lab.className='lab'; lab.textContent=label; g.appendChild(lab); const chips=[]; for(const [name,o] of Object.entries(STYLES[axis])){ const b=document.createElement('span'); b.className='fc'+(name===def?' on':''); b.style.background=o.dot; b.title=name; chips.push(b); - b.addEventListener('click',()=>{h.setStyle(axis,name); - chips.forEach(x=>x.classList.toggle('on',x===b));}); + b.addEventListener('click',()=>{sel[axis]=name; applyAll(); + chips.forEach(x=>x.classList.toggle('on',x===b)); clearPreset();}); g.appendChild(b);} + sync[axis]=name=>{sel[axis]=name; chips.forEach(x=>x.classList.toggle('on',x.title===name));}; + groups.push(g);} + if(PRESETS&&h.setPreset){ + const g=document.createElement('span'); g.className='fgroup'; + const lab=document.createElement('span'); lab.className='lab'; lab.textContent='preset'; g.appendChild(lab); + const chips=[]; + for(const [name,p] of Object.entries(PRESETS)){ + const b=document.createElement('span'); b.className='fc'+(name===defPreset?' on':''); b.title=name; + /* the on-tone is the preset's identity, so it doubles as the swatch */ + b.style.background=STYLES.on[p.on].dot; chips.push(b); + b.addEventListener('click',()=>{ + const applied=h.setPreset(name); if(!applied)return; + chips.forEach(x=>x.classList.toggle('on',x===b)); + for(const [axis,style] of Object.entries(applied)) sync[axis]?.(style);}); + g.appendChild(b);} + clearPreset=()=>chips.forEach(x=>x.classList.remove('on')); row.appendChild(g);} + groups.forEach(g=>row.appendChild(g)); cardEl.querySelector('.opts').appendChild(row); } -styleChips('01',GW.slideToggle.STYLES,[['on','on','amber'],['off','off','dark'],['off text','offText','white'],['thumb','thumb','light']]); +styleChips('01',GW.slideToggle.STYLES, + [['on','on','amber'],['on text','onText','panel'],['off','off','dark'], + ['off text','offText','white'],['thumb','thumb','light']], + GW.slideToggle.PRESETS,'panel'); styleChips('R05',GW.filterBank.STYLES,[['panel','panel','silver'],['shape','caps','block'],['color','capColor','black']]); styleChips('06',GW.segmented.STYLES,[['accent','accent','amber']]); styleChips('25',GW.slideRule.STYLES,[['face','skin','warm']]); diff --git a/docs/prototypes/widgets.js b/docs/prototypes/widgets.js index c12e5b7..147c782 100644 --- a/docs/prototypes/widgets.js +++ b/docs/prototypes/widgets.js @@ -126,30 +126,77 @@ GW.slideToggle = function (host, opts = {}) { if (!o) return; for (const [k, v] of Object.entries(o.vars)) sw.style.setProperty(k, v); }; - setStyle('on', opts.onStyle || 'amber'); - setStyle('off', opts.offStyle || 'dark'); - setStyle('offText', opts.offText || 'white'); - setStyle('thumb', opts.thumb || 'light'); + /* opts.preset names an intent; per-axis opts still win over it, so a caller can + take 'armed' and swap only the thumb. setPreset re-applies all four axes at + once and returns the axis map so a host UI can resync its own controls. */ + const preset = GW.slideToggle.PRESETS[opts.preset] || GW.slideToggle.PRESETS.panel; + /* AXIS_ORDER is load-bearing, not cosmetic: onText overrides the ink that the + `on` style sets, so applying the axes in any other order silently drops it. */ + const setPreset = name => { + const p = GW.slideToggle.PRESETS[name]; + if (!p) return null; + for (const axis of GW.slideToggle.AXIS_ORDER) if (p[axis]) setStyle(axis, p[axis]); + return p; + }; + const pick = { on: opts.onStyle, onText: opts.onText, off: opts.offStyle, + offText: opts.offText, thumb: opts.thumb }; + for (const axis of GW.slideToggle.AXIS_ORDER) setStyle(axis, pick[axis] || preset[axis]); let on; const set = v => { on = !!v; sw.classList.toggle('on', on); onChange(on, on ? 'ON' : 'OFF'); }; sw.addEventListener('click', () => set(!on)); set(opts.on !== undefined ? opts.on : true); - return { el: sw, get: () => on, set, setStyle }; + return { el: sw, get: () => on, set, setStyle, setPreset }; +}; +/* named presets — a combination that carries intent, which is what a consumer + actually reaches for; the STYLES axes below are how one is built. The `on` + tone does the semantic work and the thumb material backs it: a touchscreen + pill wears plastic, a run or armed switch wears metal. */ +GW.slideToggle.AXIS_ORDER = ['on', 'onText', 'off', 'offText', 'thumb']; +GW.slideToggle.PRESETS = { + panel: { on: 'amber', onText: 'panel', off: 'dark', offText: 'white', thumb: 'light' }, + run: { on: 'green', onText: 'panel', off: 'dark', offText: 'dim', thumb: 'chrome' }, + armed: { on: 'red', onText: 'cream', off: 'dark', offText: 'white', thumb: 'chrome' }, + caution: { on: 'warn', onText: 'panel', off: 'dark', offText: 'white', thumb: 'light' }, + /* dark — neither state lights the pill; the legend alone carries the state, + green for on and Control red ("OFF marks") for off. The chrome thumb stays + the position affordance, since with both pills dark it is the only one. */ + dark: { on: 'dark', onText: 'green', off: 'dark', offText: 'red', thumb: 'chrome' }, }; /* named styles per axis: dot = swatch color for pickers, vars = --sw-* overrides */ GW.slideToggle.STYLES = { on: { amber: { dot: 'var(--gold-hi)', vars: { '--sw-on-bg': 'linear-gradient(180deg,var(--amber-grad-top),var(--gold))', '--sw-on-brd': 'var(--gold-hi)', '--sw-on-ink': 'var(--panel)' } }, green: { dot: '#8fb944', vars: { '--sw-on-bg': 'linear-gradient(180deg,#a9c95f,var(--pass))', '--sw-on-brd': '#a9c95f', '--sw-on-ink': 'var(--panel)' } }, + /* red/warn make the ENGAGED state the notable one — mute, record, airplane + mode. Before them the only red was on the `off` axis, which paints the + disengaged state: the opposite claim. Palette semantics: --fail is + "muted keys", --amber-warn is "warn cells", distinct from panel amber. */ + red: { dot: 'var(--fail)', vars: { '--sw-on-bg': 'linear-gradient(180deg,#d98a6f,var(--fail))', '--sw-on-brd': 'var(--fail)', '--sw-on-ink': 'var(--cream)' } }, + warn: { dot: 'var(--amber-warn)', vars: { '--sw-on-bg': 'linear-gradient(180deg,#f7d193,var(--amber-warn))', '--sw-on-brd': 'var(--amber-warn)', '--sw-on-ink': 'var(--panel)' } }, dark: { dot: '#242019', vars: { '--sw-on-bg': 'linear-gradient(180deg,#15130f,#242019)', '--sw-on-brd': 'var(--slate)', '--sw-on-ink': 'var(--cream)' } }, }, off: { dark: { dot: '#242019', vars: { '--sw-off-bg': 'linear-gradient(180deg,#15130f,#242019)', '--sw-off-brd': 'var(--slate)' } }, red: { dot: 'var(--fail)', vars: { '--sw-off-bg': 'linear-gradient(180deg,#d98a6f,var(--fail))', '--sw-off-brd': 'var(--fail)' } }, }, + /* onText overrides the ink the `on` style sets. The two are not independent — + ink must contrast with its own background, which is why each `on` atom still + carries a sensible default; naming onText is how a caller says "keep the + pill, change the legend". That is what makes the `dark` preset possible: + both states unlit, the legend colour carrying the state on its own. */ + onText: { + panel: { dot: 'var(--panel)', vars: { '--sw-on-ink': 'var(--panel)' } }, + cream: { dot: 'var(--cream)', vars: { '--sw-on-ink': 'var(--cream)' } }, + green: { dot: 'var(--sevgrn)', vars: { '--sw-on-ink': 'var(--sevgrn)' } }, + }, offText: { white: { dot: 'var(--cream)', vars: { '--sw-off-ink': 'var(--cream)' } }, + /* Control red — the palette's "OFF marks" ink, so a red OFF legend is the + kit's existing grammar rather than a new claim. */ red: { dot: '#e0523a', vars: { '--sw-off-ink': '#e0523a' } }, + /* steel is the kit's engraved-caption ink: lets an off toggle recede where a + panel carries many of them and eight cream OFFs would all shout equally. */ + dim: { dot: 'var(--steel)', vars: { '--sw-off-ink': 'var(--steel)' } }, black: { dot: '#14110e', vars: { '--sw-off-ink': '#14110e' } }, }, thumb: { @@ -160,10 +207,14 @@ GW.slideToggle.STYLES = { }, }; -/* 02 console keys — mutually exclusive push buttons; {label, red} per key */ +/* 02 console keys — mutually exclusive push buttons; {label, tone} per key. + tone picks the engaged look: undefined = gold (the default lit key), 'green' = + run green, 'red' = terracotta. Reading order runs safe -> live -> muted, and + LIVE takes green because that is what --pass means everywhere else in the kit + (the palette names it "run lamps, gear greens, monitor bars, LIVE lamps"). */ GW.consoleKeys = function (host, opts = {}) { const onChange = opts.onChange || noop; - const keys = opts.keys || [{ label: 'LIVE' }, { label: 'SCAN' }, { label: 'MUTED', red: true }]; + const keys = opts.keys || GW.consoleKeys.DEFAULT_KEYS; const wrap = document.createElement('span'); host.appendChild(wrap); const btns = keys.map(k => { const b = document.createElement('button'); b.className = 'key'; b.textContent = k.label; @@ -171,14 +222,24 @@ GW.consoleKeys = function (host, opts = {}) { }); let idx; const set = i => { - idx = i; - btns.forEach((b, j) => { b.classList.remove('on', 'red'); if (j === i) b.classList.add(keys[i].red ? 'red' : 'on'); }); - onChange(i, keys[i].label); + idx = Math.max(0, Math.min(keys.length - 1, i)); + btns.forEach((b, j) => { + b.classList.remove('on', 'green', 'red'); + if (j === idx) b.classList.add(keys[idx].tone || 'on'); + }); + onChange(idx, keys[idx].label); }; btns.forEach((b, i) => b.addEventListener('click', () => set(i))); - set(opts.active || 0); + /* The default set engages LIVE, not the first key: it is the meaningful + resting state for a console and it shows the green. A caller supplying its + own keys gets the first one unless it says otherwise. */ + set(opts.active !== undefined ? opts.active + : (keys === GW.consoleKeys.DEFAULT_KEYS ? 1 : 0)); return { el: wrap, get: () => idx, set }; }; +GW.consoleKeys.DEFAULT_KEYS = [ + { label: 'SCAN' }, { label: 'LIVE', tone: 'green' }, { label: 'MUTED', tone: 'red' }, +]; /* 03 horizontal fader — continuous 0-100 */ GW.faderH = function (host, opts = {}) { @@ -4011,6 +4072,7 @@ const GW_CSS = ` .key:hover{color:var(--gold);border-color:var(--gold)} .key:active{transform:translateY(1px)} .key.on{color:var(--panel);background:linear-gradient(180deg,var(--amber-grad-top),var(--gold));border-color:var(--gold-hi);font-weight:700} +.key.green{color:var(--cream);background:linear-gradient(180deg,#9cbf5e,var(--pass));border-color:var(--pass);font-weight:700} .key.red{color:var(--cream);background:linear-gradient(180deg,#d98a6f,var(--fail));border-color:var(--fail)} .key.off{opacity:.4} diff --git a/tests/gallery-probes/probe.mjs b/tests/gallery-probes/probe.mjs index 17b3d73..d196377 100644 --- a/tests/gallery-probes/probe.mjs +++ b/tests/gallery-probes/probe.mjs @@ -100,6 +100,146 @@ try { const t1 = await evl(`document.getElementById('rd-01').textContent`); ok('toggle click responds at 3x', t0 !== t1, `'${t0}' -> '${t1}'`); + // 5. card 02 console keys: reading order and per-key tone. LIVE is green + // because --pass is what the kit means by live everywhere else; gold stays + // the generic engaged look. Craig's call, 2026-07-16. + const order = await evl(`[...document.querySelectorAll('#card-02 .key')].map(b => b.textContent)`); + ok('card 02 keys read SCAN, LIVE, MUTED', + JSON.stringify(order) === JSON.stringify(['SCAN', 'LIVE', 'MUTED']), JSON.stringify(order)); + + const engaged = await evl(`(()=>{ + const lit = [...document.querySelectorAll('#card-02 .key')].filter(b => /\\b(on|green|red)\\b/.test(b.className)); + return lit.length === 1 ? lit[0].textContent + ':' + lit[0].className.replace('key ','') : 'lit=' + lit.length; + })()`); + ok('card 02 engages LIVE in green by default', engaged === 'LIVE:green', engaged); + + const muted = await evl(`(()=>{ + const keys = [...document.querySelectorAll('#card-02 .key')]; + keys.find(b => b.textContent === 'MUTED').click(); + const lit = keys.filter(b => /\\b(on|green|red)\\b/.test(b.className)); + return lit.length === 1 ? lit[0].textContent + ':' + lit[0].className.replace('key ','') : 'lit=' + lit.length; + })()`); + ok('card 02 MUTED engages red and releases LIVE', muted === 'MUTED:red', muted); + + const rd02 = await evl(`document.getElementById('rd-02').textContent`); + ok('card 02 readout tracks the engaged key', rd02 === 'MUTED', rd02); + + // 6. card 01 slide toggle: the new tone atoms exist. red/warn on the `on` axis + // let the ENGAGED state be the notable one (mute, record, airplane) — before + // these, red was only reachable via the `off` axis, which colours the + // disengaged state instead. dim offText lets an off toggle recede in a dense + // panel. Craig's call, 2026-07-16. + const atoms = await evl(`(()=>{ + const S = GW.slideToggle.STYLES; + const want = { on: ['amber','green','dark','red','warn'], onText: ['panel','cream','green'], + off: ['dark','red'], offText: ['white','red','black','dim'], + thumb: ['light','dark','chrome','brass'] }; + const missing = []; + for (const [axis, names] of Object.entries(want)) + for (const n of names) if (!S[axis] || !S[axis][n]) missing.push(axis + '.' + n); + return missing.length ? 'missing: ' + missing.join(', ') : 'ok'; + })()`); + ok('slide toggle style atoms present', atoms === 'ok', atoms); + + // 7. presets name intent (a combination), not paint. Every axis a preset names + // must resolve in STYLES — a typo here would silently fall through to the + // stylesheet default and look "nearly right", which is the worst outcome. + const presets = await evl(`(()=>{ + const P = GW.slideToggle.PRESETS, S = GW.slideToggle.STYLES; + if (!P) return 'no PRESETS'; + const AX = GW.slideToggle.AXIS_ORDER; + const bad = []; + for (const [name, p] of Object.entries(P)) { + for (const ax of AX) { + if (!p[ax]) bad.push(name + ' missing ' + ax); + else if (!S[ax][p[ax]]) bad.push(name + '.' + ax + '="' + p[ax] + '" not in STYLES'); + } + } + return bad.length ? bad.join('; ') : Object.keys(P).join(','); + })()`); + ok('every preset names a resolvable style on every axis', + presets === 'panel,run,armed,caution,dark', presets); + + // 7a. the dark preset: neither state lights the pill, so the legend colour is + // the only thing carrying state. Both backgrounds must stay dark while the + // inks diverge — if either pill lights, the preset has lost its point. + const dark = await evl(`(()=>{ + const P = GW.slideToggle.PRESETS.dark, S = GW.slideToggle.STYLES; + const onBg = S.on[P.on].vars['--sw-on-bg'], offBg = S.off[P.off].vars['--sw-off-bg']; + const onInk = S.onText[P.onText].vars['--sw-on-ink'], offInk = S.offText[P.offText].vars['--sw-off-ink']; + if (onBg !== offBg) return 'pills differ: on=' + onBg + ' off=' + offBg; + if (onInk === offInk) return 'inks identical, state unreadable: ' + onInk; + return 'ok'; + })()`); + ok('dark preset keeps both pills dark and the inks distinct', dark === 'ok', dark); + + // 7b. the card claims the preset it is actually in. The widget defaults to + // `panel`, so a preset group with nothing lit would assert "no preset + // active" — false, and exactly the kind of quiet mislabel this walk exists + // to catch. Nothing above this point touches card 01's chips (checks 2-4 + // only toggle its switch, which does not restyle), so the default still + // stands here — but this must stay ABOVE checks 8/8b/9, which do mutate + // the chips. Insert preset-touching checks after them, not before. + const defaultPreset = await evl(`(()=>{ + const pg = [...document.querySelectorAll('#card-01 .fgroup')] + .find(g => g.querySelector('.lab')?.textContent === 'preset'); + if (!pg) return 'no preset group'; + const lit = [...pg.querySelectorAll('.fc')].filter(c => c.classList.contains('on')); + return lit.length === 1 ? lit[0].title : 'lit=' + lit.length; + })()`); + ok('card 01 defaults to the panel preset', defaultPreset === 'panel', defaultPreset); + + // 8. a preset chip drives the widget AND re-syncs the axis chips, so the card + // never shows a combination the widget isn't in. + const applied = await evl(`(()=>{ + const card = document.getElementById('card-01'); + const groups = [...card.querySelectorAll('.fgroup')]; + const pg = groups.find(g => g.querySelector('.lab')?.textContent === 'preset'); + if (!pg) return 'no preset group'; + const armed = [...pg.querySelectorAll('.fc')].find(c => c.title === 'armed'); + if (!armed) return 'no armed chip'; + armed.click(); + const onGroup = groups.find(g => g.querySelector('.lab')?.textContent === 'on'); + const lit = [...onGroup.querySelectorAll('.fc')].filter(c => c.classList.contains('on')); + const brd = card.querySelector('.switch').style.getPropertyValue('--sw-on-brd'); + return (lit.length === 1 ? lit[0].title : 'lit=' + lit.length) + '|' + brd; + })()`); + ok('armed preset drives widget and syncs the on chip', applied === 'red|var(--fail)', applied); + + // 8b. axes are not independent: onText overrides the ink `on` sets, so changing + // `on` after picking an onText must not silently revert the legend while its + // chip still shows lit. Drive it in the hazardous order and check the ink. + const orderSafe = await evl(`(()=>{ + const card = document.getElementById('card-01'); + const groups = [...card.querySelectorAll('.fgroup')]; + const grp = l => groups.find(g => g.querySelector('.lab')?.textContent === l); + const chip = (l, t) => [...grp(l).querySelectorAll('.fc')].find(c => c.title === t); + chip('on text', 'green').click(); // legend green + chip('on', 'dark').click(); // then change the pill — must keep it + const ink = card.querySelector('.switch').style.getPropertyValue('--sw-on-ink'); + const lit = [...grp('on text').querySelectorAll('.fc')].filter(c => c.classList.contains('on')); + return ink + '|' + (lit.length === 1 ? lit[0].title : 'lit=' + lit.length); + })()`); + ok('changing on keeps the chosen onText (chips cannot lie)', + orderSafe === 'var(--sevgrn)|green', orderSafe); + + // 9. diverging on one axis clears the preset — the card stops claiming a preset + // it is no longer in. Re-establishes its own precondition (preset lit) rather + // than inheriting it: the checks above already clicked axis chips, each of + // which clears the preset, so asserting lit===0 without re-lighting first + // would pass against an already-cleared group and prove nothing — including + // against a regression where clearing fired for onText but not for `on`. + const diverged = await evl(`(()=>{ + const groups = [...document.querySelectorAll('#card-01 .fgroup')]; + const grp = l => groups.find(g => g.querySelector('.lab')?.textContent === l); + const litPresets = () => [...grp('preset').querySelectorAll('.fc')].filter(c => c.classList.contains('on')).length; + [...grp('preset').querySelectorAll('.fc')].find(c => c.title === 'run').click(); + const before = litPresets(); + [...grp('on').querySelectorAll('.fc')].find(c => c.title === 'green').click(); + return before + '->' + litPresets(); + })()`); + ok('changing an axis clears the preset selection', diverged === '1->0', diverged); + // late exceptions from interactions const errs2 = events.filter(e => e.method === 'Runtime.exceptionThrown'); ok('no exceptions after interaction', errs2.length === 0); |
