Yin’s Background Studio (as shown in the background of this website) began as a small WordPress theme enhancement for selecting one repeating background and evolved into a validated composition engine supporting static, random, weighted, persistent, reduced-motion, mosaic and scattered-collage modes. The project demonstrates how to extend a mature theme safely while preserving existing media, limiting animation costs, avoiding layout shift and making every production change recoverable.
The original problem
The website originally displayed one selected background at a time. An image, animated GIF, WebP or SVG could be positioned, sized and repeated across the page, while video files could occupy a separate full-viewport layer. The system already supported random selection, but every page still used only one media record.
The next objective was visually simple but architecturally important: display several different backgrounds together. Instead of choosing one GIF and repeating it indefinitely, the system should be able to choose two, three or another configurable number of distinct records and compose them across the viewport.
The desired result was not a stack of full-screen images hiding one another. Every selected background had to remain visibly represented. The existing single-background modes also had to remain available without behavioural regressions.
Confirmed technical environment
| Component | Confirmed version or state |
|---|---|
| Operating system | Debian 13 with Linux kernel 6.12.101 |
| Server class | Small VPS with approximately one virtual CPU and 1 GB of memory |
| PHP | PHP 8.4.24 with OPcache |
| WordPress | WordPress 7.0.4 |
| Theme | Penscratch 1.0.3, heavily customized |
| JavaScript validation | Node.js was initially unavailable and was later installed at version 20.19.2 |
| Persistent WordPress option | yin_background_studio |
The initial handoff contained six saved background records. They were sometimes described informally as six GIF backgrounds, but the exported data confirmed a mixed collection: SVG, GIF and WebP records, including two records that used the same WebP with different dimensions. This distinction mattered because animation, sizing and rendering costs differ by media type.
Requirements and non-negotiable constraints
- Preserve the original Static and Random modes.
- Add composition as an optional mode rather than replacing existing behaviour.
- Select distinct records without duplication unless repetition is explicitly part of the selected layout.
- Continue supporting equal and weighted selection.
- Preserve page-load, browser-session and daily persistence.
- Keep the reduced-motion fallback outside animated composition.
- Never automatically promote an animated GIF into the reduced-motion fallback.
- Preserve SVG, ordinary images, GIF, WebP and existing single-video support.
- Allow each record to be included in or excluded from mosaics.
- Keep decorative composition behind the website, non-interactive and hidden from assistive technology.
- Avoid cumulative layout shift by using fixed layers that do not participate in document flow.
- Avoid unnecessary server-side media processing.
- Limit simultaneous animation because the VPS and client devices have finite resources.
- Keep PHP validation authoritative for every saved setting.
- Preserve all six existing records and their individual settings.
- Do not modify credentials, unrelated theme functionality, posts, uploads or media files.
- Create a recoverable checkpoint before every production deployment.
The original Background Studio architecture
The baseline implementation was divided into a PHP module, two JavaScript files and CSS integration inside the active theme.
| Example path | Responsibility |
|---|---|
/var/www/example-site/wp-content/themes/penscratch/functions.php |
Loads the Background Studio module. |
inc/background-studio.php |
Defaults, option registration, sanitization, administration interface and front-end configuration. |
js/background-selector.js |
Selection, persistence, reduced-motion handling and rendering. |
js/background-studio-admin.js |
Media selection, cards, duplication, ordering, previews and dimension controls. |
css/background-studio-admin.css |
Administration interface styling and invalid-field feedback. |
style.css |
Public image and video background integration. |
The module was loaded defensively from functions.php:
<?php
$studio_file = get_template_directory()
. '/inc/background-studio.php';
if ( file_exists( $studio_file ) ) {
require_once $studio_file;
}
This kept the main theme bootstrap small and allowed the feature to be inspected or disabled independently.
The saved data model
The complete configuration lived in one structured WordPress option named yin_background_studio. Its global fields included:
| Field | Purpose |
|---|---|
enabled |
Enables or disables managed backgrounds. |
mode |
Originally static or random; later extended with Mosaic and Random + Mosaic. |
random_method |
Equal or weighted selection. |
persistence |
Page load, browser session or day. |
static_id |
The record used in Static mode. |
reduced_motion_id |
The explicitly selected reduced-motion fallback. |
items |
The ordered collection of background records. |
Each record stored an identifier, display name, enabled state, attachment identifier, URL, selection weight, fallback colour, size mode, width, height, repetition, horizontal and vertical position, attachment behaviour and page scope. Composition eligibility was subsequently added per record.
A simplified and anonymized representation looks like this:
{
"enabled": true,
"mode": "random",
"random_method": "equal",
"persistence": "page",
"static_id": "background_one",
"reduced_motion_id": "background_one",
"items": [
{
"id": "background_one",
"name": "Background One",
"enabled": true,
"attachment_id": 100,
"url": "https://example.com/wp-content/uploads/background.webp",
"weight": 1,
"color": "#000000",
"size_mode": "custom",
"width": "auto",
"height": "225px",
"repeat": "repeat",
"position_x": "right",
"position_y": "top",
"attachment": "scroll",
"scope": "all"
}
]
}
Server-side validation
The PHP module was the authority for saved settings. Enumerated values were restricted by allowlists, URLs were sanitized, colours passed through sanitize_hex_color(), attachment identifiers became non-negative integers and weights were constrained to a safe numeric range.
Duplicate record identifiers were made unique during normalization. If a saved Static or reduced-motion identifier no longer existed, the sanitizer selected a valid remaining record.
Manual dimensions needed stronger behaviour. The original sanitizer could replace malformed lengths with a fallback. This was changed so invalid manual dimensions produced an explicit WordPress settings error and preserved the previously valid values. Valid unitless numbers were normalized to pixels, so 256 became 256px.
<?php
function yin_background_studio_normalize_dimension( $value ) {
if ( ! is_scalar( $value ) ) {
return array( 'valid' => false, 'value' => '' );
}
$value = trim( wp_unslash( (string) $value ) );
if ( '' === $value || 'auto' === strtolower( $value ) ) {
return array( 'valid' => true, 'value' => 'auto' );
}
if ( preg_match( '/^(?:\d+(?:\.\d+)?|\.\d+)$/', $value ) ) {
if ( 0 === strpos( $value, '.' ) ) {
$value = '0' . $value;
}
return array(
'valid' => true,
'value' => $value . 'px',
);
}
if (
preg_match(
'/^(\d+(?:\.\d+)?|\.\d+)(px|%|em|rem|vw|vh|vmin|vmax)$/i',
$value,
$matches
)
) {
return array(
'valid' => true,
'value' => $matches[1] . strtolower( $matches[2] ),
);
}
return array( 'valid' => false, 'value' => $value );
}
The administration JavaScript provided immediate feedback, but it did not replace PHP validation. Focusing or editing a manual dimension activated Custom size mode, normalized valid values and marked invalid inputs using aria-invalid="true". PHP repeated the validation when WordPress saved the option.
How the original selector worked
Eligibility and page scope
PHP filtered records before sending data to the browser. A record had to be enabled, have a usable URL and match the current request scope. Supported scopes included the whole site, home page, posts, pages, singular content and archives.
The remaining sanitized records were passed to JavaScript through a localized configuration object. This prevented the browser from receiving irrelevant or disabled records.
Equal and weighted selection
Equal selection gave every eligible record the same chance. Weighted selection summed the positive weights, generated a random cursor and walked through the records until the cursor crossed zero.
The browser preferred crypto.getRandomValues() and fell back to Math.random() when necessary.
function chooseRandom(items, method) {
if (method !== 'weighted') {
return items[
Math.floor(randomUnit() * items.length)
];
}
var total = items.reduce(function (sum, item) {
return sum + Math.max(
0.01,
Number(item.weight) || 1
);
}, 0);
var cursor = randomUnit() * total;
var selected = items[items.length - 1];
items.some(function (item) {
cursor -= Math.max(
0.01,
Number(item.weight) || 1
);
if (cursor <= 0) {
selected = item;
return true;
}
return false;
});
return selected;
}
Persistence modes
| Mode | Behaviour |
|---|---|
| Page Load | A fresh choice is made whenever the page loads. |
| Session | The selected record is stored in sessionStorage and reused for the browser session. |
| Daily | The identifier and date are stored in localStorage and reused while the stored date matches. |
Storage operations were wrapped in try/catch blocks. If browser storage was blocked, selection continued without persistence rather than breaking the page.
The original daily implementation derived its date from toISOString(), so its day boundary followed UTC rather than the visitor’s local timezone. This remained a minor behavioural limitation worth documenting.
Reduced motion
The selector checked prefers-reduced-motion: reduce before Static or Random selection. If reduced motion was active, it used the explicitly configured fallback identifier or the first valid item.
var reducedMotion =
window.matchMedia &&
window.matchMedia(
'(prefers-reduced-motion: reduce)'
).matches;
if (reducedMotion) {
selected =
findById(config.reducedMotionId) ||
items[0];
} else if (config.mode === 'static') {
selected =
findById(config.staticId) ||
items[0];
} else {
selected = choosePersistentRandom();
}
The system did not automatically assign an animated GIF as the fallback. The administrator remained responsible for choosing a genuinely static record. A future improvement could warn when a GIF or video is manually selected as the reduced-motion item.
Rendering images and video
Images, SVG, GIF and WebP
Image-compatible media used CSS custom properties on the root element. The JavaScript selected the record and assigned its URL, colour, size, repetition, position and attachment values. The public stylesheet then consumed those values.
html.yin-background-studio-active {
background-color:
var(--yin-background-color, #000000) !important;
background-image:
var(--yin-background-image) !important;
background-size:
var(--yin-background-size, auto) !important;
background-repeat:
var(--yin-background-repeat, repeat) !important;
background-position:
var(--yin-background-position, left top) !important;
background-attachment:
var(--yin-background-attachment, scroll) !important;
}
html.yin-background-studio-active body {
background-color: transparent !important;
background-image: none !important;
}
This preserved native browser handling for ordinary images, SVG, animated GIF and WebP. No server-side tiling, rasterization or image recomposition was necessary.
Video
Video cannot be rendered through background-image, so the original system created a fixed decorative layer containing a real <video> element. It was muted, looping, inline, control-free and marked aria-hidden="true". Metadata rather than the entire video was requested during preload.
The layer used object-fit: cover or contain, while the main page received a higher stacking level. If loading or playback failed, the configured fallback colour remained visible.
Single-background video support was preserved throughout the work. The later scattered composition work was designed and tested primarily for images, GIF and WebP. Video composition was not the final target and should be treated as unverified until separately tested.
Comparing composition architectures
| Approach | Advantages | Problems | Decision |
|---|---|---|---|
| CSS multiple backgrounds | No extra DOM nodes; native image rendering; useful for explicitly positioned layers. | Several full-screen layers obscure lower layers. Per-layer layout, accessibility state and video handling become awkward. | Rejected as the primary mosaic architecture, but later reused selectively for bounded scattered repetitions. |
| Fixed DOM and CSS Grid mosaic | Every selected record receives a visible tile. Grid geometry, stacking and responsive layouts remain understandable. | More DOM elements and more simultaneous animation and painting. | Accepted for the original Two- and Three-background mosaic layouts. |
| Canvas | Complete procedural control over geometry and drawing. | Animated GIF and video handling become substantially more complicated. Canvas adds custom rendering work without solving the main problem better than the browser. | Rejected because it offered no meaningful advantage for this use case. |
The key architectural conclusion was that classic compositions should use a fixed decorative mosaic behind the content. CSS multiple-background layers could still be useful where every image was deliberately assigned an independent size and position rather than stretched across the complete viewport.
Extending the feature without destabilizing the original module
The earliest patch attempts tried to insert new controls and enqueue calls by locating exact text inside existing source files. This proved fragile because the expected labels or formatting did not match the live source precisely.
The safer solution was an isolated extension architecture. A small loader was added once, after which composition behaviour lived in separate PHP, JavaScript and CSS files.
| Extension file | Responsibility |
|---|---|
inc/background-mosaic-extension.php |
Mosaic settings, server integration and asset loading. |
js/background-mosaic.js |
Classic mosaic selection and rendering. |
js/background-mosaic-admin.js |
Mosaic administration controls. |
css/background-mosaic.css |
Fixed mosaic layout and stacking. |
js/background-random-collage.js |
Random collage geometry. |
inc/background-separate-panels-extension.php |
Separate-panel configuration and validation. |
js/background-separate-panels.js |
Scattered repetitions and balanced viewport distribution. |
inc/background-scatter-controls-extension.php |
Density and gap-colour controls. |
inc/background-collage-layout-mix-extension.php |
Server-authoritative selection among Classic One, Two, Three and scattered layouts. |
This extension approach reduced the number of edits to the already-working core module. It also made rollback easier because each candidate file could be validated outside the live theme and deployed only after all checks passed.
Classic Mosaic and Random + Mosaic
Two- and Three-background layouts
The first composition implementation provided deterministic classic layouts containing two or three distinct selected records. Each selected item occupied a visible region rather than being drawn as another full-screen overlay.
The mosaic container was fixed to the viewport, placed behind the website, removed from normal document flow and made non-interactive. Decorative markup received aria-hidden="true", and the foreground page retained the higher stacking context.
Because the container did not affect document dimensions, the mosaic itself did not introduce cumulative layout shift.
Distinct weighted selection
Composition retained the existing equal and weighted methods. To prevent duplicates, each selected item was removed from the candidate pool before the next draw. The following pseudocode expresses the rule without claiming to reproduce the final deployed file verbatim:
var selected = [];
var pool = eligibleItems.slice();
while (selected.length < targetCount && pool.length) {
var item = chooseUsingConfiguredMethod(pool);
selected.push(item);
pool = pool.filter(function (candidate) {
return candidate.id !== item.id;
});
}
If fewer eligible records existed than the requested count, selection was necessarily limited by the available pool. No synthetic duplicate was silently introduced.
Random + Mosaic
A separate Display Mode named Random + Mosaic was added. It selected between the original single-background renderer and the mosaic renderer with a 50/50 probability. This preserved the visual surprise of the original Random mode while periodically presenting a multi-image composition.
Its result continued to respect the selected persistence mode. Consequently, Session or Daily persistence could make repeated refreshes appear unchanged. Page Load persistence was therefore used during layout testing.
The evolution of Full Random Collage
First random geometry
The first Full Random Collage implementation selected between two and a configurable maximum number of eligible backgrounds. The maximum defaulted to three and could be set from two through six.
Although the random geometry worked, repeating media inside adjacent regions sometimes created one large visual block. Two different images could appear joined along a straight boundary, making the result resemble a conventional grid rather than a free collage.
Separate random panels
An optional Separate random panels control was introduced. Its first version displayed each selected image once as an independent floating region. That solved the joined-block problem, but exposed two new deficiencies:
- One copy per selected image could leave excessive empty space.
- The first version did not consistently reproduce the saved background dimensions expected by the existing records.
This was an important design correction: “separate” did not mean “show each image only once.” The desired behaviour was repeated but scattered imagery, with identical copies kept apart where practical.
Scattered repetitions
The renderer was revised to generate several copies of every selected image while inheriting its configured width and height. Copies were distributed across the viewport, and the placement routine discouraged adjacent instances of the same image.
The implementation used a bounded maximum of 21 visible CSS copies. This was a practical approximation of visually continuous repetition without creating an unlimited number of DOM elements or layers.
Some adjacency remained permissible because strict geometric separation could itself create unnatural gaps. The rule was therefore to avoid deliberately constructing a large continuous block of the same image, not to guarantee that no edges would ever touch.
Balancing viewport coverage
Pure random coordinates can cluster by chance. This explained screenshots containing large unoccupied regions even though the configured density had not changed.
The solution was balanced distribution: divide the usable viewport conceptually into regions, vary their order and place instances across those regions with randomized offsets. This retained randomness while reducing the probability that every copy clustered on one side.
Balanced distribution reduced empty space but did not promise complete coverage. Gaps remained part of the collage aesthetic and were subsequently treated as an explicit design surface.
Gap colour controls
A colour override was added specifically for Separate random panels. It filled the areas behind the scattered images without modifying the colours stored in individual background records or affecting the other modes.
The administrator could use a fixed colour or request a random gap colour. Random colour selection was made persistent according to the current persistence behaviour, preventing unnecessary colour changes during a Session or Daily selection.
Density controls
The final administration interface offered Light, Balanced and Dense image density. Density changed the number of visible repetitions, while the global safety cap prevented unbounded rendering.
Dense should be used cautiously with animated GIFs. Reusing the same URL usually avoids downloading the same file independently for every copy, but the browser still has to composite and paint multiple animated regions.
Bringing every earlier layout into Full Random Collage
The Full Random Collage mode eventually became a layout family rather than one renderer. The administrator could independently include:
- Classic One-background repeat;
- Classic Two-background layout;
- Classic Three-background layout;
- the scattered Full Random Collage.
Classic One reproduced the original behaviour: choose one eligible record and repeat it according to its saved settings. Classic Two and Classic Three used the original fixed mosaic arrangements. The scattered collage remained another possible outcome.
Independent checkboxes were necessary. An earlier combined “Include classic Two/Three layouts” option made it difficult to test and reason about the two branches separately.
The Three-background branch also required the configured maximum to be at least three and at least three eligible records to be available.
Why the layout selector moved to PHP
After the combined option was split into separate Two and Three checkboxes, testing revealed that disabling Two and enabling only Three could still produce a Two-style result.
The saved settings were correct: Mosaic mode was active, the method was Full Random Collage, the maximum was three, Two was disabled and Three was enabled. This established that the administration form was not the problem.
The correction made PHP authoritative for the layout choice. It also introduced a new persistence version and disabled the obsolete browser-side layout selector. Tests then confirmed that:
- Three-only choices excluded Classic Two;
- Three-only choices included Classic Three;
- Classic Two produced exactly two selected records;
- Classic Three produced exactly three selected records.
This change illustrates a useful rule: when several scripts can independently decide the same state, stale persistence and duplicated selection logic become difficult to debug. One authoritative selector is safer.
The final administration model
| Control | Final purpose |
|---|---|
| Background Studio | Enable or disable the managed system. |
| Display Mode | Static, Random, Mosaic or Random + Mosaic. |
| Random Method | Equal probability or per-record weight. |
| Persistence | Page Load, Session or Daily. |
| Mosaic backgrounds | Two different backgrounds, Three different backgrounds or Full Random Collage. |
| Maximum backgrounds | Two through six, with three as the conservative default. |
| Separate random panels | Use scattered repeated images instead of intentionally joined partitions. |
| Image density | Light, Balanced or Dense. |
| Gap colour override | Fill gaps behind scattered images without altering individual records. |
| Fixed or random colour | Choose a stable colour or persistent random colour for the gaps. |
| Include Classic One | Allow the original one-background repeat inside Full Random Collage. |
| Include Classic Two | Allow the original two-background mosaic. |
| Include Classic Three | Allow the original three-background mosaic when enough records are available. |
| Per-record mosaic eligibility | Exclude unsuitable media without disabling it from every other mode. |
A safe production procedure
Every iteration followed the same operational principle: inspect first, build outside the live theme, validate completely, deploy once and restore automatically if a post-deployment test failed.
1. Verify the live state
- Confirm every expected source file exists.
- Run PHP syntax checks on the module, extensions and theme bootstrap.
- Record current source checksums.
- Read and count the saved background records.
- Record all existing identifiers.
- Confirm WordPress can bootstrap before making changes.
2. Create a timestamped checkpoint
The checkpoint contained the original files, candidates, validation helpers and a rollback script. Candidate construction occurred outside /var/www/example-site.
3. Build and validate candidates
The following is a condensed, anonymized validation pattern reflecting the safeguards used. It is not the historical installer verbatim.
set -euo pipefail
WP_ROOT="/var/www/example-site"
THEME_DIR="$WP_ROOT/wp-content/themes/penscratch"
STAMP="$(date -u +%Y%m%dT%H%M%SZ)"
CHECKPOINT="/srv/checkpoints/background-studio-$STAMP"
CANDIDATES="$CHECKPOINT/candidates"
mkdir -p "$CANDIDATES"
php -l "$THEME_DIR/inc/background-studio.php"
php -l "$THEME_DIR/functions.php"
wp --path="$WP_ROOT" option get \
yin_background_studio \
--format=json \
> "$CHECKPOINT/settings-before.json"
BEFORE_COUNT="$(
jq '.items | length' \
"$CHECKPOINT/settings-before.json"
)"
echo "Saved backgrounds before deployment: $BEFORE_COUNT"
node --check "$CANDIDATES/background-mosaic.js"
node --check "$CANDIDATES/background-mosaic-admin.js"
sha256sum \
"$THEME_DIR/inc/background-studio.php" \
"$THEME_DIR/js/background-selector.js" \
> "$CHECKPOINT/checksums-before.txt"
4. Deploy only after all checks pass
Validated candidates were copied into the live theme only after PHP, JavaScript and CSS structure checks succeeded. Immediately afterward, the procedure repeated syntax checks, loaded WordPress and tested the public page and relevant assets.
php -l \
"$THEME_DIR/inc/background-mosaic-extension.php"
wp --path="$WP_ROOT" eval \
'echo "WordPress bootstrap: PASS\n";'
curl --fail --silent --show-error \
"https://example.com/" \
> /dev/null
curl --fail --silent --show-error \
"https://example.com/wp-content/themes/penscratch/js/background-mosaic.js" \
> /dev/null
5. Confirm data preservation
The option was read again after deployment. The count and identifiers had to match their pre-deployment values. Every successful installation reported that all six background records remained present and unchanged.
6. Provide rollback instructions
Each successful installer printed the checkpoint location and one rollback command. A failed post-deployment test restored the live theme automatically rather than leaving a partially installed feature.
Important failures and what they revealed
| Observed failure | What it established | Correction |
|---|---|---|
Could not find Random method row |
The installer depended on an exact administration markup pattern that did not match the live source. | The operation stopped before deployment. Later work used isolated extensions. |
Existing frontend enqueue call not found |
A second text-insertion assumption was also too brittle. | A small extension loader replaced repeated surgery on existing functions. |
Expected one administration visibility function; found 0 |
The random-collage installer assumed an administration helper that was not present in the expected form. | The collage controls were implemented as an independent extension. |
Node.js is required for JavaScript validation |
The VPS initially lacked a real JavaScript parser. | Deployment stopped safely. Node.js 20.19.2 was subsequently installed and used with syntax validation. |
| PHP-based structural checks passed, but the installer still stopped | Delimiter counting and marker checks are useful but are not equivalent to JavaScript parsing. | The candidates were revalidated with Node.js before deployment. |
Public validation failed because WP-CLI rejected a format value |
The production code had already passed; the error was in the validation command. | The automatic rollback restored the theme. The URL retrieval and public checks were corrected before redeployment. |
Mosaic configuration object was not recognized |
The patch assumed the localized JavaScript object had a particular variable name. | The corrected patch detected the actual object before adding layout switches. |
| Three-only settings still appeared to produce Two | The saved checkboxes were correct, so duplicated browser-side selection or persistence remained involved. | Layout choice moved to the server, the persistence version changed and the obsolete client selector was disabled. |
| A candidate function was unavailable during a pre-deployment test | The test attempted to call code that WordPress had not yet loaded. | The operation stopped. Candidates were revalidated, deployed and then tested inside the loaded WordPress bootstrap. |
A failed installer that changes nothing is a successful safety mechanism. Several attempts ended with exit status 1, but the live theme and all saved records remained intact.
Validation results
The completed implementation passed the following checks:
- PHP syntax validation for the core module, all extension modules and
functions.php. - Node.js syntax validation for public and administration JavaScript.
- Structural CSS validation.
- WordPress bootstrap after deployment.
- Public page response validation.
- Direct requests for every newly deployed asset.
- Server-side option validation for the new controls.
- Equal and weighted selection paths.
- Exact Two- and Three-background selection branches.
- Random + Mosaic selection.
- Scattered repetition, configured size inheritance and same-image separation.
- Balanced viewport distribution.
- Fixed and persistent random gap colours.
- Independent Classic One, Two and Three checkboxes.
- Preservation of all six original records and identifiers after every successful deployment.
Final manual testing in Mosaic mode confirmed that Classic One, Classic Two, Classic Three and the scattered random layout all appeared as intended. The completed state was reported as working successfully.
Performance and accessibility considerations
Animated media cost
The conservative recommendation remained two or three simultaneously selected animated records. Although the administration interface allowed a maximum of six, that maximum should not be interpreted as a performance recommendation.
The Dense scattered mode could generate as many as 21 visible CSS copies. This was more appropriate for lightweight static WebP or SVG media than for several large animated GIFs.
The practical cost is paid mainly by the visitor’s browser through decoding, compositing and repainting. Battery-powered devices and integrated graphics can therefore experience a greater effect than the VPS itself.
No server-side image composition
The server did not generate mosaic bitmaps, contact sheets or resized derivatives for this feature. It delivered the original media and configuration; the browser performed the composition. This avoided additional PHP memory pressure and permanent derivative files.
Stacking and interaction
Composition layers remained fixed behind the foreground page, used pointer-events: none and were decorative. They did not intercept links, text selection, scrolling or keyboard interaction.
Decorative containers used aria-hidden="true". Because they did not enter document flow, they did not reserve space or push content after loading.
Colour and readability
The gap-colour override solved visual emptiness but introduced a design responsibility. A random colour may interact unpredictably with translucent foreground panels. Sites using this option should ensure that foreground text and content blocks establish their own reliable contrast.
Remaining limitations and future improvements
- The 21-copy scattered renderer is a bounded visual approximation, not mathematically infinite repetition.
- Purely random layouts can still produce some gaps or adjacency; balanced distribution reduces but cannot eliminate randomness.
- Reduced-motion safety depends on the administrator choosing a nonanimated fallback. The panel could add a warning for GIF and video extensions.
- Video remains confirmed in single-background mode but requires dedicated testing before being recommended inside collages.
- Daily persistence currently follows a UTC date boundary.
- Server-side layout persistence should be reviewed if aggressive full-page caching is introduced, because a cached response could unintentionally share one server-selected result.
- The extension-based approach was ideal for safe incremental deployment, but the accumulated modules could eventually be consolidated after a new complete source handoff and regression suite are created.
- Automated browser tests could verify tile count, distinct identifiers, stacking, reduced motion and layout choice at several viewport sizes.
- A development-only debug mode could expose the chosen layout and record identifiers without affecting ordinary visitors.
- Performance telemetry could help choose safer density limits for animated GIFs on mobile devices.
The original source handoff predates the final extension files. The successful installer logs confirm their deployment and validation, but any future development should begin by exporting the complete current live source rather than reconstructing the final code from historical patch commands.
Practical lessons
- Preserve the working mode first. Composition was added alongside Static and Random rather than replacing them.
- Model selection separately from rendering. Equal weighting, weighted choice, persistence and distinctness should not be entangled with grid geometry.
- Use the correct rendering primitive. Grid suited classic partitions; controlled CSS layers suited scattered repetitions; Canvas provided no useful advantage.
- Make one layer authoritative. Moving final layout choice to PHP eliminated contradictory client-side decisions.
- Treat random placement as a distribution problem. Uniform coordinates can cluster; balanced regions produce more useful visual randomness.
- Do not confuse repetition with duplication. Selection remained distinct, while the renderer could deliberately repeat each selected visual.
- Keep validation independent of the interface. JavaScript improved usability, but PHP decided what could be saved.
- Validate the validator. Several failures came from installer assumptions or test commands rather than production code.
- Build outside production. Candidates were validated before touching the live theme.
- Count persistent records before and after every change. All six backgrounds survived every successful iteration.
- Keep rollback automatic. A failed public test restored the previous live files immediately.
- Use conservative animation defaults. Two or three animated records can create a strong composition without treating six as the normal operating point.
Conclusion
Background Studio evolved successfully because the work treated the problem as more than a visual effect. It combined a structured WordPress option, authoritative validation, weighted and persistent selection, accessible decorative rendering, controlled random geometry and recoverable deployment.
The final system can still repeat one classic background, but it can also select distinct media for two- and three-part mosaics, alternate between Random and Mosaic, generate scattered repetitions, control density and gap colour, and mix the original One-, Two- and Three-background layouts inside Full Random Collage.
Most importantly, the feature reached this point without deleting or rewriting the existing media library, without replacing the original single-background behaviour and without leaving failed experimental patches in production.
