Overview
Playground’s preferences system goes beyond colors and typography to include background images, visual effects, and feature-specific settings that enhance your workspace experience.
Background Image Management
Users can personalize their workspace with custom background images loaded via file upload or URL.
Upload Methods
Upload an image file from your local system: procesarImagenFondo ( archivo ) {
// Validate file type
if ( ! archivo . type . startsWith ( 'image/' )) {
this . mostrarNotificacion ( 'Por favor seleccione un archivo de imagen válido' , 'error' );
return ;
}
// Check file size (max 5MB)
const tamañoMaximo = 5 * 1024 * 1024 ;
if ( archivo . size > tamañoMaximo ) {
this . mostrarNotificacion ( 'La imagen es demasiado grande. Máximo 5MB permitido.' , 'error' );
return ;
}
// Read and convert to base64
const reader = new FileReader ();
reader . onload = ( e ) => {
this . configuracionActual . imagenFondo = e . target . result ;
this . aplicarConfiguracion ();
this . mostrarPreviewImagenActual ();
};
reader . readAsDataURL ( archivo );
}
Images are stored as base64 in localStorage. File size limit is 5MB to prevent storage quota issues.
Load an image directly from a URL: cargarImagenDesdeURL ( url ) {
const img = new Image ();
img . onload = () => {
this . configuracionActual . imagenFondo = url ;
this . aplicarConfiguracion ();
this . mostrarPreviewImagenActual ();
this . mostrarNotificacion ( 'Imagen cargada desde URL correctamente' , 'exito' );
};
img . onerror = () => {
this . mostrarNotificacion (
'Error al cargar la imagen desde la URL. Verifica que sea válida y accesible.' ,
'error'
);
};
img . src = url ;
}
URL images are not stored as base64, saving localStorage space. The URL is stored directly.
Image Application
Background images are applied with CSS properties for optimal display:
if ( this . configuracionActual . imagenFondo ) {
document . body . style . backgroundImage = `url( ${ this . configuracionActual . imagenFondo } )` ;
document . body . style . backgroundSize = 'cover' ;
document . body . style . backgroundPosition = 'center' ;
document . body . style . backgroundRepeat = 'no-repeat' ;
document . body . style . backgroundAttachment = 'fixed' ;
}
Image Preview
A preview is displayed before saving:
mostrarPreviewImagenActual () {
const previewContainer = document . getElementById ( 'previewImagenFondo' );
if ( this . configuracionActual . imagenFondo ) {
const img = document . createElement ( 'img' );
img . src = this . configuracionActual . imagenFondo ;
img . style . maxWidth = '200px' ;
img . style . maxHeight = '150px' ;
img . style . objectFit = 'cover' ;
img . style . border = '2px solid var(--color-primario)' ;
img . style . borderRadius = '8px' ;
previewContainer . appendChild ( img );
}
}
Parallax Effect
The parallax effect creates a subtle 3D movement as the user moves their cursor, making the background feel more dynamic.
Enabling Parallax
When the parallax checkbox is enabled:
A special parallax container is created
The background image is moved to the parallax layer
Mouse movement listeners are attached
if ( this . configuracionActual . parallaxActivo ) {
// Create parallax structure
const parallaxContainer = document . createElement ( 'div' );
parallaxContainer . className = 'parallax-background' ;
const parallaxLayer = document . createElement ( 'div' );
parallaxLayer . className = 'parallax-layer' ;
parallaxLayer . style . backgroundImage = `url( ${ this . configuracionActual . imagenFondo } )` ;
parallaxContainer . appendChild ( parallaxLayer );
document . body . insertBefore ( parallaxContainer , document . body . firstChild );
// Activate parallax effect
this . activarEfectoParallax ();
}
Parallax CSS Styles
.parallax-background {
position : fixed ;
top : 0 ;
left : 0 ;
width : 100 % ;
height : 100 % ;
overflow : hidden ;
z-index : -1 ;
}
.parallax-layer {
position : absolute ;
top : 50 % ;
left : 50 % ;
transform : translate ( -50 % , -50 % );
width : 110 % ;
height : 110 % ;
background-size : cover ;
background-position : center ;
transition : transform 0.1 s ease-out ;
}
Mouse Movement Handler
activarEfectoParallax () {
this . parallaxListener = ( e ) => {
const parallaxLayer = document . querySelector ( '.parallax-layer' );
if ( ! parallaxLayer ) return ;
// Calculate mouse position as percentage
const mouseX = e . clientX / window . innerWidth ;
const mouseY = e . clientY / window . innerHeight ;
// Calculate movement offset (max 20px)
const moveX = ( mouseX - 0.5 ) * 20 ;
const moveY = ( mouseY - 0.5 ) * 20 ;
// Apply transform
parallaxLayer . style . transform =
`translate(calc(-50% + ${ moveX } px), calc(-50% + ${ moveY } px))` ;
};
document . addEventListener ( 'mousemove' , this . parallaxListener );
}
Movement is limited to ±20 pixels to create a subtle effect without being disorienting.
Blur Effect
The blur setting applies a backdrop filter to navigation and footer elements, creating a frosted glass effect.
Blur Range
Minimum : 0% (no blur)
Maximum : 100% (maximum blur)
Step : 1%
Blur Application
const blurPixels = this . configuracionActual . blurFondo / 10 ;
const opacity = this . configuracionActual . blurFondo / 100 ;
const nav = document . querySelector ( 'nav' );
const footer = document . querySelector ( 'footer' );
const header = document . querySelector ( 'header' );
if ( this . configuracionActual . imagenFondo ) {
if ( nav ) {
nav . style . backdropFilter = `blur( ${ blurPixels } px)` ;
nav . style . backgroundColor = `rgba(255, 255, 255, ${ opacity * 0.2 } )` ;
}
if ( footer ) {
footer . style . backdropFilter = `blur( ${ blurPixels } px)` ;
footer . style . backgroundColor = `rgba(255, 255, 255, ${ opacity * 0.2 } )` ;
}
if ( header ) {
header . style . backdropFilter = `blur( ${ blurPixels } px)` ;
header . style . backgroundColor = 'rgba(255, 255, 255, 0)' ;
}
}
Blur Calculation
The blur percentage (0-100) is divided by 10 to get pixel value (0-10px).
Opacity Calculation
The same percentage is divided by 100 to get opacity (0-1), then multiplied by 0.2 for subtle transparency.
Apply Backdrop Filter
CSS backdrop-filter: blur() creates the frosted glass effect.
Blur Slider UI
< div class = "slider-container" >
< label for = "blur" > Desenfoque del fondo </ label >
< div class = "slider-wrapper" >
< input type = "range" id = "blur" min = "0" max = "100" value = "0" step = "1" >
< span id = "blurValue" class = "slider-value" > 0% </ span >
</ div >
</ div >
The slider value updates in real-time:
inputBlur . addEventListener ( 'input' , () => {
this . configuracionActual . blurFondo = parseInt ( inputBlur . value );
blurValue . textContent = ` ${ inputBlur . value } %` ;
this . aplicarConfiguracion ();
});
Feature Preferences
Two toggle switches control application-specific features:
Vista Horarios Principal
When enabled, the schedule page displays in timeline view by default instead of table view.
const switchVistaHorarios = document . getElementById ( 'vistaHorariosPrincipal' );
switchVistaHorarios . addEventListener ( 'change' , () => {
this . configuracionActual . vistaHorariosPrincipal = switchVistaHorarios . checked ;
});
Stored as boolean in the preferences object under key vistaHorariosPrincipal.
Búsqueda en Plantillas
Enables deep content search within templates, allowing users to search inside template content rather than just titles.
const switchBusquedaPlantillas = document . getElementById ( 'busquedaPlantillas' );
switchBusquedaPlantillas . addEventListener ( 'change' , () => {
this . configuracionActual . busquedaPlantillas = switchBusquedaPlantillas . checked ;
});
Stored as boolean in the preferences object under key busquedaPlantillas.
Toggle Switch Component
The toggle switches use a custom CSS slider:
.switch {
position : relative ;
display : inline-block ;
width : 60 px ;
height : 34 px ;
}
.switch input {
opacity : 0 ;
width : 0 ;
height : 0 ;
}
.slider {
position : absolute ;
cursor : pointer ;
top : 0 ;
left : 0 ;
right : 0 ;
bottom : 0 ;
background-color : #ccc ;
transition : .4 s ;
border-radius : 34 px ;
}
.slider:before {
position : absolute ;
content : "" ;
height : 26 px ;
width : 26 px ;
left : 4 px ;
bottom : 4 px ;
background-color : white ;
transition : .4 s ;
border-radius : 50 % ;
}
input :checked + .slider {
background-color : var ( --color-secundario );
}
input :checked + .slider:before {
transform : translateX ( 26 px );
}
Removing Background Image
Users can delete their background image:
eliminarImagenFondo () {
const confirmacion = confirm ( '¿Está seguro que desea eliminar la imagen de fondo?' );
if ( ! confirmacion ) return ;
// Clear from configuration
this . configuracionActual . imagenFondo = null ;
this . aplicarConfiguracion ();
this . mostrarPreviewImagenActual ();
// Clear file input
const inputImagen = document . getElementById ( 'ImagenFondo' );
if ( inputImagen ) inputImagen . value = '' ;
// Update localStorage
const clave = this . generarClavePreferencias ();
const preferencias = JSON . parse ( localStorage . getItem ( clave ) || '{}' );
preferencias . imagenFondo = null ;
localStorage . setItem ( clave , JSON . stringify ( preferencias ));
this . mostrarNotificacion ( 'Imagen de fondo eliminada correctamente' , 'exito' );
}
Each saved configuration includes metadata:
const nuevasPreferencias = {
// ... all preference values ...
fechaGuardado: new Date (). toISOString (),
usuario: this . nombreUsuario
};
ISO 8601 timestamp of when preferences were last saved
Username who saved the preferences
Storage Quota Management
localStorage has a typical limit of 5-10MB per domain. Large base64 images can quickly consume this space.
Error Handling
try {
localStorage . setItem ( clave , JSON . stringify ( nuevasPreferencias ));
this . mostrarNotificacion ( 'Configuraciones guardadas correctamente' , 'exito' );
} catch ( error ) {
if ( error . name === 'QuotaExceededError' ) {
this . mostrarNotificacion (
'Error: No hay suficiente espacio de almacenamiento. La imagen es demasiado grande.' ,
'error'
);
} else {
this . mostrarNotificacion ( 'Error al guardar configuraciones' , 'error' );
}
}
Best Practices
Use URLs When possible, use image URLs instead of file uploads to save storage space.
Optimize Images Compress images before uploading to reduce file size while maintaining quality.
Monitor Size The 5MB limit helps prevent storage quota issues.
Export Regularly Export your complete configuration as backup in case of storage issues.