fix(producer): fetch missing font weights from Google Fonts at render time

The deterministic font system now supplements its embedded font bundle
with Google Fonts fetches for any weights not in the pre-bundled set.
This fixes compositions that request font weights (e.g. Montserrat 300)
not included in the CANONICAL_FONTS faces array — previously those
weights were silently dropped, causing invisible text in renders.

Also replaces caption-glitch-rgb and caption-weight-shift with improved
versions from avatar preview compositions, adapted to 1920x1080 with
standard demo transcript.
This commit is contained in:
Miguel Ángel
2026-05-17 20:41:30 -04:00
parent c3c2129d17
commit c8e8fdcf6d
5 changed files with 396 additions and 269 deletions
@@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@200;300;400;500;600;700;800;900&display=swap"
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
@@ -59,7 +59,7 @@
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 260px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
@@ -70,7 +70,7 @@
left: 0;
top: 0;
width: 1400px;
height: 260px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
@@ -100,7 +100,7 @@
white-space: nowrap;
color: #ffffff;
font-family: "Montserrat", Arial, sans-serif;
font-weight: 200;
font-weight: 300;
font-size: 72px;
letter-spacing: -0.03em;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
@@ -142,10 +142,10 @@
var WORD_SPACING = 12;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_DURATION = 100 / 1000;
var WEIGHT_TRANSITION = 250 / 1000;
var WEIGHT_TRANSITION = 100 / 1000;
var FONT_WIDTH_SAFETY = 1.14;
var FONT_WEIGHT_LIGHT = 200;
var FONT_WEIGHT_BOLD = 900;
var FONT_WEIGHT_LIGHT = 300;
var FONT_WEIGHT_BOLD = 700;
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
@@ -224,7 +224,7 @@
}
function splitLines(words) {
if (measureLineWidth(words) <= MAX_LINE_WIDTH) {
if (words.length < 2) {
return [{ words: words, startIndex: 0 }];
}
var bestSplit = Math.ceil(words.length / 2);
@@ -248,6 +248,35 @@
return result;
}
function avoidSingleWordGroups(groups) {
var out = [];
groups.forEach(function (group) {
if (
group.words.length === 1 &&
out.length > 0 &&
out[out.length - 1].words.length < MAX_WORDS_PER_GROUP
) {
out[out.length - 1] = makeGroup(out[out.length - 1].words.concat(group.words));
} else {
out.push(group);
}
});
for (var i = 0; i < out.length; i++) {
if (out[i].words.length !== 1) continue;
if (i + 1 < out.length && out[i + 1].words.length < MAX_WORDS_PER_GROUP) {
out[i] = makeGroup(out[i].words.concat(out[i + 1].words));
out.splice(i + 1, 1);
} else if (i > 0 && out[i - 1].words.length < MAX_WORDS_PER_GROUP) {
out[i - 1] = makeGroup(out[i - 1].words.concat(out[i].words));
out.splice(i, 1);
i--;
}
}
return out;
}
function makeGroups(words) {
var groups = [];
var current = [];
@@ -273,7 +302,7 @@
}
});
if (current.length) groups.push(makeGroup(current));
return groups;
return avoidSingleWordGroups(groups);
}
function makeGroup(words) {
@@ -309,17 +338,12 @@
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex;
lineEl.style.fontWeight = FONT_WEIGHT_LIGHT;
lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT;
lineEl.style.fontSize = computedSize + "px";
line.words.forEach(function (word, wordIndex) {
var wordEl = document.createElement("span");
wordEl.id = "cw-" + groupIndex + "-" + lineIndex + "-" + wordIndex;
wordEl.textContent = word.text.toLowerCase();
wordEl.style.fontWeight = FONT_WEIGHT_LIGHT;
wordEl.style.display = "inline-block";
wordEl.style.transition = "none";
wordEl.dataset.start = word.start;
wordEl.dataset.end = word.end;
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
@@ -363,24 +387,23 @@
visibleStart,
);
group.lines.forEach(function (line, lineIndex) {
line.words.forEach(function (word, wordIndex) {
var wEl = document.getElementById(
"cw-" + groupIndex + "-" + lineIndex + "-" + wordIndex,
);
if (!wEl) return;
tl.to(
wEl,
{ fontWeight: FONT_WEIGHT_BOLD, duration: WEIGHT_TRANSITION, ease: "power2.out" },
word.start,
);
tl.to(
wEl,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.in" },
word.end,
);
});
});
if (group.lines.length === 2) {
var line1El = document.getElementById("caption-group-" + groupIndex + "-line-0");
var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1");
var line2FirstWord = group.lines[1].words[0];
var switchTime = line2FirstWord.start;
tl.to(
line1El,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
tl.to(
line2El,
{ fontWeight: FONT_WEIGHT_BOLD, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
}
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});
+56 -33
View File
@@ -7,7 +7,7 @@
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@200;300;400;500;600;700;800;900&display=swap"
href="https://fonts.googleapis.com/css2?family=Montserrat:wght@300;700&display=swap"
rel="stylesheet"
/>
<script src="https://cdn.jsdelivr.net/npm/gsap@3.14.2/dist/gsap.min.js"></script>
@@ -59,7 +59,7 @@
left: 50%;
transform: translateX(-50%);
width: 1400px;
height: 260px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
@@ -70,7 +70,7 @@
left: 0;
top: 0;
width: 1400px;
height: 260px;
height: 278px;
display: flex;
align-items: center;
justify-content: center;
@@ -100,7 +100,7 @@
white-space: nowrap;
color: #ffffff;
font-family: "Montserrat", Arial, sans-serif;
font-weight: 200;
font-weight: 300;
font-size: 72px;
letter-spacing: -0.03em;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
@@ -142,10 +142,10 @@
var WORD_SPACING = 12;
var PAUSE_THRESHOLD = 0.1;
var ENTRY_DURATION = 100 / 1000;
var WEIGHT_TRANSITION = 250 / 1000;
var WEIGHT_TRANSITION = 100 / 1000;
var FONT_WIDTH_SAFETY = 1.14;
var FONT_WEIGHT_LIGHT = 200;
var FONT_WEIGHT_BOLD = 900;
var FONT_WEIGHT_LIGHT = 300;
var FONT_WEIGHT_BOLD = 700;
var TRANSCRIPT = [
{ text: "Every", start: 0.0, end: 0.3 },
@@ -224,7 +224,7 @@
}
function splitLines(words) {
if (measureLineWidth(words) <= MAX_LINE_WIDTH) {
if (words.length < 2) {
return [{ words: words, startIndex: 0 }];
}
var bestSplit = Math.ceil(words.length / 2);
@@ -248,6 +248,35 @@
return result;
}
function avoidSingleWordGroups(groups) {
var out = [];
groups.forEach(function (group) {
if (
group.words.length === 1 &&
out.length > 0 &&
out[out.length - 1].words.length < MAX_WORDS_PER_GROUP
) {
out[out.length - 1] = makeGroup(out[out.length - 1].words.concat(group.words));
} else {
out.push(group);
}
});
for (var i = 0; i < out.length; i++) {
if (out[i].words.length !== 1) continue;
if (i + 1 < out.length && out[i + 1].words.length < MAX_WORDS_PER_GROUP) {
out[i] = makeGroup(out[i].words.concat(out[i + 1].words));
out.splice(i + 1, 1);
} else if (i > 0 && out[i - 1].words.length < MAX_WORDS_PER_GROUP) {
out[i - 1] = makeGroup(out[i - 1].words.concat(out[i].words));
out.splice(i, 1);
i--;
}
}
return out;
}
function makeGroups(words) {
var groups = [];
var current = [];
@@ -273,7 +302,7 @@
}
});
if (current.length) groups.push(makeGroup(current));
return groups;
return avoidSingleWordGroups(groups);
}
function makeGroup(words) {
@@ -309,17 +338,12 @@
var lineEl = document.createElement("div");
lineEl.className = "caption-line";
lineEl.id = "caption-group-" + groupIndex + "-line-" + lineIndex;
lineEl.style.fontWeight = FONT_WEIGHT_LIGHT;
lineEl.style.fontWeight = lineIndex === 0 ? FONT_WEIGHT_BOLD : FONT_WEIGHT_LIGHT;
lineEl.style.fontSize = computedSize + "px";
line.words.forEach(function (word, wordIndex) {
var wordEl = document.createElement("span");
wordEl.id = "cw-" + groupIndex + "-" + lineIndex + "-" + wordIndex;
wordEl.textContent = word.text.toLowerCase();
wordEl.style.fontWeight = FONT_WEIGHT_LIGHT;
wordEl.style.display = "inline-block";
wordEl.style.transition = "none";
wordEl.dataset.start = word.start;
wordEl.dataset.end = word.end;
lineEl.appendChild(wordEl);
});
copyEl.appendChild(lineEl);
@@ -363,24 +387,23 @@
visibleStart,
);
group.lines.forEach(function (line, lineIndex) {
line.words.forEach(function (word, wordIndex) {
var wEl = document.getElementById(
"cw-" + groupIndex + "-" + lineIndex + "-" + wordIndex,
);
if (!wEl) return;
tl.to(
wEl,
{ fontWeight: FONT_WEIGHT_BOLD, duration: WEIGHT_TRANSITION, ease: "power2.out" },
word.start,
);
tl.to(
wEl,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.in" },
word.end,
);
});
});
if (group.lines.length === 2) {
var line1El = document.getElementById("caption-group-" + groupIndex + "-line-0");
var line2El = document.getElementById("caption-group-" + groupIndex + "-line-1");
var line2FirstWord = group.lines[1].words[0];
var switchTime = line2FirstWord.start;
tl.to(
line1El,
{ fontWeight: FONT_WEIGHT_LIGHT, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
tl.to(
line2El,
{ fontWeight: FONT_WEIGHT_BOLD, duration: WEIGHT_TRANSITION, ease: "power2.out" },
switchTime,
);
}
tl.set(groupEl, { opacity: 0 }, visibleEnd);
});