Compare commits
4 Commits
b77b3bffd2
...
41831c0893
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
41831c0893 | ||
|
|
8238bcce0e | ||
|
|
43c876b6be | ||
|
|
046e92cf30 |
@ -131,6 +131,17 @@ css:
|
||||
--font-body: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
||||
--font-mono: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
|
||||
}
|
||||
/* Bundled display faces. The host does not emit @font-face for bundled_fonts
|
||||
* (LoadedPlugin.BundledFonts is unconsumed), so the theme declares its own
|
||||
* faces against the served artifact paths (/templates/scifi-clean/fonts/...).
|
||||
* Presets set typography.fontHeading/body/mono to template:scifi-clean:<Family>
|
||||
* so --font-heading/--font-body/--font-mono resolve to these families. */
|
||||
@font-face { font-family: 'Space Grotesk'; src: url('/templates/scifi-clean/fonts/space-grotesk-500.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Space Grotesk'; src: url('/templates/scifi-clean/fonts/space-grotesk-700.woff2') format('woff2'); font-weight: 700; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Inter'; src: url('/templates/scifi-clean/fonts/inter-400.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'Inter'; src: url('/templates/scifi-clean/fonts/inter-600.woff2') format('woff2'); font-weight: 600; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'JetBrains Mono'; src: url('/templates/scifi-clean/fonts/jetbrains-mono-400.woff2') format('woff2'); font-weight: 400; font-style: normal; font-display: swap; }
|
||||
@font-face { font-family: 'JetBrains Mono'; src: url('/templates/scifi-clean/fonts/jetbrains-mono-400.woff2') format('woff2'); font-weight: 500; font-style: normal; font-display: swap; }
|
||||
/* ============================================================
|
||||
Corporate Modernist — appended component CSS.
|
||||
Semantic tokens only: hsl(var(--token)). No literal colors.
|
||||
|
||||
@ -2,7 +2,7 @@
|
||||
name = "scifi-clean"
|
||||
display_name = "Sci-Fi Clean"
|
||||
scope = "@themes"
|
||||
version = "0.3.0"
|
||||
version = "0.4.0"
|
||||
description = "Minimalist futurist theme for aerospace, robotics, and R&D — off-white surfaces, technical blue, signal-orange accents, mono numerals."
|
||||
kind = "theme"
|
||||
categories = ["templates"]
|
||||
|
||||
28
presets.json
@ -5,6 +5,13 @@
|
||||
"description": "The default Sci-Fi Clean look. Light is a white-room lab with technical-blue HUD lines and a signal-orange accent; dark is deep mission-control navy with lifted blue readouts.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:scifi-clean:Space Grotesk",
|
||||
"fontBody": "template:scifi-clean:Inter",
|
||||
"fontMono": "template:scifi-clean:JetBrains Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.6"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "210 14% 97%",
|
||||
"foreground": "215 30% 12%",
|
||||
@ -55,6 +62,13 @@
|
||||
"description": "Highest-contrast surgical white with deep-navy ink in light, and near-black space navy in dark. For high-precision spec and dashboard work.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:scifi-clean:Space Grotesk",
|
||||
"fontBody": "template:scifi-clean:Inter",
|
||||
"fontMono": "template:scifi-clean:JetBrains Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.6"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "0 0% 100%",
|
||||
"foreground": "220 40% 10%",
|
||||
@ -105,6 +119,13 @@
|
||||
"description": "Overt HUD styling. Cyan primary guide lines with an amber signal accent, on a cool white lab ground in light and a teal-tinted mission ground in dark.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:scifi-clean:Space Grotesk",
|
||||
"fontBody": "template:scifi-clean:Inter",
|
||||
"fontMono": "template:scifi-clean:JetBrains Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.6"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "195 20% 97%",
|
||||
"foreground": "200 40% 11%",
|
||||
@ -155,6 +176,13 @@
|
||||
"description": "Restrained neutral. Slate-blue primary and a muted signal accent on graphite surfaces. Quietest of the four for text-dense product docs.",
|
||||
"theme": {
|
||||
"mode": "both",
|
||||
"typography": {
|
||||
"fontHeading": "template:scifi-clean:Space Grotesk",
|
||||
"fontBody": "template:scifi-clean:Inter",
|
||||
"fontMono": "template:scifi-clean:JetBrains Mono",
|
||||
"fontSizeBase": "17",
|
||||
"lineHeightBase": "1.6"
|
||||
},
|
||||
"lightColors": {
|
||||
"background": "220 12% 96%",
|
||||
"foreground": "220 20% 14%",
|
||||
|
||||
BIN
preview.png
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 156 KiB |
|
Before Width: | Height: | Size: 94 KiB After Width: | Height: | Size: 222 KiB |
|
Before Width: | Height: | Size: 96 KiB After Width: | Height: | Size: 218 KiB |
|
Before Width: | Height: | Size: 68 KiB After Width: | Height: | Size: 407 KiB |
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 387 KiB |
@ -2,6 +2,7 @@
|
||||
"demo": true,
|
||||
"settings": {
|
||||
"merge": {
|
||||
"title": "Halcyon Robotics",
|
||||
"site_name": "Halcyon Robotics",
|
||||
"tagline": "Autonomy systems for the field."
|
||||
}
|
||||
@ -66,7 +67,7 @@
|
||||
"reconcile_blocks": true,
|
||||
"blocks": [
|
||||
{ "block_key": "heading", "title": "Title", "slot": "main", "sort_order": 1, "content": { "text": "Why we run the control loop at 1000 Hz", "level": 1 } },
|
||||
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>On a paved test track a 200 Hz loop tracks a path cleanly. On loose gravel the wheels slip between updates and the estimate lags the truth. The machine corrects late and starts to hunt.</p><p>At 1000 Hz the loop sees the slip inside a single wheel rotation. The correction is small and the path stays smooth. The cost is a tighter compute budget, which is why the runtime is deterministic rather than best effort.</p>" } }
|
||||
{ "block_key": "text", "title": "Body", "slot": "main", "sort_order": 2, "content": { "text": "<p>On a paved test track a 200 Hz loop tracks a path cleanly. On loose gravel the wheels slip between updates and the estimate lags the truth. The machine corrects late and starts to hunt.</p><p>At 1000 Hz the loop sees the slip inside a single wheel rotation. The correction is small and the path stays smooth. The cost is a tighter compute budget, which is why the runtime is deterministic rather than best effort.</p><p>The first version of the platform ran the controller at 250 Hz. It looked fine in the lab and on the paved apron behind the workshop. The trouble started the first time we drove it across a dry creek bed.</p><p>On that surface the tyres broke traction for tens of milliseconds at a time. Between control updates the vehicle rotated a few degrees more than the model expected, and by the time the next update landed the correction it applied was already stale. The machine oscillated around the path instead of settling on it.</p><p>We spent a week convinced the problem was the estimator. It was not. The state estimate was accurate to within the sensor noise floor. The gap was time: the world was changing faster than we were reading it back.</p><p>Raising the loop rate is not free. Every doubling halves the time budget for the estimator, the planner, and the control solve. At 1000 Hz the whole chain has to finish in under a millisecond, every millisecond, with no exceptions and no pauses to collect memory.</p><p>That constraint is why the runtime is written the way it is. Memory is allocated once at start-up. The control thread is pinned to an isolated core. The scheduler is deterministic rather than best effort, so a slow frame is a bug we can catch instead of a statistic we learn to tolerate.</p><p>The payoff shows up on exactly the surfaces that used to break it. On gravel and loose dirt the corrections are now small and continuous. The path traces stay inside a few centimetres of the reference even when the tyres are slipping the whole way through a turn.</p><p>There is a ceiling to this. Past a certain rate the actuators cannot respond any faster and the extra updates buy nothing but heat. For our drivetrain that ceiling sits a little above 1000 Hz, which is why we stopped there rather than chase a larger number for its own sake.</p><p>If you are specifying a system like this, do not ask a vendor for their loop rate in isolation. Ask what surface they measured on, what the worst-case frame time is, and what happens on the frame that runs long. Those three answers tell you far more than the headline figure.</p>" } }
|
||||
]
|
||||
},
|
||||
{
|
||||
|
||||
@ -1 +1 @@
|
||||
{% if context.currentAuthor %}<section class="sc-section {{ class }}" data-block="scifi-clean:author-bio-hero"><div class="sc-content-well"><div class="sc-measure sc-stack" style="text-align: center; margin-left: auto; margin-right: auto;">{% if showAvatar and context.currentAuthor.avatarUrl %}<img class="sc-avatar" src="{{ context.currentAuthor.avatarUrl }}" alt="{{ context.currentAuthor.displayName }}" style="width: 6rem; height: 6rem; border-radius: 9999px; object-fit: cover; margin-left: auto; margin-right: auto;">{% else %}<span class="sc-mark sc-mark-ring" aria-hidden="true" style="margin-left: auto; margin-right: auto;"></span>{% endif %}<h1 class="sc-display" style="margin: 0;">{{ context.currentAuthor.displayName }}</h1>{% if context.currentAuthor.bio %}<p class="sc-lede" style="margin: 0;">{{ context.currentAuthor.bio }}</p>{% endif %}{% if showSocial %}<div class="sc-btn-row" style="justify-content: center;">{% if context.currentAuthor.websiteUrl %}<a class="sc-btn-ghost sc-btn-sm" href="{{ context.currentAuthor.websiteUrl }}" target="_blank" rel="noopener noreferrer" title="Website"><svg class="sc-icon" aria-hidden="true"><use href="/icons/lucide.svg#globe"/></svg></a>{% endif %}{% if context.currentAuthor.twitterHandle %}<a class="sc-btn-ghost sc-btn-sm" href="https://twitter.com/{{ context.currentAuthor.twitterHandle|cut:"@" }}" target="_blank" rel="noopener noreferrer" title="X"><svg class="sc-icon" aria-hidden="true"><use href="/icons/simple-icons.svg#x"/></svg></a>{% endif %}{% if context.currentAuthor.linkedinUrl %}<a class="sc-btn-ghost sc-btn-sm" href="{{ context.currentAuthor.linkedinUrl }}" target="_blank" rel="noopener noreferrer" title="LinkedIn"><svg class="sc-icon" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"/></svg></a>{% endif %}</div>{% endif %}</div></div></section>{% endif %}
|
||||
{% if context.currentAuthor %}<section class="sc-section {{ class }}" data-block="scifi-clean:author-bio-hero"><div class="sc-content-well"><div class="sc-measure sc-stack" style="text-align: center; margin-left: auto; margin-right: auto;">{% if showAvatar and context.currentAuthor.avatarUrl %}<img class="sc-avatar" src="{{ context.currentAuthor.avatarUrl }}" alt="{{ context.currentAuthor.displayName }}" style="width: 6rem; height: 6rem; border-radius: 9999px; object-fit: cover; margin-left: auto; margin-right: auto;">{% else %}<span class="sc-mark sc-mark-ring" aria-hidden="true" style="margin-left: auto; margin-right: auto;"></span>{% endif %}<h1 class="sc-display" style="margin: 0;">{{ context.currentAuthor.displayName }}</h1>{% if context.currentAuthor.bio %}<p class="sc-lede" style="margin: 0;">{{ context.currentAuthor.bio }}</p>{% endif %}{% if showSocial %}<div class="sc-btn-row" style="justify-content: center;">{% if context.currentAuthor.websiteUrl %}<a class="sc-btn-ghost sc-btn-sm" href="{{ context.currentAuthor.websiteUrl }}" target="_blank" rel="noopener noreferrer" title="Website"><svg class="sc-icon" width="16" height="16" aria-hidden="true"><use href="/icons/lucide.svg#globe"/></svg></a>{% endif %}{% if context.currentAuthor.twitterHandle %}<a class="sc-btn-ghost sc-btn-sm" href="https://twitter.com/{{ context.currentAuthor.twitterHandle|cut:"@" }}" target="_blank" rel="noopener noreferrer" title="X"><svg class="sc-icon" width="16" height="16" aria-hidden="true"><use href="/icons/simple-icons.svg#x"/></svg></a>{% endif %}{% if context.currentAuthor.linkedinUrl %}<a class="sc-btn-ghost sc-btn-sm" href="{{ context.currentAuthor.linkedinUrl }}" target="_blank" rel="noopener noreferrer" title="LinkedIn"><svg class="sc-icon" width="16" height="16" aria-hidden="true"><use href="/icons/simple-icons.svg#linkedin"/></svg></a>{% endif %}</div>{% endif %}</div></div></section>{% endif %}
|
||||
|
||||
@ -30,5 +30,5 @@
|
||||
{% endwith %}
|
||||
{% endif %}
|
||||
</div></section>
|
||||
<style>#contact-form-{{ bid }} .sc-form-grid{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.25rem;margin-top:2rem;}@media (max-width:640px){#contact-form-{{ bid }} .sc-form-grid{grid-template-columns:1fr;}}#contact-form-{{ bid }} .sc-field--full{grid-column:1 / -1;}#contact-form-{{ bid }} .sc-label{display:block;margin-bottom:0.375rem;font-size:0.8125rem;font-weight:500;color:hsl(var(--foreground));font-family:var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);}#contact-form-{{ bid }} .sc-input{width:100%;padding:0.5rem 0.75rem;font-size:0.9375rem;font-family:var(--font-body, "Inter", system-ui, sans-serif);color:hsl(var(--foreground));background:hsl(var(--background));border:1px solid hsl(var(--input));border-radius:4px;box-sizing:border-box;}#contact-form-{{ bid }} .sc-input:focus{outline:2px solid hsl(var(--ring));outline-offset:1px;}</style>
|
||||
<style>#contact-form-{{ bid }} .sc-form-grid{display:grid;grid-template-columns:repeat(2, minmax(0, 1fr));gap:1.25rem;margin-top:2rem;}@media (max-width:640px){ #contact-form-{{ bid }} .sc-form-grid{grid-template-columns:1fr;}}#contact-form-{{ bid }} .sc-field--full{grid-column:1 / -1;}#contact-form-{{ bid }} .sc-label{display:block;margin-bottom:0.375rem;font-size:0.8125rem;font-weight:500;color:hsl(var(--foreground));font-family:var(--font-heading, "Inter Tight", "Inter", system-ui, sans-serif);}#contact-form-{{ bid }} .sc-input{width:100%;padding:0.5rem 0.75rem;font-size:0.9375rem;font-family:var(--font-body, "Inter", system-ui, sans-serif);color:hsl(var(--foreground));background:hsl(var(--background));border:1px solid hsl(var(--input));border-radius:4px;box-sizing:border-box;}#contact-form-{{ bid }} .sc-input:focus{outline:2px solid hsl(var(--ring));outline-offset:1px;}</style>
|
||||
{% endwith %}
|
||||
|
||||
@ -1,3 +1,3 @@
|
||||
<nav class="{{ class }}" data-block="scifi-clean:sidebar-nav" style="{% if showBorder %}border-right: 1px solid hsl(var(--border)); padding-right: 1rem;{% endif %}">{% if title %}<h3 class="sc-kicker" style="margin-top: 0;">{{ title }}</h3>{% endif %}<ul class="sc-stack-sm" style="list-style: none; margin: 0; padding: 0;">{% for item in menu.items %}<li><a class="sc-sidebar-link" href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link>{% if item.icon %}<svg class="sc-icon" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"/></svg>{% endif %}{{ item.label }}</a></li>{% endfor %}</ul>{% if highlightCurrent %}<script>
|
||||
<nav class="{{ class }}" data-block="scifi-clean:sidebar-nav" style="{% if showBorder %}border-right: 1px solid hsl(var(--border)); padding-right: 1rem;{% endif %}">{% if title %}<h3 class="sc-kicker" style="margin-top: 0;">{{ title }}</h3>{% endif %}<ul class="sc-stack-sm" style="list-style: none; margin: 0; padding: 0;">{% for item in menu.items %}<li><a class="sc-sidebar-link" href="{{ item.url|default:'#' }}"{% if item.open_in_new_tab %} target="_blank" rel="noopener"{% endif %}{% if item.title %} title="{{ item.title }}"{% endif %} data-bn-sidebar-link>{% if item.icon %}<svg class="sc-icon" width="16" height="16" aria-hidden="true"><use href="/icons/{{ item.icon|split:":"|first }}.svg#{{ item.icon|split:":"|last }}"/></svg>{% endif %}{{ item.label }}</a></li>{% endfor %}</ul>{% if highlightCurrent %}<script>
|
||||
(function(){var p=window.location.pathname;document.querySelectorAll('[data-bn-sidebar-link]').forEach(function(a){if(a.getAttribute('href')===p){a.setAttribute('aria-current','page');}});})();
|
||||
</script>{% endif %}</nav>
|
||||
|
||||
@ -33,8 +33,15 @@
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px' });
|
||||
items.forEach(function(el){ io.observe(el); });
|
||||
/* Reveal anything already on screen now: first-screen readouts must not
|
||||
wait on the async observer or a scroll (fixes ghosted above-the-fold
|
||||
content in no-scroll captures / IO gaps). */
|
||||
requestAnimationFrame(function(){
|
||||
var vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
items.forEach(function(el){ if (el.getBoundingClientRect().top < vh) { el.classList.add("sc-in"); io.unobserve(el); } });
|
||||
});
|
||||
/* Safety net: guarantee full reveal for no-scroll captures / IO gaps. */
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 1600);
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 400);
|
||||
} else {
|
||||
items.forEach(function(el){ el.classList.add('sc-in'); });
|
||||
}
|
||||
|
||||
@ -30,8 +30,15 @@
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px' });
|
||||
items.forEach(function(el){ io.observe(el); });
|
||||
/* Reveal anything already on screen now: first-screen readouts must not
|
||||
wait on the async observer or a scroll (fixes ghosted above-the-fold
|
||||
content in no-scroll captures / IO gaps). */
|
||||
requestAnimationFrame(function(){
|
||||
var vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
items.forEach(function(el){ if (el.getBoundingClientRect().top < vh) { el.classList.add("sc-in"); io.unobserve(el); } });
|
||||
});
|
||||
/* Safety net: guarantee full reveal for no-scroll captures / IO gaps. */
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 1600);
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 400);
|
||||
} else {
|
||||
items.forEach(function(el){ el.classList.add('sc-in'); });
|
||||
}
|
||||
|
||||
@ -36,8 +36,15 @@
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px' });
|
||||
items.forEach(function(el){ io.observe(el); });
|
||||
/* Reveal anything already on screen now: first-screen readouts must not
|
||||
wait on the async observer or a scroll (fixes ghosted above-the-fold
|
||||
content in no-scroll captures / IO gaps). */
|
||||
requestAnimationFrame(function(){
|
||||
var vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
items.forEach(function(el){ if (el.getBoundingClientRect().top < vh) { el.classList.add("sc-in"); io.unobserve(el); } });
|
||||
});
|
||||
/* Safety net: guarantee full reveal for no-scroll captures / IO gaps. */
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 1600);
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 400);
|
||||
} else {
|
||||
items.forEach(function(el){ el.classList.add('sc-in'); });
|
||||
}
|
||||
|
||||
@ -32,8 +32,15 @@
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px' });
|
||||
items.forEach(function(el){ io.observe(el); });
|
||||
/* Reveal anything already on screen now: first-screen readouts must not
|
||||
wait on the async observer or a scroll (fixes ghosted above-the-fold
|
||||
content in no-scroll captures / IO gaps). */
|
||||
requestAnimationFrame(function(){
|
||||
var vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
items.forEach(function(el){ if (el.getBoundingClientRect().top < vh) { el.classList.add("sc-in"); io.unobserve(el); } });
|
||||
});
|
||||
/* Safety net: guarantee full reveal for no-scroll captures / IO gaps. */
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 1600);
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 400);
|
||||
} else {
|
||||
items.forEach(function(el){ el.classList.add('sc-in'); });
|
||||
}
|
||||
|
||||
@ -28,8 +28,15 @@
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px' });
|
||||
items.forEach(function(el){ io.observe(el); });
|
||||
/* Reveal anything already on screen now: first-screen readouts must not
|
||||
wait on the async observer or a scroll (fixes ghosted above-the-fold
|
||||
content in no-scroll captures / IO gaps). */
|
||||
requestAnimationFrame(function(){
|
||||
var vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
items.forEach(function(el){ if (el.getBoundingClientRect().top < vh) { el.classList.add("sc-in"); io.unobserve(el); } });
|
||||
});
|
||||
/* Safety net: guarantee full reveal for no-scroll captures / IO gaps. */
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 1600);
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 400);
|
||||
} else {
|
||||
items.forEach(function(el){ el.classList.add('sc-in'); });
|
||||
}
|
||||
|
||||
@ -30,8 +30,15 @@
|
||||
});
|
||||
}, { rootMargin: '0px 0px -8% 0px' });
|
||||
items.forEach(function(el){ io.observe(el); });
|
||||
/* Reveal anything already on screen now: first-screen readouts must not
|
||||
wait on the async observer or a scroll (fixes ghosted above-the-fold
|
||||
content in no-scroll captures / IO gaps). */
|
||||
requestAnimationFrame(function(){
|
||||
var vh = window.innerHeight || document.documentElement.clientHeight;
|
||||
items.forEach(function(el){ if (el.getBoundingClientRect().top < vh) { el.classList.add("sc-in"); io.unobserve(el); } });
|
||||
});
|
||||
/* Safety net: guarantee full reveal for no-scroll captures / IO gaps. */
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 1600);
|
||||
setTimeout(function(){ items.forEach(function(el){ el.classList.add("sc-in"); }); }, 400);
|
||||
} else {
|
||||
items.forEach(function(el){ el.classList.add('sc-in'); });
|
||||
}
|
||||
|
||||