From f52fb05f5e22f3517903f956ceb61a97e5b0b693 Mon Sep 17 00:00:00 2001 From: root Date: Tue, 9 Jun 2026 23:58:03 +1000 Subject: [PATCH] feat(dross): avatar component (soft-eye / wisp / motes) Co-Authored-By: Claude Sonnet 4.6 --- public/components/dross_avatar.js | 20 ++++++++++++++++++++ tests/views/dross_avatar.test.js | 21 +++++++++++++++++++++ 2 files changed, 41 insertions(+) create mode 100644 public/components/dross_avatar.js create mode 100644 tests/views/dross_avatar.test.js diff --git a/public/components/dross_avatar.js b/public/components/dross_avatar.js new file mode 100644 index 0000000..3eb8941 --- /dev/null +++ b/public/components/dross_avatar.js @@ -0,0 +1,20 @@ +// public/components/dross_avatar.js +import { el } from '../dom.js'; + +// Returns a .dross-orb element rendering the chosen avatar. Colours come from +// CSS vars (--dross*), set on the element by the caller for per-user accent. +export function drossAvatar(variant = 'soft-eye', size = 60) { + let inner; + if (variant === 'wisp') { + inner = [el('div', { class: 'b-core' }), el('div', { class: 'b-bright' })]; + } else if (variant === 'motes') { + inner = [ + el('div', { class: 'd-ring' }, el('div', { class: 'd-mote' })), + el('div', { class: 'd-ring r2' }, el('div', { class: 'd-mote' })), + el('div', { class: 'd-core' }) + ]; + } else { // soft-eye (default) + inner = [el('div', { class: 'av-eye' }, el('div', { class: 'av-pupil' }))]; + } + return el('div', { class: 'dross-orb', style: { width: size + 'px', height: size + 'px' } }, ...inner); +} diff --git a/tests/views/dross_avatar.test.js b/tests/views/dross_avatar.test.js new file mode 100644 index 0000000..732b58d --- /dev/null +++ b/tests/views/dross_avatar.test.js @@ -0,0 +1,21 @@ +// @vitest-environment jsdom +import { describe, it, expect } from 'vitest'; +import { drossAvatar } from '../../public/components/dross_avatar.js'; + +describe('drossAvatar', () => { + it('renders the requested variant class', () => { + const eye = drossAvatar('soft-eye', 60); + expect(eye.classList.contains('dross-orb')).toBe(true); + expect(eye.querySelector('.av-eye')).toBeTruthy(); + expect(drossAvatar('wisp', 30).querySelector('.b-core')).toBeTruthy(); + expect(drossAvatar('motes', 30).querySelector('.d-core')).toBeTruthy(); + }); + it('falls back to soft-eye for unknown variants', () => { + expect(drossAvatar('bogus', 60).querySelector('.av-eye')).toBeTruthy(); + }); + it('sets the pixel size', () => { + const a = drossAvatar('wisp', 42); + expect(a.style.width).toBe('42px'); + expect(a.style.height).toBe('42px'); + }); +});