|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| const LL = (window.LL = window.LL || {});
|
|
|
|
|
|
|
|
|
|
|
| function currentLang() {
|
| if (LL.lang) return LL.lang === "en" ? "en" : "es";
|
| const docLang = (document.documentElement.lang || "es").toLowerCase();
|
| return docLang.startsWith("en") ? "en" : "es";
|
| }
|
|
|
| function bilingual(es, en, cls) {
|
| const el = document.createElement("span");
|
| if (cls) el.className = cls;
|
| el.setAttribute("data-es", es);
|
| el.setAttribute("data-en", en);
|
| el.textContent = currentLang() === "en" ? en : es;
|
| return el;
|
| }
|
|
|
|
|
|
|
|
|
| const STAGES = [
|
| { id: "oyendo", es: "OYENDO", en: "LISTENING", keys: ["oyendo", "listening", "warming", "ready"] },
|
| { id: "traduciendo", es: "TRADUCIENDO", en: "TRANSLATING", keys: ["traduciendo", "translating"] },
|
| { id: "dirigiendo", es: "DIRIGIENDO", en: "DIRECTING", keys: ["dirigiendo", "directing", "directed"] },
|
| { id: "pintando", es: "PINTANDO", en: "PAINTING", keys: ["pintando", "painting", "depth"] },
|
| { id: "revelando", es: "REVELANDO", en: "REVEALING", keys: ["revelando", "revealing", "painted"] },
|
| ];
|
|
|
|
|
| const CAMERA_LABELS = {
|
| static: { es: "fijo", en: "static" },
|
| dolly_in: { es: "dolly-in", en: "dolly-in" },
|
| dolly_out: { es: "dolly-out", en: "dolly-out" },
|
| pan_left: { es: "pan izq.", en: "pan left" },
|
| pan_right: { es: "pan der.", en: "pan right" },
|
| tilt_up: { es: "tilt arriba", en: "tilt up" },
|
| crane_down: { es: "grúa abajo", en: "crane down" },
|
| };
|
|
|
|
|
| const SCALE_NAMES = {
|
| ECU: { es: "primerísimo primer plano", en: "extreme close-up" },
|
| CU: { es: "primer plano", en: "close-up" },
|
| MS: { es: "plano medio", en: "medium shot" },
|
| WS: { es: "plano general", en: "wide shot" },
|
| EWS: { es: "gran plano general", en: "extreme wide shot" },
|
| };
|
|
|
| function cameraLabel(move) {
|
| const l = CAMERA_LABELS[move];
|
| if (!l) return move ? String(move).replace(/_/g, "-") : "—";
|
| return currentLang() === "en" ? l.en : l.es;
|
| }
|
| function scaleName(scale) {
|
| const s = SCALE_NAMES[scale];
|
| if (!s) return "";
|
| return currentLang() === "en" ? s.en : s.es;
|
| }
|
|
|
|
|
| let root = null;
|
| let els = null;
|
| let lastDecision = null;
|
|
|
| function injectStyles() {
|
| if (document.getElementById("ll-slate-styles")) return;
|
| const css = `
|
| /* Clapperboard panel — chalk on slate, warm amber accents, never blue-tech. */
|
| #slate {
|
| font-family: var(--font-ui);
|
| color: var(--ink);
|
| background:
|
| linear-gradient(180deg, rgba(20,20,24,0.92), rgba(10,10,12,0.96)),
|
| repeating-linear-gradient(135deg, transparent 0 14px, rgba(255,255,255,0.012) 14px 28px);
|
| border: 1px solid var(--line);
|
| border-radius: var(--r-panel);
|
| box-shadow: var(--panel-shadow, 0 10px 40px rgba(0,0,0,.6));
|
| padding: 14px 16px 16px;
|
| display: flex;
|
| flex-direction: column;
|
| gap: 12px;
|
| user-select: none;
|
| }
|
| /* The clapper sticks: alternating amber/ink diagonal bars across the top. */
|
| .ll-slate-clapper {
|
| height: 16px;
|
| border-radius: 6px 6px 0 0;
|
| background: repeating-linear-gradient(
|
| -52deg,
|
| var(--amber) 0 16px,
|
| #14140f 16px 32px
|
| );
|
| box-shadow: var(--halo-soft, 0 0 18px rgba(232,163,61,.10)) inset;
|
| margin: -2px -2px 2px;
|
| opacity: .92;
|
| }
|
| .ll-slate-head {
|
| display: flex; align-items: center; justify-content: space-between; gap: 10px;
|
| }
|
| .ll-slate-plano {
|
| font-family: var(--font-display);
|
| font-weight: 600;
|
| letter-spacing: .04em;
|
| font-size: 15px;
|
| color: var(--ink);
|
| }
|
| .ll-slate-plano b { color: var(--amber); font-weight: 700; }
|
|
|
| /* Decision chip: the eye-magnet. CUT = REC red, CONTINUITY = amber. */
|
| .ll-chip {
|
| --chip-bg: var(--amber-soft);
|
| --chip-fg: var(--amber);
|
| --chip-br: var(--amber);
|
| display: inline-flex; align-items: center; gap: 6px;
|
| font-family: var(--font-ui);
|
| font-weight: 800;
|
| font-size: 13px;
|
| letter-spacing: .12em;
|
| text-transform: uppercase;
|
| padding: 6px 12px;
|
| border-radius: var(--r-pill);
|
| color: var(--chip-fg);
|
| background: var(--chip-bg);
|
| border: 1.5px solid var(--chip-br);
|
| box-shadow: 0 0 0 0 transparent;
|
| transform: perspective(400px) rotateX(0deg) scale(1);
|
| transform-origin: center;
|
| }
|
| .ll-chip[data-decision="cut"] {
|
| --chip-fg: #fff;
|
| --chip-bg: var(--rec);
|
| --chip-br: var(--rec);
|
| box-shadow: 0 0 16px rgba(224,68,46,.45);
|
| }
|
| .ll-chip[data-decision="continuity"] {
|
| --chip-fg: var(--amber);
|
| --chip-bg: var(--amber-soft);
|
| --chip-br: var(--amber);
|
| }
|
| .ll-chip .ll-chip-dot {
|
| width: 8px; height: 8px; border-radius: 50%;
|
| background: currentColor; opacity: .9;
|
| }
|
| /* Flip+scale when the call changes. Reduced motion = no flip. */
|
| @keyframes ll-chip-flip {
|
| 0% { transform: perspective(400px) rotateX(0deg) scale(1); }
|
| 45% { transform: perspective(400px) rotateX(90deg) scale(1.12); }
|
| 55% { transform: perspective(400px) rotateX(-90deg) scale(1.12); }
|
| 100% { transform: perspective(400px) rotateX(0deg) scale(1); }
|
| }
|
| .ll-chip.ll-flip { animation: ll-chip-flip 420ms var(--ease) both; }
|
| body.reduced-motion .ll-chip.ll-flip { animation: none; }
|
|
|
| /* Big shot-scale block. */
|
| .ll-slate-scale {
|
| display: flex; align-items: baseline; gap: 10px;
|
| padding: 6px 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
|
| }
|
| .ll-slate-scale-big {
|
| font-family: var(--font-display); font-weight: 700; font-size: 30px; line-height: 1;
|
| color: var(--ink); letter-spacing: .02em;
|
| }
|
| .ll-slate-scale-name { font-size: 12px; color: var(--ink-dim); font-style: italic; }
|
|
|
| /* Rows: camera + lighting, chalk-handwritten feel via display serif italic. */
|
| .ll-slate-row { display: flex; align-items: baseline; gap: 8px; font-size: 13px; }
|
| .ll-slate-row .ll-k {
|
| color: var(--ink-faint); text-transform: uppercase; letter-spacing: .1em;
|
| font-size: 10px; min-width: 58px;
|
| }
|
| .ll-slate-row .ll-v { color: var(--ink); font-family: var(--font-display); font-style: italic; }
|
|
|
| /* Palette swatches. */
|
| .ll-slate-palette { display: flex; align-items: center; gap: 8px; }
|
| .ll-slate-palette .ll-k {
|
| color: var(--ink-faint); text-transform: uppercase; letter-spacing: .1em; font-size: 10px;
|
| }
|
| .ll-swatches { display: flex; gap: 6px; }
|
| .ll-swatch {
|
| width: 26px; height: 26px; border-radius: 6px;
|
| border: 1px solid rgba(255,255,255,.18);
|
| box-shadow: 0 1px 6px rgba(0,0,0,.5);
|
| transition: transform var(--transition-ms) var(--ease);
|
| }
|
|
|
| /* Setting line (small, optional). */
|
| .ll-slate-setting { font-size: 11px; color: var(--ink-dim); font-style: italic; }
|
|
|
| /* Micro-stage bar — thin amber chips under the canvas. Never a spinner. */
|
| #stage-bar, .ll-stagebar {
|
| display: flex; gap: 6px; flex-wrap: wrap;
|
| align-items: center;
|
| }
|
| .ll-stage-chip {
|
| font-family: var(--font-ui); font-size: 10px; font-weight: 700;
|
| letter-spacing: .14em; text-transform: uppercase;
|
| padding: 4px 9px; border-radius: var(--r-pill);
|
| color: var(--ink-faint);
|
| background: transparent;
|
| border: 1px solid var(--line);
|
| transition: color 240ms var(--ease), background 240ms var(--ease),
|
| border-color 240ms var(--ease), box-shadow 240ms var(--ease);
|
| }
|
| .ll-stage-chip.is-done { color: var(--ink-dim); border-color: var(--line); }
|
| .ll-stage-chip.is-active {
|
| color: #14140f; background: var(--amber); border-color: var(--amber);
|
| box-shadow: var(--halo-soft, 0 0 18px rgba(232,163,61,.10));
|
| }
|
| body.reduced-motion .ll-stage-chip { transition: none; }
|
| `;
|
| const style = document.createElement("style");
|
| style.id = "ll-slate-styles";
|
| style.textContent = css;
|
| document.head.appendChild(style);
|
| }
|
|
|
|
|
| function build() {
|
| if (els) return els;
|
| injectStyles();
|
| root = document.getElementById("slate");
|
| if (!root) return null;
|
|
|
| root.innerHTML = "";
|
| root.setAttribute("aria-live", "polite");
|
| root.setAttribute("aria-label", "Director's slate");
|
|
|
| const clapper = document.createElement("div");
|
| clapper.className = "ll-slate-clapper";
|
|
|
|
|
| const head = document.createElement("div");
|
| head.className = "ll-slate-head";
|
| const plano = document.createElement("div");
|
| plano.className = "ll-slate-plano";
|
| const chip = document.createElement("span");
|
| chip.className = "ll-chip";
|
| chip.setAttribute("data-decision", "continuity");
|
| head.append(plano, chip);
|
|
|
|
|
| const scale = document.createElement("div");
|
| scale.className = "ll-slate-scale";
|
| const scaleBig = document.createElement("span");
|
| scaleBig.className = "ll-slate-scale-big";
|
| scaleBig.textContent = "—";
|
| const scaleName = document.createElement("span");
|
| scaleName.className = "ll-slate-scale-name";
|
| scale.append(scaleBig, scaleName);
|
|
|
|
|
| const camRow = document.createElement("div");
|
| camRow.className = "ll-slate-row";
|
| const camK = bilingual("CÁMARA", "CAMERA", "ll-k");
|
| const camV = document.createElement("span");
|
| camV.className = "ll-v";
|
| camV.textContent = "—";
|
| camRow.append(camK, camV);
|
|
|
|
|
| const litRow = document.createElement("div");
|
| litRow.className = "ll-slate-row";
|
| const litK = bilingual("LUZ", "LIGHT", "ll-k");
|
| const litV = document.createElement("span");
|
| litV.className = "ll-v";
|
| litV.textContent = "—";
|
| litRow.append(litK, litV);
|
|
|
|
|
| const pal = document.createElement("div");
|
| pal.className = "ll-slate-palette";
|
| const palK = bilingual("PALETA", "PALETTE", "ll-k");
|
| const swatches = document.createElement("div");
|
| swatches.className = "ll-swatches";
|
| const swEls = [];
|
| for (let i = 0; i < 3; i++) {
|
| const s = document.createElement("span");
|
| s.className = "ll-swatch";
|
| s.style.background = "var(--line)";
|
| swatches.appendChild(s);
|
| swEls.push(s);
|
| }
|
| pal.append(palK, swatches);
|
|
|
|
|
| const setting = document.createElement("div");
|
| setting.className = "ll-slate-setting";
|
|
|
| root.append(clapper, head, scale, camRow, litRow, pal, setting);
|
|
|
| els = { root, plano, chip, scaleBig, scaleName, camV, litV, swEls, setting };
|
| return els;
|
| }
|
|
|
|
|
| function setDecision(decision) {
|
| const e = els;
|
| const norm = decision === "cut" ? "cut" : "continuity";
|
| e.chip.setAttribute("data-decision", norm);
|
|
|
| e.chip.innerHTML = "";
|
| const dot = document.createElement("span");
|
| dot.className = "ll-chip-dot";
|
| const label =
|
| norm === "cut"
|
| ? bilingual("CORTE", "CUT")
|
| : bilingual("CONTINUIDAD", "CONTINUITY");
|
| e.chip.append(dot, label);
|
|
|
| if (lastDecision !== null && lastDecision !== norm) {
|
| e.chip.classList.remove("ll-flip");
|
|
|
| void e.chip.offsetWidth;
|
| e.chip.classList.add("ll-flip");
|
| }
|
| lastDecision = norm;
|
| }
|
|
|
|
|
| LL.slate = {
|
|
|
|
|
|
|
|
|
| update(shot, sceneId, shotIndex) {
|
| const e = build();
|
| if (!e || !shot) return;
|
|
|
|
|
| const planoNum = String(shotIndex ?? "—").padStart(2, "0");
|
| e.plano.innerHTML = "";
|
| const planoLabel = currentLang() === "en" ? "SHOT " : "PLANO ";
|
| e.plano.append(document.createTextNode(planoLabel));
|
| const b = document.createElement("b");
|
| b.textContent = planoNum;
|
| e.plano.appendChild(b);
|
|
|
|
|
| setDecision(shot.decision);
|
|
|
|
|
| const scale = shot.shot_scale || "—";
|
| e.scaleBig.textContent = scale;
|
| e.scaleName.textContent = scaleName(scale);
|
|
|
|
|
| e.camV.textContent = cameraLabel(shot.camera_move);
|
|
|
|
|
| e.litV.textContent = shot.lighting ? String(shot.lighting) : "—";
|
|
|
|
|
| const palette = Array.isArray(shot.palette) ? shot.palette : [];
|
| for (let i = 0; i < 3; i++) {
|
| const c = palette[i];
|
| const valid = typeof c === "string" && /^#?[0-9a-fA-F]{3,8}$/.test(c.trim());
|
| e.swEls[i].style.background = valid
|
| ? c.trim().startsWith("#")
|
| ? c.trim()
|
| : "#" + c.trim()
|
| : "var(--line)";
|
| e.swEls[i].title = valid ? c : "";
|
| }
|
|
|
|
|
| e.setting.textContent = shot.setting ? String(shot.setting) : "";
|
| },
|
|
|
|
|
|
|
| setStage(name) {
|
| const bar = document.getElementById("stage-bar");
|
| if (!bar) return;
|
| injectStyles();
|
|
|
|
|
| if (!bar.querySelector(".ll-stage-chip")) {
|
| bar.classList.add("ll-stagebar");
|
| bar.innerHTML = "";
|
| STAGES.forEach((st) => {
|
| const chip = document.createElement("span");
|
| chip.className = "ll-stage-chip";
|
| chip.dataset.stageId = st.id;
|
| chip.setAttribute("data-es", st.es);
|
| chip.setAttribute("data-en", st.en);
|
| chip.textContent = currentLang() === "en" ? st.en : st.es;
|
| bar.appendChild(chip);
|
| });
|
| }
|
|
|
| const key = String(name || "").toLowerCase();
|
| const idx = STAGES.findIndex((st) => st.id === key || st.keys.includes(key));
|
| const chips = bar.querySelectorAll(".ll-stage-chip");
|
| chips.forEach((chip, i) => {
|
| chip.classList.toggle("is-active", idx >= 0 && i === idx);
|
| chip.classList.toggle("is-done", idx >= 0 && i < idx);
|
| });
|
| },
|
|
|
|
|
| retranslate() {
|
| const lang = currentLang();
|
| document.querySelectorAll("#slate [data-es], #stage-bar [data-es]").forEach((el) => {
|
| const v = el.getAttribute(lang === "en" ? "data-en" : "data-es");
|
| if (v != null) el.textContent = v;
|
| });
|
| },
|
| };
|
|
|