/* ============================================================ Mambo DVD — salvapantallas Paleta y tipografía de OSD de reproductor de DVD (negro puro, cian de menú, monoespaciada en versalitas con tracking ancho). ============================================================ */ :root { /* Color */ --void: #000000; --osd: #4de3ff; /* cian de menú */ --osd-dim: #1c5c6e; --claim: #ff3d9a; /* magenta: momento de esquina */ --ink: #e9f6fa; --ink-mute: #7c99a2; /* AA sobre negro al tamaño de 10-12px del OSD */ /* Tipografía */ --mono: "Cascadia Mono", "Consolas", ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace; --track: 0.22em; /* Estado en vivo, lo escribe app.js */ --hue: 0; --sprite-size: 180px; /* Ritmo */ --gutter: clamp(16px, 3.2vw, 40px); --corner-size: clamp(28px, 5vw, 56px); /* Las marcas de esquina tienen su propia banda: el HUD y la leyenda arrancan pasado ese ancho para no taparlas al reclamarse. */ --edge-inset: calc(var(--gutter) + var(--corner-size) + 20px); } * { box-sizing: border-box; } html, body { height: 100%; margin: 0; } body { background: var(--void); color: var(--ink); font-family: var(--mono); overflow: hidden; overscroll-behavior: none; -webkit-font-smoothing: antialiased; } /* ── Escenario ───────────────────────────────────────────── */ .stage { position: fixed; inset: 0; overflow: hidden; cursor: none; background: var(--void); } .stage.is-idle { cursor: none; } .stage.is-active { cursor: default; } /* ── El sprite que rebota ────────────────────────────────── */ .sprite { position: absolute; top: 0; left: 0; width: var(--sprite-size); height: var(--sprite-size); will-change: transform; transform: translate3d(0, 0, 0); user-select: none; -webkit-user-drag: none; } .sprite__art, .sprite__solid { position: absolute; inset: 0; width: 100%; height: 100%; } .sprite__art { object-fit: contain; pointer-events: none; -webkit-user-drag: none; } /* Silueta plana: el comportamiento literal del logo original de DVD. Se pinta en canvas y no con mask-image, porque las máscaras CSS pasan por CORS y en file:// (origen "null") el sprite quedaba invisible. */ .sprite__solid { filter: drop-shadow(0 0 26px hsl(var(--hue) 100% 58% / 0.5)); } /* Modo TINTE (por defecto): conserva el peluche, gira su color */ .sprite[data-color="tint"] .sprite__art { filter: saturate(1.9) hue-rotate(calc(var(--hue) * 1deg)) drop-shadow(0 0 28px hsl(var(--hue) 100% 60% / 0.42)); } .sprite[data-color="tint"] .sprite__solid { display: none; } /* Modo SÓLIDO */ .sprite[data-color="solid"] .sprite__art { visibility: hidden; } /* Modo ORIGINAL: sin recoloreo */ .sprite[data-color="off"] .sprite__art { filter: drop-shadow(0 0 22px rgb(255 255 255 / 0.16)); } .sprite[data-color="off"] .sprite__solid { display: none; } /* ── Marcas de esquina (elemento firma) ──────────────────── */ .corners { position: absolute; inset: 0; pointer-events: none; } .corner { --edge: 2px; position: absolute; width: var(--corner-size); height: var(--corner-size); border: 0 solid var(--osd-dim); opacity: 0.5; transition: opacity 400ms ease, border-color 400ms ease; } .corner[data-corner="tl"] { top: var(--gutter); left: var(--gutter); border-top-width: var(--edge); border-left-width: var(--edge); } .corner[data-corner="tr"] { top: var(--gutter); right: var(--gutter); border-top-width: var(--edge); border-right-width: var(--edge); } .corner[data-corner="br"] { bottom: var(--gutter); right: var(--gutter); border-bottom-width: var(--edge); border-right-width: var(--edge); } .corner[data-corner="bl"] { bottom: var(--gutter); left: var(--gutter); border-bottom-width: var(--edge); border-left-width: var(--edge); } /* Reclamada: se queda encendida con el color exacto que la golpeó */ .corner.is-claimed { --edge: 3px; opacity: 1; border-color: hsl(var(--claimed-hue) 96% 62%); box-shadow: 0 0 18px -2px hsl(var(--claimed-hue) 96% 62% / 0.7); } .corner.is-hit { animation: cornerHit 900ms cubic-bezier(0.16, 1, 0.3, 1); } @keyframes cornerHit { 0% { transform: scale(1); } 18% { transform: scale(2.1); } 100% { transform: scale(1); } } /* ── Impacto de esquina ──────────────────────────────────── */ .impact { position: absolute; inset: 0; pointer-events: none; } .impact__ring { position: absolute; top: 0; left: 0; width: 22px; height: 22px; margin: -11px 0 0 -11px; border: 2px solid hsl(var(--hue) 100% 66%); border-radius: 50%; opacity: 0; } .impact__ring.is-live { animation: ring 1100ms cubic-bezier(0.12, 0.8, 0.2, 1); } @keyframes ring { 0% { opacity: 0.95; transform: scale(1); border-width: 3px; } 100% { opacity: 0; transform: scale(46); border-width: 0.5px; } } .flash { position: absolute; inset: 0; pointer-events: none; opacity: 0; background: hsl(var(--hue) 100% 70%); mix-blend-mode: screen; } .flash.is-live { animation: flash 460ms ease-out; } @keyframes flash { 0% { opacity: 0.34; } 100% { opacity: 0; } } /* ── Atmósfera CRT: muy contenida ────────────────────────── */ .grain, .vignette { position: absolute; inset: 0; pointer-events: none; } .grain { opacity: 0.045; background: repeating-linear-gradient( to bottom, rgb(255 255 255 / 0.9) 0 1px, transparent 1px 3px ); } .vignette { background: radial-gradient( ellipse 80% 70% at 50% 50%, transparent 40%, rgb(0 0 0 / 0.55) 100% ); } /* ── HUD ─────────────────────────────────────────────────── */ .hud { position: fixed; left: var(--edge-inset); bottom: var(--gutter); display: flex; gap: clamp(20px, 3.6vw, 44px); align-items: flex-end; padding: 14px 20px 12px; border-left: 2px solid var(--osd); background: linear-gradient(90deg, rgb(6 22 27 / 0.82), rgb(6 22 27 / 0)); backdrop-filter: blur(2px); transition: opacity 320ms ease, transform 320ms ease; pointer-events: none; } .hud.is-hidden { opacity: 0; transform: translateY(8px); } .hud__stat { display: flex; flex-direction: column; gap: 6px; } .hud__label { font-size: 9px; letter-spacing: var(--track); text-transform: uppercase; color: var(--ink-mute); } .hud__value { font-size: clamp(22px, 3.4vw, 32px); line-height: 0.9; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); } .hud__value--word { font-size: clamp(13px, 1.8vw, 16px); letter-spacing: 0.1em; text-transform: uppercase; padding-bottom: 4px; } /* La cuenta de esquinas es el número que importa */ .hud__stat--lead .hud__value { font-size: clamp(34px, 5.4vw, 54px); color: var(--osd); text-shadow: 0 0 22px hsl(191 100% 60% / 0.45); } .hud__stat--lead .hud__label { color: var(--osd); } .hud__value.is-bumped { animation: bump 700ms cubic-bezier(0.16, 1, 0.3, 1); } @keyframes bump { 0% { transform: scale(1); color: var(--claim); } 22% { transform: scale(1.32); color: var(--claim); } 100% { transform: scale(1); } } /* ── Aviso de audio: solo se muestra cuando falla ────────── */ .audio-alert { position: fixed; top: var(--gutter); left: 50%; transform: translateX(-50%); margin: 0; padding: 12px 20px; max-width: min(90vw, 620px); border: 1px solid var(--claim); background: rgb(28 3 15 / 0.92); font-size: 11px; line-height: 1.7; letter-spacing: 0.08em; color: #ffd7e9; text-align: center; opacity: 0; visibility: hidden; transition: opacity 260ms ease, visibility 260ms; } .audio-alert.is-live { opacity: 1; visibility: visible; } .audio-alert kbd { font: inherit; padding: 1px 6px; border: 1px solid var(--claim); color: var(--claim); } /* ── Leyenda de teclas ───────────────────────────────────── */ .keys { position: fixed; right: var(--edge-inset); bottom: var(--gutter); display: flex; flex-wrap: wrap; justify-content: flex-end; gap: 6px 18px; max-width: min(46vw, 520px); font-size: 10px; letter-spacing: 0.14em; text-transform: uppercase; color: var(--ink-mute); transition: opacity 700ms ease; pointer-events: none; } .keys.is-hidden { opacity: 0; } .keys kbd { font: inherit; padding: 2px 6px; margin-right: 4px; border: 1px solid var(--osd-dim); color: var(--osd); } /* ── Indicador de pausa ──────────────────────────────────── */ .paused { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); padding: 10px 22px; border: 1px solid var(--osd); font-size: 12px; letter-spacing: var(--track); text-transform: uppercase; color: var(--osd); background: rgb(0 0 0 / 0.6); opacity: 0; transition: opacity 200ms ease; pointer-events: none; } .paused.is-live { opacity: 1; } /* ── Portada de arranque ─────────────────────────────────── */ .boot { position: fixed; inset: 0; display: grid; place-items: center; padding: var(--gutter); background: var(--void); z-index: 10; transition: opacity 520ms ease, visibility 520ms; } .boot.is-gone { opacity: 0; visibility: hidden; } .boot__panel { width: min(560px, 100%); padding: clamp(28px, 5vw, 48px); border: 1px solid var(--osd-dim); border-left: 3px solid var(--osd); text-align: left; } .boot__eyebrow { margin: 0 0 22px; font-size: 10px; letter-spacing: 0.4em; text-transform: uppercase; color: var(--osd); } .boot__title { margin: 0 0 20px; font-size: clamp(38px, 9vw, 76px); line-height: 0.94; font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; } .boot__title-dim { display: block; color: var(--osd-dim); letter-spacing: 0.3em; font-size: 0.42em; margin-top: 12px; } .boot__note { margin: 0 0 32px; max-width: 42ch; font-size: 12px; line-height: 1.85; letter-spacing: 0.05em; color: var(--ink-mute); text-transform: none; } .boot__play { display: inline-flex; align-items: center; gap: 12px; padding: 15px 34px; border: 1px solid var(--osd); background: transparent; font: inherit; font-size: 13px; font-weight: 700; letter-spacing: var(--track); text-transform: uppercase; color: var(--osd); cursor: pointer; transition: background-color 180ms ease, color 180ms ease, box-shadow 180ms ease; } .boot__play:hover, .boot__play:focus-visible { background: var(--osd); color: var(--void); box-shadow: 0 0 30px -4px var(--osd); } .boot__play:focus-visible { outline: 2px solid var(--ink); outline-offset: 4px; } .boot__glyph { animation: blink 1.6s steps(1, end) infinite; } @keyframes blink { 0%, 55% { opacity: 1; } 56%, 99% { opacity: 0.25; } } /* ── Accesibilidad ───────────────────────────────────────── */ /* El rebote es el contenido, así que se conserva. Lo que se apaga es el ruido decorativo alrededor del impacto. */ @media (prefers-reduced-motion: reduce) { .impact__ring.is-live, .corner.is-hit, .hud__value.is-bumped, .boot__glyph { animation: none; } .flash.is-live { animation: flash 200ms linear; } .grain { display: none; } } @media (max-width: 720px) { .keys { display: none; } .hud { gap: 18px; padding: 10px 14px 8px; } }