A sandbox for designing Live Helper Chat (LHC) widget themes. You author a theme as one JSON file, preview it in a browser against snapshots of the real widget markup, then import that same file into LHC.
There is no build step: the preview renders straight from the theme file, so an edit plus a reload is the whole loop, and there is no generated stylesheet that could drift from what the widget actually emits.
- https://youtu.be/JHgZ3wOUVho
- Theme created Professional Blue
- Sample themes to ask ai to create :) https://qu.lt/widget-themes/index.html
| Path | What it is |
|---|---|
themes/<slug>.json |
The theme — the deliverable, and the only home of its CSS (custom_widget_css). |
themes/sample-reference.json |
One worked example of a complete, importable theme (structure only — its colours belong to its own brand). |
theme.php |
The preview CSS emitter: renders a theme file into the stylesheet the widget would load (builder colours → message-style block → custom_widget_css). |
index.html, *.html |
The preview fixtures — dummy-rendered widget snapshots (start chat, start chat with fields, active chat, mobile, new-message). |
css/core.css, css/core-mobile.css |
Snapshots of the real widget stylesheet (Bootstrap + widget rules). Do not edit. |
css/core-override-reference.css |
The last known-good theme, kept as a reference for how a rule has to be written to win inside the widget. Never imported. |
css/page-css.css |
Host-page shell CSS, mirrored into the theme's bot_configuration.custom_page_css. |
font/ |
The Material Icons font the widget uses for its icons. |
.github/copilot-instructions.md |
The full playbook: key reference, cascade rules, DOM hooks and a catalogue of hard-won pitfalls. |
| Scope | Lives in | Styles |
|---|---|---|
| Widget body (inside the iframe) | custom_widget_css |
header, messages, composer, forms — everything visible in the widget |
| Host page | bot_configuration.custom_page_css (mirrored by css/page-css.css) |
the outer iframe shell: radius, shadow, responsive rules |
| Iframe element, inline | custom_container_css |
declarations only (it is appended to the iframe's style attribute, so selectors are silently discarded) |
Each theme is one importable file (themes/<slug>.json) plus a folder of the same name holding a
short README.md (palette, metrics, switches) and a screenshots/ set captured from the fixtures.
All nine themes are bubble-style, restyle the header bar, the bubbles and the message-send
area, are validated across the full bubble fixture set, and use local system fonts only — no
webfonts, no @import. Each one repaints the bar, reshapes its two controls (always keeping the
bar full-width — nothing is absolutely positioned) and turns the composer into a themed pill or
bar with its own send button. They differ in palette, radius, and how soft or flat the surfaces
are: Minimal, for instance, deliberately uses a flat bordered bar instead of a pill.
| Theme | File | Folder | Look |
|---|---|---|---|
| Evergreen | themes/evergreen.json |
themes/evergreen/ |
Emerald gradient bar, mint canvas, white/emerald bubbles, 26px composer pill |
| Modern Light | themes/modern-light.json |
themes/modern-light/ |
White surfaces, blue accents, 24px pill, circular gradient send |
| Dark Professional | themes/dark-professional.json |
themes/dark-professional/ |
Near-black canvas, blue accents, 14px pill, rounded-square send |
| Minimal | themes/minimal.json |
themes/minimal/ |
Near-white, soft green, no shadows — a flat bar composer instead of a pill |
| Premium Gold | themes/premium-gold.json |
themes/premium-gold/ |
Near-black + gold accents, gold bubbles with dark text, serif type, gold-trimmed pill |
| Vibrant / Brand | themes/vibrant-brand.json |
themes/vibrant-brand/ |
Purple → pink → cyan gradient, 999px full-pill composer, 38px send |
| Midnight Neon | themes/midnight-neon.json |
themes/midnight-neon/ |
Dark navy, violet→magenta bubbles, neon cyan accents |
| Citrus Sunrise | themes/citrus-sunrise.json |
themes/citrus-sunrise/ |
Cream canvas, sunny amber header, crimson bubbles, warm and soft |
| Slate Mint | themes/slate-mint.json |
themes/slate-mint/ |
Flat deep-teal header, crisp hairlines, squared corners, fintech |
themes/sample-reference.json is the worked example every theme is structured after (keys and
scopes only — its colours belong to its own brand), and themes/professional-blue.json is the
original demo theme. Neither has a screenshot folder.
Every folder above contains the same four captures, named after the fixture they come from:
01-started-chat-bubble-desktop.png (350×450), 02-started-chat-bubble-mobile.png (390×780 phone
viewport), 03-start-chat-bubble.png and 04-start-chat-fields.png (both 350×450). In the chat
shots the two rating thumbs are a fixture artefact — the themes set show_voting: 0, so the real
widget hides them.
theme.php is PHP, and every fixture links it, so the previews only work when a PHP-capable web
server serves this folder. Opened over file:// the browser receives the PHP source instead of
CSS and every theme rule appears to be missing.
php -S localhost:8080 # or drop the folder into any LAMP / nginx + php-fpm hostThen browse to the served root, e.g. http://localhost:8080/index.html.
theme.php starts with:
$defaultTheme = 'evergreen';The fixtures link plain theme.php with no query string, so this one assignment decides which
themes/<slug>.json the previews render — change it to the slug you are working on. You can also
request the generated stylesheet for a specific theme directly with
theme.php?theme=<slug>, which is handy when you want to read exactly what the widget would load.
(It cannot switch the fixtures themselves: their <link> carries no query string.)
The theme JSON is the source of truth — there is no companion stylesheet to keep in sync. Author
the CSS as plain CSS and let JSON.stringify() do the escaping, re-embedding after every edit:
node -e "const fs=require('fs'),p='themes/<slug>.json',t=JSON.parse(fs.readFileSync(p));t.custom_widget_css=fs.readFileSync('/tmp/theme.css','utf8');fs.writeFileSync(p,JSON.stringify(t,null,2)+'\n')"Reading the CSS back out again (handy before a diff):
node -e "process.stdout.write(require('./themes/<slug>.json').custom_widget_css)"Three rules that are easy to get wrong:
- Top-level colour keys (
text_color,buble_operator_background, …) are hex without#("2563eb"), because the templates print#<value>. Insidecustom_*_cssuse normal#rrggbb. bot_configurationandnotification_configurationare JSON strings — serialised JSON inside the JSON file, i.e. double-encoded.custom_widget_cssis a layer over the factory widget, inserted beforecore.css, so anything it must win needs!important— but not the propertiescore.cssdeliberately owns (message grouping is the classic trap).
Open index.html and work through the group that matches the theme's message style:
| Message style | bot_configuration |
Fixtures |
|---|---|---|
| Bubble | bubble_style_profile: 1 |
started-chat-bubble.html, started-chat-bubble-mobile.html, start-chat-bubble.html |
| Normal | bubble_style_profile: 0 |
started-chat-default.html, started-chat-new-message.html, start-chat-ui.html |
start-chat-fields.html is checked for both styles: it carries no message markup, and it is the
only fixture that shows the start form's inputs, labels and submit button.
A theme is not finished until the same surfaces look the same across its whole set — desktop vs mobile, chat vs start/offline, and the form state — including hover / focus / disabled states of every button.
System configuration → Themes → Import theme (/siteadmin/theme/import) accepts
themes/<slug>.json as-is.
The documentation refers to the sandbox's absolute base URL through a single variable,
PREVIEW_BASE_URL, which is defined in exactly one place —
.github/copilot-instructions.md → §0. Nothing in this README hardcodes a host, and no theme file
does either:
Change that one line after cloning the sandbox onto your own host, and every URL in the docs resolves.
.github/copilot-instructions.md is both the human-readable playbook and the operating document
that GitHub Copilot reads automatically in this folder. It covers the theme-file key reference, the
stylesheets' cascade order, the widget's DOM/CSS hooks, the fixture workflow, and a catalogue of
pitfalls that are expensive to rediscover (message grouping, device-suffixed classes, glyph
alignment, contrast pairs, the start-form field guards).