Customisation

Screens and colours are prepared in the console, then attached to the widget by a single identifier.

You do not describe the widget’s appearance in your code. You prepare a customisation in the ops console, and you attach it to the widget by its identifier.

That separation is the point. The people who decide how the widget looks are rarely the people who deploy the page, and a change of palette should not need a release.

1. Create a customisation in the console

In the ops console, the customisation workshop lets you prepare and preview what the widget will show. For a v2 customisation it covers nearly the whole surface, screen by screen:

What you can changeWhere
Which screens the journey includesEach screen switches on or off
The content of a screen, element by elementA tree you edit node by node, in all ten languages
Which consent checkboxes the screen asks forFour of them, each on or off; the first two are shown out of the box, the other two are yours to write
The frame around the screens — toolbar, menu panel, the surface under itTrees, edited like a screen’s content
Each button, its label and its appearanceA small tree per language, edited like a screen’s content
Menu entries, form wordingOne field per language, with a copy-to-all
Which entries the menu showsThe three documentary entries switch on or off; the language choice always stays
The tooltip that points at the menu buttonIts content, its dismiss link, and which guidance screen shows it
Colours, spacing, sizes, typographic rolesOn any node, and on each button separately, per language
The personal-information formEach field on or off, required or not, with its bounds, and the unit system offered first — including a free-text identifier of your own (externalId), off by default and returned in the result’s userData
How the measurement startsOn its own once the face has held still, or on a button; and how long it must hold still
The result screen’s coloursOne pair per measured value
Measured values, tags, opening languageTogether, as measurement settings

A screen’s content is a tree of elements — containers, paragraphs, text, images, links. You add a child, move it, duplicate it, change its type, and set what it carries. The workshop only offers what the widget accepts at that place, so a tree built there is a tree the widget will render.

Three things stay as the widget ships them: the three documents of the menu — instructions for use, terms of use, privacy policy — which are the manufacturer’s own text; the names of the languages in the language panel, each written in its own language; and the short status line under the frame on the placement screen.

A live widget sits beside the settings, on the very screen you are editing — every screen, including the ones no setting reaches: the result, the two abnormal ends and the five camera refusals are shown with fabricated data, so their appearance is judged without provoking them. What you validate is what your users will see, and the widget refuses to start on a tree it cannot render, which is how a mistake shows up before you save. The same workshop serves both widget versions; the v1 page shows the v1 one in pictures.

Beside that screen-by-screen view, the preview also shows the whole widget, as an integrator mounts it: the controls work, the journey runs from end to end, and a measurement can be taken there. It is what you reach for to judge a sequence rather than a screen — the price being that every change reloads the widget, so the journey starts again from the beginning.

A customisation belongs to your account, carries a name and a description, and is tied to a widget version. Several can coexist: one per product, per brand, per campaign.

2. Attach it to the widget

Add its identifier to the address you load the widget from.

<script type="module">
    import SaphereScan from "https://cdn.saphere.ai/saphere-scan/v2/main.js?customizationId=8f1c2d34-5a6b-4c7d-8e9f-0a1b2c3d4e5f"
</script>

Nothing else changes in your integration: same call, same options, same events. The widget is served already carrying the customisation.

An identifier that designates no v2 customisation on that environment — one prepared on the other environment, one prepared for the v1 widget, a typo — is not an error: the widget is served with its own defaults, silently. A value that is not an identifier at all makes the address answer an error, and the import fails. To see what a given address carries, import from it and call describeDefaults().

It behaves like a partial option payload, sitting between the widget’s defaults and yours: what it does not say stays at the default, and what you pass to create() or setOptions() wins over it. That is what lets a customisation decide the appearance while your code keeps the last word on whatever depends on the user.

One address, two decisions

The address you load from already decides the environment — cdn.test.saphere.ai reaches Test, cdn.saphere.ai reaches Production. It now also decides the appearance. A customisation prepared on Test is not the same record as one prepared on Production, so its identifier does not carry across. See Environments.

What stays in your code

Some settings belong to the integration rather than to the appearance, so they stay in the options you pass to create(). Your options are applied last: wherever they and the customisation both say something, yours win.

OptionWhy it stays with you
langThe language depends on your user. Without one, the widget follows the browser’s language — unless the customisation sets an opening language, which then applies to everyone
proxy.retrieveAccessTokenIt names your own endpoint
createMeasureOptionsIt carries what you know about the person being measured
onEvent, hooksThey are your code

Changing a customisation

Edit it in the workshop. The widget picks up the new version the next time it is loaded, with no deployment on your side, because the identifier in your page does not change.

This is also why a customisation should be reviewed before it is saved: it takes effect for everyone using that identifier.

Starting from what the widget ships

The workshop starts from the widget’s own default contents rather than from a blank page, and it keeps only what you changed: anything you leave alone follows the widget as it evolves, anything you edit stays as you left it.

If you build your own tooling, the same starting point is available from the module:

import SaphereScan from "https://cdn.saphere.ai/saphere-scan/v2/main.js"

// what the widget would apply if you passed nothing: the whole option tree, as JSON
const defaults = SaphereScan.describeDefaults()

// what it would apply for the options you are considering, without mounting anything
const resolved = SaphereScan.describeDefaults({ components: { onboarding: { onboarding3: { ignore: false } } } })

It resolves exactly as create() does, customisation included: loaded from an address carrying a customizationId, the module describes what this bundle would apply rather than the bare defaults. It throws on options the widget would refuse, which makes it a way to check a payload before you ship it. Two things it does not return: anything that is a function — onEvent, the hooks, the fetch of a handle strategy, a createMeasureOptions written as a function — and the drawings the widget ships, which are code rather than data.

Availability

The workshop and the customizationId parameter are being rolled out with the v2 widget, which is in beta. Ask i-Virtual where they stand for your account before you build a release around them.