Skip to content

Tutorial: a flat design mywant-gui ​

The built-in grid design draws overlays as full-bleed coloured tiles on a dark box. This tutorial builds Flat — a light card, cells as separate rounded tiles with a gap between them, solid colours, words in their own case — and ships it as an extension that installs beside the GUI with no build step.

A design can also be shipped as a design custom, installed with mywant custom install owner/repo — see Tutorial: a design plugin, which compares the two. The finished extension is in this site's repository: examples/overlay-flat.

overlay-flat/
├── gui-extension.json   the manifest
├── overlay-flat.js      registers the design
└── overlay-flat.css     what its class names look like

1. The manifest ​

json
{
  "name": "overlay-flat",
  "version": "0.1.0",
  "script": "overlay-flat.js",
  "styles": ["overlay-flat.css"]
}

No requires: the extension uses only the design API, so it does not pin a mywant-gui version. See Runtime extensions.

2. The design ​

A design is only class names (Designs). Flat prefixes its own with mwf- and registers through the extension API, which mywant-gui publishes on window.__mywantModules before it loads any extension:

js
/*
 * overlay-flat — a flat overlay design for mywant-gui, as a runtime extension.
 *
 * No build step: mywant-gui loads this file as a plain script before its first
 * render, with its own modules published on window.__mywantModules. The design
 * is only class names; what they look like is in overlay-flat.css, which the
 * manifest lists under "styles". (The app's Tailwind build contains only the
 * classes the app itself uses, so a design from outside brings its own.)
 *
 * Install: copy this directory to ~/.mywant/gui-extensions/overlay-flat and
 * reload the GUI. Pick "Flat" under Overlay Design in your character's display.
 */
(function () {
  var design = {
    id: 'flat',
    name: 'Flat',
    tones: {
      confirm: 'mwf-confirm',
      cancel: 'mwf-cancel',
      danger: 'mwf-danger',
      primary: 'mwf-primary',
      caution: 'mwf-caution',
      info: 'mwf-info',
      accent: 'mwf-accent',
      special: 'mwf-special',
      muted: 'mwf-muted',
    },
    disabled: 'mwf-disabled',
    cell: 'mwf-cell',
    cellInteractive: 'mwf-interactive',
    cellOff: 'mwf-off',
    focus: 'mwf-focus',
    label: 'mwf-label',
    header: 'mwf-header',
    surface: 'mwf-surface',
    frame: 'mwf-frame',
    tailColor: 'var(--mwf-outline)',
    backdrop: 'mwf-backdrop',
    input: 'mwf-input',
    enterAnimation: 'mwf-in 120ms ease-out forwards',
    cellEnterAnimation: 'mwf-cell-in 140ms ease-out both',
    // The same look as values, for menus drawn where this stylesheet is not
    // loaded — the browser extension's and the bookmarklet's, on other pages.
    // Stored with the choice; they apply it as CSS custom properties.
    portable: {
      tones: {
        confirm: '#10b981', cancel: '#94a3b8', danger: '#ef4444', primary: '#3b82f6',
        caution: '#f59e0b', info: '#0ea5e9', accent: '#6366f1', special: '#8b5cf6', muted: '#64748b',
      },
      surface: '#ffffff',
      outline: '#e2e8f0',
      ink: '#334155',
      radius: 14,
      cellRadius: 10,
      cellGap: 3,
      labelTransform: 'none',
      labelWeight: 600,
      labelSize: 10,
      backdrop: 'rgba(255,255,255,.6)',
    },
  };

  var registry = window.__mywantModules && window.__mywantModules['@/extensions/registry'];
  if (registry && registry.registerExtension) {
    // As an extension — the way anything added to the GUI is added.
    registry.registerExtension({ id: 'overlay-flat', overlayDesigns: [design] });
  } else if (window.__mywant && window.__mywant.registerOverlayDesign) {
    window.__mywant.registerOverlayDesign(design);
  }
})();

The portable block is the same look as values: the browser extension and the bookmarklet draw their menus with it on other people's pages, where overlay-flat.css is not loaded (see Designs).

3. The stylesheet ​

The classes the design names. The app's Tailwind build would not contain them, which is why a design from outside brings its own. Dark mode follows the app's dark class on <html>; the focus ring takes whoever holds the keys' colour from --mw-focus-ring:

css
/*
 * overlay-flat — the look behind the class names overlay-flat.js registers.
 *
 * Flat: a light card instead of a dark box, cells as separate rounded tiles
 * with a gap between them, solid colours, no transparency, no glow, words in
 * their own case. The app puts `dark` on <html> in dark mode.
 */
:root {
  --mwf-surface: #ffffff;
  --mwf-outline: #e2e8f0;
  --mwf-ink: #334155;
  --mwf-field: #f8fafc;
  --mwf-backdrop: rgba(255, 255, 255, 0.6);
}
.dark {
  --mwf-surface: #1e293b;
  --mwf-outline: #334155;
  --mwf-ink: #e2e8f0;
  --mwf-field: #0f172a;
  --mwf-backdrop: rgba(15, 23, 42, 0.6);
}

/* The box, and its surface for other shapes (a pill). */
.mwf-surface { background: var(--mwf-surface); border: 1px solid var(--mwf-outline); }
.mwf-frame {
  background: var(--mwf-surface);
  border: 1px solid var(--mwf-outline);
  border-radius: 14px;
  overflow: hidden;
  box-shadow: 0 6px 20px rgba(15, 23, 42, 0.12);
}
.mwf-backdrop { background: var(--mwf-backdrop); backdrop-filter: blur(2px); }

/* Cells: separate tiles with a gap, instead of one full-bleed block. */
.mwf-cell {
  margin: 3px;
  width: calc(100% - 6px) !important;
  height: calc(100% - 6px) !important;
  border-radius: 10px;
}
.mwf-interactive { transition: filter 120ms, transform 120ms; }
.mwf-interactive:hover { filter: brightness(1.07); }
.mwf-interactive:active { transform: scale(0.97); }
.mwf-off { opacity: 0.5; }
.mwf-disabled { background: var(--mwf-outline); opacity: 0.6; cursor: not-allowed; }

/* The ring's colour is whoever holds the keys (--mw-focus-ring). */
.mwf-focus { box-shadow: 0 0 0 3px var(--mw-focus-ring, #38bdf8); }

/* Tones — solid, one step lighter than the grid's. */
.mwf-confirm { background: #10b981; }
.mwf-cancel  { background: #94a3b8; }
.mwf-danger  { background: #ef4444; }
.mwf-primary { background: #3b82f6; }
.mwf-caution { background: #f59e0b; }
.mwf-info    { background: #0ea5e9; }
.mwf-accent  { background: #6366f1; }
.mwf-special { background: #8b5cf6; }
.mwf-muted   { background: #64748b; }

/* Words. */
.mwf-label { color: #fff; font-size: 10px; font-weight: 600; line-height: 1; }
.mwf-header {
  display: flex; align-items: center; gap: 6px;
  color: var(--mwf-ink); font-size: 12px; font-weight: 600;
}
.mwf-input {
  width: 100%;
  border-radius: 8px;
  background: var(--mwf-field);
  border: 1px solid var(--mwf-outline);
  padding: 4px 10px;
  font-size: 14px;
  color: var(--mwf-ink);
  outline: none;
}
.mwf-input:focus { border-color: #0ea5e9; }

/* Coming in. */
@keyframes mwf-in { from { opacity: 0; transform: translateY(4px); } to { opacity: 1; transform: none; } }
@keyframes mwf-cell-in { from { opacity: 0; transform: scale(0.94); } to { opacity: 1; transform: none; } }

4. Install it ​

sh
cp -R examples/overlay-flat ~/.mywant/gui-extensions/overlay-flat
# reload the GUI in the browser
curl -s localhost:8081/api/v1/gui-extensions   # lists overlay-flat

5. Pick it ​

Open Settings → Overlay Design, or your character's Display tab on the Characters page, and choose Flat. Every overlay changes at once — the card long-press menus, Shift+Enter, the yes/no questions and the board's bubbles — because every one of them is drawn from the overlay library.

The choice is stored on the character, at display.ext.overlay.design; with no character chosen in the browser, Settings keeps it in the browser instead.

Making it yours ​

  • Change only what you care about: fields you leave out of a design are taken from grid.
  • Keep the tones' meanings. A design decides what danger looks like, never which actions are dangerous.
  • Keep the cell's size. The layout is shared by every design; restyle the cell's shape inside it (margin, border-radius), as Flat does.