SLOPSHOPPER

theming-demo

The theming-demo hooks module

newtoast
A shopper browsing a rack in a slop shop
README

theming-demo

Sample mod for the $.theme API. It:

  • recolors the app with $.theme.vars (separate light and dark palettes for --accent, --primary, --bg-sidebar),
  • styles its own sidebar section with a scoped $.theme.css sheet (accent border and title),
  • adds a Theme demo sidebar section with Swap palette (forest ↔ dusk) and Clear theme.

Not installed by default. Copy this folder to ~/.pawn/mods/theming-demo and allow it in the review dialog; the dialog lists the Change colors and styles permission. Switch the theme off without disabling the mod from the mods menu (Theme toggle).

Docs: docs/agent/MODS.md.

Source 1 files
hooks/register.js 58 lines
1// Palettes for the whole app. Only `--name` custom properties are accepted.
2const PALETTES = {
3  forest: {
4    light: { '--accent': '#2f6b3a', '--primary': '#2f6b3a', '--bg-sidebar': '#eef3ec' },
5    dark: { '--accent': '#8fd19e', '--primary': '#8fd19e', '--bg-sidebar': '#121a14' }
6  },
7  dusk: {
8    light: { '--accent': '#6b3fa0', '--primary': '#6b3fa0', '--bg-sidebar': '#f3eef8' },
9    dark: { '--accent': '#c3a6ff', '--primary': '#c3a6ff', '--bg-sidebar': '#17121f' }
10  }
11}
12let current = 'forest'
13
14// Each $.theme / $.slot call is written out literally so consent shows it.
15function apply($) {
16  const p = PALETTES[current]
17  $.theme.vars({ id: 'palette-light', mode: 'light', vars: p.light })
18  $.theme.vars({ id: 'palette-dark', mode: 'dark', vars: p.dark })
19  // Scoped: this sheet only reaches elements this mod drew through $.slot.*.
20  $.theme.css({
21    id: 'card',
22    css: [
23      ':scope { border-left: 3px solid var(--accent); border-radius: 6px; }',
24      '.mod-slot-sidebar-name { color: var(--accent); font-weight: 600; }'
25    ].join('\n')
26  })
27  const el = $.ui.resolve({})
28  $.slot.sidebar({
29    id: 'theme',
30    title: 'Theme demo',
31    tree: [
32      el.Text({ text: 'palette: ' + current }),
33      el.Button({ id: 'swap', text: 'Swap palette' }),
34      el.Button({ id: 'off', text: 'Clear theme' })
35    ]
36  })
37}
38
39export function register(on) {
40  on('session.start', async ($, e, next) => {
41    if (e.plugin !== $.plugin.name) return next(e)
42    apply($)
43    return next(e)
44  })
45
46  on('ui.press', { plugin: 'theming-demo' }, async ($, e, next) => {
47    if (e.id === 'swap') {
48      current = current === 'forest' ? 'dusk' : 'forest'
49      apply($)
50    }
51    if (e.id === 'off') {
52      for (const t of $.theme.list()) $.theme.remove({ id: t.id })
53      $.ui.toast('Theme cleared. Swap palette to bring it back.')
54    }
55    return next(e)
56  })
57}
58