There are two layers of customization for a self-hosted instance:
- Project configuration in
uvproj.yaml(andTranslations/ui18n-config.yaml) — branding, canonical URL and the external service URLs. These are substituted into the source at build time by the UVKBuildTool, so they apply to both the HTML templates and the JS. - Runtime tuning constants scattered across a few JS
files in
CONSTANT BLOCKcomment fences — grading, animation timings, writer sizing and the character-database download behaviour.
Project configuration
(uvproj.yaml)
The variables: section of uvproj.yaml is
the single source of truth for branding and URLs. Each
var/val pair is substituted wherever the
template token appears in any processed
file.
| Variable | Default | What it controls |
|---|---|---|
trademark |
Youyin |
The product/brand name. Substituted via
in the HTML chrome (page titles, Open Graph/Twitter meta) and via the
{brand} placeholder inside translation strings. |
cannonical_url |
https://youyin.madladsquad.com |
The deployed site's base URL. CI rewrites https://madladsquad.com/ links to
this and it feeds the canonical/OG/sitemap URLs. |
marketplace_url |
https://github.com/MadLadSquad/YouyinPublicDeckRepository |
Human-facing link to the public deck repository. |
marketplace_cdn_url |
https://cdn.jsdelivr.net/gh/MadLadSquad/YouyinPublicDeckRepository@latest |
jsDelivr base the marketplace page fetches deck lists/decks from
(MARKETPLACE_CDN in marketplace.js, also
pre-cached by sw.js). |
char_data_url |
https://cdn.jsdelivr.net/gh/MadLadSquad/hanzi-writer-data-youyin@latest |
jsDelivr base for the character stroke database. Builds
CHARACTER_MANIFEST_URL and
CHARACTER_CHUNK_URL_BASE in
character-database.js. |
additional_cdn_hosts |
none |
Extra hosts to add to the service worker's pre-cache list
(sw.js). The literal string none disables the
block. |
Renaming the site
Because the brand appears in two different substitution engines, renaming requires editing two files:
uvproj.yaml→trademark— used by the build'stoken (HTML chrome, meta tags).Translations/ui18n-config.yaml→brand— the literal name the translation engine substitutes for the{brand}placeholder inside localized strings. It must be a plain literal (the i18n engine is single-pass, so a nestedtoken would not resolve).
Never hardcode the name inside a translation string or template —
always write {brand} / so a
rename stays a two-line change. Other {name}-style
placeholders (e.g. {streak}) are filled at runtime by JS,
not at build time.
Runtime tuning constants
These live inside
// --- CONSTANT BLOCK EDIT IF RUNNING ON A CUSTOM SYSTEM ---
fences. They are no longer all in one file — each block sits next to the
code that uses it.
scripts/index.js — core
window.MAX_KNOWLEDGE_LEVEL = 4;
window.HOUR_UNIX = 3600000; // ms in an hour
window.MINUTE_UNIX = 60000;
window.SECOND_UNIX = 1000;scripts/pages/main-page.js
— grading, session size & writer timing
window.MAX_POINTS_ON_CHARACTER = 0.05;
window.ADD_POINTS_ON_ERROR_3_4 = 0.0375; // 3/4 of 0.05
window.ADD_POINTS_ON_ERROR_1_2 = 0.025; // 1/2 of 0.05
window.ADD_POINTS_ON_ERROR_1_4 = 0.0125; // 1/4 of 0.05
// Hard cap on cards (and, separately, phrases) revised per play session. Larger
// decks are shuffled and only the first entries are drawn, so each session is a
// random subset of at most this many cards and this many phrases.
window.MAX_SESSION_REVISION_ITEMS = 8;
window.WRITER_SLEEP_AFTER_COMPLETE = 1200; // ms to admire a finished character
window.WRITER_FLY_TO_COUNTER_DURATION = 650; // ms of the "fly into the counter" animation
window.WRITER_SHOW_HINT_ON_ERRORS = 3; // misses before a hint at normal levels
window.WRITER_SHOW_HINT_ON_ERRORS_LVL_3 = 1; // misses before a hint at the advanced levelscripts/components/writer.js
— writer sizing
window.CARD_WRITER_SIZE = 100; // single-character widget
window.PHRASE_CARD_WRITER_SIZE = 50; // per-character widget inside a phrase card
window.CARD_WRITER_STROKE_ANIMATION_SPEED = 1.25;
window.CARD_WRITER_DELAY_BETWEEN_STROKES = 50;
window.WRITER_PADDING = 5;The writer colours (WRITER_RADICAL_COLOUR,
WRITER_STROKE_COLOUR, WRITER_OUTLINE_COLOUR)
are not constants here — they are owned by
scripts/data/theme.js and set from the active theme's
accent/stroke/outline colours.
scripts/pages/deck-new.js
— card-editor preview defaults
window.CARD_DEFAULT_CHARACTER = "是";
window.CARD_DEFAULT_PREVIEW_NAME = "Preview Name";scripts/data/character-database.js
— stroke-database download
window.CHARACTER_MANIFEST_URL = "/character-map-chunks.json";
window.CHARACTER_CHUNK_URL_BASE = "/character-map-chunks/character-map-full-";
window.CHARACTER_CHUNK_BATCH_SIZE = 5; // chunks fetched per batch
window.CHARACTER_CHUNK_COOLDOWN_MS = 300; // pause between batches
window.CHARACTER_FETCH_RETRIES = 4; // retries per manifest/chunk fetch
window.CHARACTER_FETCH_RETRY_DELAY_MS = 600;The two URLs are derived from the char_data_url build
variable — to point at a different stroke dataset, change
char_data_url in uvproj.yaml rather than
editing these lines.
A note on the grading constants
Messing with the grading settings may lead to logical breakages in
the application. Setting MAX_KNOWLEDGE_LEVEL above 4 will
simply raise the upper points limit without changing functionality;
lowering it disables the advanced levels.
- Home
- Contributor content
- Localization
- Adding additional writing systems
- Developer content