Skip to content

Motor de Colores Dinámicos ​

El Motor de Colores Dinámicos permite que las obras interactivas OIM adapten su esquema de color de forma fluida a la identidad visual, el tema o el modo claro/oscuro de la aplicación anfitriona, sin alterar los metadatos ni el archivo original.

En los medios tradicionales, un banner interactivo diseñado con acentos de marca morados no puede insertarse en una web corporativa azul sin desentonar estéticamente. Cambiarlo obliga al diseñador a abrir el archivo fuente, cambiar los colores a mano, exportar y volver a subirlo.

Con los Colores Dinámicos de OIM, los creadores asignan roles semánticos a textos, rellenos vectoriales, trazos y máscaras. En tiempo de ejecución, <oim-player> mapea estos roles a variables CSS estándar (--oim-color-*) proporcionadas por la web anfitriona, garantizando siempre colores de respaldo (fallbacks) infalibles y protección absoluta frente a inyecciones de código CSS.


Los 12 Roles Semánticos ​

OIM adopta una paleta de 12 roles semánticos inspirada en los estándares de tokens de diseño contemporáneos (como Material Design 3 y Design Tokens W3C):

Rol SemánticoPropiedad CSSPropósito PrincipalPareja de Alto Contraste
primary--oim-color-primaryColor principal de marca, botones CTA prioritarios, insignias clave.on-primary
on-primary--oim-color-on-primaryTipografía e iconos situados directamente sobre superficies primary.primary
secondary--oim-color-secondaryColor de marca secundario, etiquetas complementarias, chips, badges.on-secondary
on-secondary--oim-color-on-secondaryTipografía e iconos situados directamente sobre superficies secondary.secondary
surface--oim-color-surfaceColor de fondo para tarjetas contenedoras, paneles y máscaras en modo ventana.on-surface
on-surface--oim-color-on-surfaceTexto principal del cuerpo, títulos y elementos neutros sobre surface.surface
outline--oim-color-outlineBordes estructurales, trazos vectoriales, líneas divisorias y marcos.N/A
accent--oim-color-accentRealce expresivo, detalles destacados, estrellas de valoración, avisos.N/A
text-primary--oim-color-text-primaryTitulares y texto de gran tamaño.N/A
text-secondary--oim-color-text-secondarySubtítulos y texto de cuerpo.N/A
text-muted--oim-color-text-mutedLetra pequeña, etiquetas y metadatos.N/A
text-on-accent--oim-color-text-on-accentTexto sobre rellenos accent.accent

Contrato TypeScript (@oim-media/core) ​

typescript
export type DynamicColorRole =
  | "primary"
  | "on-primary"
  | "secondary"
  | "on-secondary"
  | "surface"
  | "on-surface"
  | "outline"
  | "accent"
  | "text-primary"
  | "text-secondary"
  | "text-muted"
  | "text-on-accent";

export const DYNAMIC_COLOR_ROLES: readonly DynamicColorRole[] = [
  "primary",
  "on-primary",
  "secondary",
  "on-secondary",
  "surface",
  "on-surface",
  "outline",
  "accent",
  "text-primary",
  "text-secondary",
  "text-muted",
  "text-on-accent",
] as const;

Pipeline de Resolución en Runtime (resolveCssColor) ​

El motor proporciona un punto de resolución único y centralizado (resolveCssColor) a través del cual pasan los 13 sumideros de renderizado de color en el reproductor (ImageEngine):

typescript
export function resolveCssColor(
  role: DynamicColorRole | string | undefined | null,
  fallback?: string | null
): string

Reglas de Resolución ​

  1. Rol Activo con Color de Respaldo:

    typescript
    resolveCssColor("accent", "#2563EB")
    // Devuelve: "var(--oim-color-accent, var(--oim-palette-accent, #2563EB))"

    El navegador utiliza el valor de --oim-color-accent si la página anfitriona lo ha definido. Si no, usa el color de ese rol en la paleta del archivo y, si el archivo no tiene, el color de respaldo #2563EB del autor.

  2. Rol Activo sin Color de Respaldo:

    typescript
    resolveCssColor("surface", undefined)
    // Devuelve: "var(--oim-color-surface)"
  3. Sin Rol Semántico (Color Fijo del Autor):

    typescript
    resolveCssColor(undefined, "#10B981")
    // Devuelve: "#10B981"

    La figura se dibuja con el color estático del autor y no se ve afectada por el tema de la página.

  4. Intento de Inyección en Color de Respaldo:

    typescript
    resolveCssColor("accent", "red; background: url(//evil.com/baliza)")
    // Devuelve: "var(--oim-color-accent, var(--oim-palette-accent))"

    El respaldo malicioso se descarta de inmediato. Se conserva la variable semántica, pero el valor envenenado se elimina.

  5. Sin Rol y Respaldo Malicioso:

    typescript
    resolveCssColor(undefined, "red; background: url(//evil.com/baliza)")
    // Devuelve: ""

    El valor no seguro es rechazado y devuelve una cadena vacía en lugar de ocultar la manipulación con colores por defecto.


Arquitectura de Seguridad: Lista Blanca Estricta (isSafeCssColor) ​

En un archivo .oim, los colores proceden de un JSON externo que puede ser manipulado por un tercero. Si un color se pegara sin validar al atributo style de un elemento HTML, un atacante podría introducir una carga dañina:

text
// CARGA MALICIOSA EN EL METADATO OIM:
"strokeColor": "red; background-image: url(https://servidor-atacante/baliza)"

// INYECCIÓN VULNERABLE RESULTANTE (SIN SANITIZAR):
<div style="stroke: red; background-image: url(https://servidor-atacante/baliza)"></div>

El navegador parsearía la cadena como dos declaraciones CSS independientes, realizando una petición HTTP no autorizada hacia un servidor externo (baliza de rastreo o exfiltración de datos).

Para erradicar este vector de ataque, OIM implementa isSafeCssColor, una lista blanca positiva rigurosamente auditada:

typescript
export function isSafeCssColor(valor: unknown, profundidad = 0): boolean {
  if (typeof valor !== "string") return false;
  const v = valor.trim();
  
  // Cerrojo 1: Longitud máxima 128 caracteres y profundidad recursiva <= 4
  if (!v || v.length > 128 || profundidad > 4) return false;
  
  // Cerrojo 2: Rechazo de caracteres de control, comillas y barras de escape
  if (/[;{}<>"]/.test(v) || v.includes(String.fromCharCode(92))) return false;
  
  // Cerrojo 3: Rechazo de funciones CSS peligrosas y comentarios
  if (/url\(|expression\(|\/\*/i.test(v)) return false;
  
  // Patrones Positivos en Lista Blanca:
  if (HEX.test(v)) return true;             // #RGB, #RGBA, #RRGGBB, #RRGGBBAA
  if (PALABRAS.has(v.toLowerCase())) return true; // transparent, currentcolor, none, inherit
  if (FUNCION_COLOR.test(v)) return true;   // rgb(...), rgba(...), hsl(...), hsla(...)
  if (VAR_SIMPLE.test(v)) return true;      // var(--propiedad)
  
  // Validación recursiva del respaldo en variables anidadas var(--a, respaldo)
  const conRespaldo = VAR_CON_RESPALDO.exec(v);
  if (conRespaldo) return isSafeCssColor(conRespaldo[2], profundidad + 1);
  
  return false;
}

Qué campos de una capa llevan rol ​

Un rol viaja siempre junto al color que sustituye. Las herramientas que leen un .oim (para listar los roles que usa un archivo, o el color del autor de cada uno) deben leer estas parejas del style de cada capa, las mismas que resuelve el player:

Campo de rolColor del autor (respaldo)
fillColorRolefillColor
colorRolefillColor (texto)
strokeColorRolestrokeColor
textStrokeColorRoletextStrokeColor
effectColorRoleeffectColor (resplandor)
maskBgColorRolemaskBgColor
maskStrokeColorRolemaskStrokeColor

Guía de Integración para Desarrolladores ​

Definición de Paletas Temáticas en CSS ​

Para personalizar los medios OIM embebidos en tu aplicación, define las variables CSS en la raíz o en un contenedor contenedor:

css
/* Tema Claro */
:root {
  --oim-color-primary: #2563eb;
  --oim-color-on-primary: #ffffff;
  --oim-color-secondary: #475569;
  --oim-color-on-secondary: #f8fafc;
  --oim-color-surface: #ffffff;
  --oim-color-on-surface: #0f172a;
  --oim-color-outline: #cbd5e1;
  --oim-color-accent: #f59e0b;
}

/* Tema Oscuro */
[data-theme="dark"] {
  --oim-color-primary: #3b82f6;
  --oim-color-on-primary: #ffffff;
  --oim-color-secondary: #94a3b8;
  --oim-color-on-secondary: #0f172a;
  --oim-color-surface: #1e293b;
  --oim-color-on-surface: #f8fafc;
  --oim-color-outline: #334155;
  --oim-color-accent: #fbbf24;
}

Paletas dentro del archivo ​

Un .oim también puede llevar sus propias paletas con nombre (hasta 6 por página, definidas en el editor o con oim_apply_theme). El reproductor escribe la elegida como --oim-palette-<rol> y la escoge en este orden:

  1. el atributo palette de <oim-player> (por ejemplo palette="dark");
  2. si el archivo trae una paleta dark y otra light, la que corresponde a la preferencia del sistema del lector (prefers-color-scheme);
  3. la defaultPalette del archivo.

Así, una imagen con paletas dark y light sigue sola el modo oscuro del lector, sin CSS en la página anfitriona. Si la página define --oim-color-*, sigue mandando sobre la paleta del archivo, y allow-dynamic-colors="false" apaga ambas.

html
<!-- Fuerza la paleta oscura sin importar la preferencia del sistema -->
<oim-player src="obra.oim.png" palette="dark"></oim-player>

Aislamiento de Obras Concretas (allow-dynamic-colors) ​

Si tu sitio web define variables --oim-color-* globales, pero deseas embeber una obra artística externa preservando exactamente los colores de autor, utiliza el atributo allow-dynamic-colors="false":

html
<!-- Ignora las variables CSS globales y renderiza la paleta fiel del autor -->
<oim-player 
  src="obra-independiente.oim.png"
  allow-dynamic-colors="false"
  mode="full">
</oim-player>

Al especificar allow-dynamic-colors="false", el reproductor asigna automáticamente --oim-color-${role}: initial a los 12 roles semánticos dentro de su Shadow DOM, neutralizando la herencia CSS de la página anfitriona.