@closerclick/closer-click-profile 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/package.json +1 -1
  2. package/src/index.js +33 -2
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@closerclick/closer-click-profile",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Web Component (custom element) <closer-click-profile> reutilizable por cualquier app del ecosistema Closer Click: tarjeta de perfil + reputación (confianza/afinidad, web-of-trust, reputación de la red). Autohosteado, Shadow DOM, temable por CSS vars.",
5
5
  "type": "module",
6
6
  "main": "src/index.js",
package/src/index.js CHANGED
@@ -151,6 +151,9 @@ const STYLE = `
151
151
  --_muted: var(--ccp-muted, var(--muted, #8a7a66));
152
152
  --_accent: var(--ccp-accent, var(--accent, #c0392b));
153
153
  --_accent-2: var(--ccp-accent-2, var(--accent-2, #a93226));
154
+ /* Texto sobre el acento (botón primario). Default blanco; las apps con un
155
+ acento claro (p. ej. lima) lo ponen oscuro vía --ccp-accent-text. */
156
+ --_accent-text: var(--ccp-accent-text, #fff);
154
157
  --_gold: var(--ccp-gold, var(--gold, #d4a72c));
155
158
  --_derived: var(--ccp-derived, var(--derived, #a37a45));
156
159
  --_online: var(--ccp-online, var(--online, #5a8a3a));
@@ -242,6 +245,8 @@ const STYLE = `
242
245
  width: fit-content;
243
246
  }
244
247
  .since { font-size: 12px; color: var(--_muted); }
248
+ .ind-badges { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 6px; }
249
+ .ind-badge { font-size: 12px; font-weight: 700; color: var(--_accent); border: 1px solid var(--_accent); border-radius: 8px; padding: 2px 8px; white-space: nowrap; }
245
250
 
246
251
  /* ----- Sections ----- */
247
252
  .section { display: flex; flex-direction: column; gap: 6px; position: relative; }
@@ -309,7 +314,7 @@ const STYLE = `
309
314
  /* ----- Footer ----- */
310
315
  .foot { display: flex; gap: 10px; justify-content: flex-end; padding: 14px 24px; background: var(--_bg-2); border-top: 1px solid var(--_border); }
311
316
  .btn { font: inherit; font-weight: 600; padding: 9px 16px; border-radius: 10px; border: 1px solid transparent; cursor: pointer; }
312
- .btn.primary { background: var(--_accent); color: #fff; }
317
+ .btn.primary { background: var(--_accent); color: var(--_accent-text); }
313
318
  .btn.primary:hover:not(:disabled) { background: var(--_accent-2); }
314
319
  .btn.primary:disabled { opacity: 0.6; cursor: default; }
315
320
  .btn.secondary { background: transparent; color: var(--_text); border-color: var(--_border); }
@@ -318,7 +323,7 @@ const STYLE = `
318
323
 
319
324
  class CloserClickProfile extends HTMLElement {
320
325
  static get observedAttributes() {
321
- return ['pubkey', 'name', 'since', 'online', 'mode', 'modal', 'heading', 'lang']
326
+ return ['pubkey', 'name', 'since', 'online', 'mode', 'modal', 'heading', 'lang', 'indicators']
322
327
  }
323
328
 
324
329
  constructor() {
@@ -328,6 +333,7 @@ class CloserClickProfile extends HTMLElement {
328
333
  this._my = { confianza: 0, afinidad: 0, notes: '' }
329
334
  this._endorsements = []
330
335
  this._derived = null
336
+ this._indicators = [] // indicadores derivados a mostrar (p. ej. ELO por juego)
331
337
  this._cloud = null
332
338
  this._cloudLoading = true
333
339
  this._hover = 0
@@ -357,9 +363,17 @@ class CloserClickProfile extends HTMLElement {
357
363
  attributeChangedCallback(name, oldV, newV) {
358
364
  if (oldV === newV) return
359
365
  if (name === 'pubkey') { this._resetState(); this.reload(); return }
366
+ if (name === 'indicators') { this._indicators = []; this.reload(); return }
360
367
  if (this.shadowRoot.childElementCount) this._render()
361
368
  }
362
369
 
370
+ // Indicadores derivados a mostrar (atributo `indicators`), p. ej.
371
+ // "elo:cuarenta" o "elo:chess,elo:cuarenta". Formato: nombre[:scope], coma.
372
+ get _indicatorSpecs() {
373
+ return (this.getAttribute('indicators') || '').split(',').map(s => s.trim()).filter(Boolean)
374
+ .map(s => { const [iName, scope] = s.split(':'); return { name: iName, scope: scope || undefined } })
375
+ }
376
+
363
377
  _onKeydown(e) {
364
378
  if (e.key === 'Escape' && this.hasAttribute('modal')) this._close()
365
379
  }
@@ -385,6 +399,7 @@ class CloserClickProfile extends HTMLElement {
385
399
  this._my = { confianza: 0, afinidad: 0, notes: '' }
386
400
  this._endorsements = []
387
401
  this._derived = null
402
+ this._indicators = []
388
403
  this._cloud = null
389
404
  this._cloudLoading = true
390
405
  this._hover = 0
@@ -419,6 +434,17 @@ class CloserClickProfile extends HTMLElement {
419
434
  this._derived = e ? e.derived : null
420
435
  }
421
436
  } catch (_) { /* best-effort */ }
437
+ // Indicadores derivados (p. ej. ELO por juego), vía provider.getDerived.
438
+ try {
439
+ const specs = this._indicatorSpecs
440
+ if (specs.length && typeof p.getDerived === 'function') {
441
+ const vals = await Promise.all(specs.map(async s => {
442
+ try { const d = await p.getDerived(pk, s.name, s.scope); return d ? { ...s, value: (d.value != null ? d.value : d.elo), count: (d.count != null ? d.count : d.games) } : null } catch (_) { return null }
443
+ }))
444
+ if (token !== this._loadToken) return
445
+ this._indicators = vals.filter(v => v && v.value != null)
446
+ }
447
+ } catch (_) { /* best-effort */ }
422
448
  if (token === this._loadToken) this._render()
423
449
 
424
450
  try {
@@ -556,6 +582,11 @@ class CloserClickProfile extends HTMLElement {
556
582
  </label>` : `<div class="name">${this._esc(name)}</div>`}
557
583
  <code class="pubkey">${this._esc(this._shortKey(pk))}</code>
558
584
  ${since ? `<div class="since">${this._esc(t.knownSince)} ${this._esc(this._fmtDate(since))}</div>` : ''}
585
+ ${this._indicators.length ? `<div class="ind-badges">${this._indicators.map(i => {
586
+ const label = this._esc((i.name || '').toUpperCase())
587
+ const tip = label + (i.scope ? ' · ' + this._esc(i.scope) : '') + (i.count != null ? ' · ' + i.count : '')
588
+ return `<span class="ind-badge" title="${tip}">${label} ${this._esc(String(i.value))}</span>`
589
+ }).join('')}</div>` : ''}
559
590
  </div>
560
591
  </div>`
561
592