Loading splash: animated logo over a dark overlay on desktop
index.html carries the overlay markup and styles inline so it paints before any script; public/splash.js (external, for the CSP) plays the logo animation from icons/ultimo-splash.html, holds, fades out after ~3.25 s and removes itself. Skipped at phone sizes (same query as useIsMobile) and in pop-out compose windows; a click skips it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,237 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title>Ultimo Splash</title>
|
||||
<style>
|
||||
/* ---- Splash styles (copy into your site) ---------------------------- */
|
||||
:root { --splash-bg: #0f0f12; }
|
||||
#ultimo-splash {
|
||||
position: fixed; inset: 0; z-index: 9999;
|
||||
display: grid; place-items: center;
|
||||
background: var(--splash-bg);
|
||||
transition: opacity .35s ease, visibility 0s linear .35s;
|
||||
cursor: pointer;
|
||||
}
|
||||
#ultimo-splash.is-done { opacity: 0; visibility: hidden; pointer-events: none; }
|
||||
#ultimo-splash svg { width: min(46vmin, 340px); height: auto; overflow: visible; }
|
||||
|
||||
/* ---- Demo page only (remove on your site) ---------------------------- */
|
||||
html, body { margin: 0; height: 100%; }
|
||||
body { font-family: system-ui, sans-serif; background: #f6f6f7; color: #1b1b1f;
|
||||
display: grid; place-items: center; }
|
||||
.demo { text-align: center; }
|
||||
.demo button { font: inherit; padding: 12px 20px; border-radius: 10px; border: 1px solid #c9c9cf;
|
||||
background: #fff; cursor: pointer; min-height: 44px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- ======================= SPLASH MARKUP (copy) ======================= -->
|
||||
<div id="ultimo-splash" aria-hidden="true">
|
||||
<svg viewBox="-10 -6 65 56" xmlns="http://www.w3.org/2000/svg">
|
||||
<defs>
|
||||
<linearGradient id="ul-g1" x1="54.340946" y1="122.84999" x2="54.340946" y2="162.66415" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.75684298,0,0,0.80151105,-19.989667,-137.1)">
|
||||
<stop offset="0.20198774" stop-color="#f89629"/><stop offset="1" stop-color="#f74176"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="ul-r1" cx="8.0343275" cy="13.452971" fx="8.0343275" fy="13.452971" r="16.678839" gradientUnits="userSpaceOnUse" gradientTransform="matrix(-0.29273442,-0.00257918,0.01127876,-1.2801289,10.212159,-15.400302)">
|
||||
<stop offset="0" stop-color="#fa4435" stop-opacity="1"/><stop offset="1" stop-color="#fa7367" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<radialGradient id="ul-r2" cx="34.523952" cy="11.253304" fx="34.523952" fy="11.253304" r="16.740065" gradientUnits="userSpaceOnUse" gradientTransform="matrix(0.27572675,-0.00557307,0.0193391,0.95679742,24.457243,-42.568342)">
|
||||
<stop offset="0" stop-color="#fadc56" stop-opacity="1"/><stop offset="1" stop-color="#faae3a" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="ul-gs" x1="0" y1="5.11" x2="0" y2="37.01" gradientUnits="userSpaceOnUse">
|
||||
<stop offset="0.202" stop-color="#f89629"/><stop offset="1" stop-color="#f74176"/>
|
||||
</linearGradient>
|
||||
<filter id="ul-sh" x="-30%" y="-30%" width="160%" height="160%">
|
||||
<feDropShadow dx="0" dy="0.35" stdDeviation="0.5" flood-color="#000" flood-opacity="0.42"/>
|
||||
</filter>
|
||||
<filter id="ul-blur" x="-40%" y="-40%" width="180%" height="180%">
|
||||
<feGaussianBlur stdDeviation="1.1"/>
|
||||
</filter>
|
||||
<g id="ul-logo" transform="translate(1.425541,43.741704)">
|
||||
<path id="ul-main" fill="url(#ul-g1)" d="m 35.878324,-39.647594 c -2.15234,-7.4e-4 -4.0678,1.84724 -4.0671,4.1266 v 2.85212 c -0.073,-0.005 -0.14656,-0.007 -0.2202,-0.007 h -10.50012 -10.49855 c -0.0418,-7e-5 -0.0835,5.9e-4 -0.12516,0.002 v -2.78918 c -1.6e-4,-2.27807 -1.9592603,-4.12477 -4.1103803,-4.12494 -2.15111,1.8e-4 -4.20874,1.84687 -4.2089,4.12494 v 15.55127 c -4e-5,0.005 -0.0295,3.84339 2.173,7.60498 2.20396,3.7640196 7.0382003,7.3825696 14.4526703,7.3825696 h 4.58067 c 7.4177,0 12.26918,-3.66617 14.50586,-7.4422096 2.23668,-3.77604 2.27039,-7.63481 2.27039,-7.63481 l 0.002,-0.0344 v -15.48541 c 7e-4,-2.27872 -2.10202,-4.12642 -4.25374,-4.1266 z m -2.00792,7.9252 v 11.59858 0.0286 c 0,0 -0.0664,2.0824 -1.31139,4.18416 -0.57202,0.96572 -1.44541,2.15762 -2.91767,2.99793 -1.47227,0.8403 -3.46066,1.33079 -6.27223,1.33079 h -4.58106 c -2.82381,0 -4.79864,-0.47938 -6.25697,-1.30429 -1.45833,-0.8249 -2.31357,-2.00343 -2.8590102,-2.93496 -1.1966401,-2.04365 -1.2300401,-4.07193 -1.2300401,-4.07193 l -10e-4,-0.0141 v -0.0157 -11.69637 l 1.96e-4,-0.0142 0.0035,-0.008 c 0.05128,0.0487 0.18806,0.16304 0.24192,0.20765 l 5.4247013,4.54513 5.51504,4.57144 c 0.65194,0.53945 2.27903,0.53945 2.93097,0 l 5.51504,-4.57144 5.487378,-4.57351 c 0.0199,-0.0164 0.04083,-0.0331 0.06003,-0.048 l 0.02765,-0.0215 z"/>
|
||||
<path fill="url(#ul-r1)" d="m 2.3483903,-36.677919 c -0.1292796,0.384121 -0.2004748,0.792004 -0.2005045,1.214913 v 15.550989 c -4e-5,0.005 -0.029506,3.84363 2.172994,7.60522 2.20396,3.7640196 7.0383952,7.3824946 14.4528652,7.3824946 h 4.580598 c 5.433936,0 9.490679,-1.96731 12.151197,-4.504117 -1.517177,-1.2182566 -3.218664,-2.5953336 -5.013648,-4.0550496 -0.258984,0.201994 -0.541601,0.394146 -0.850594,0.570508 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -1.45833,-0.8249 -2.313817,-2.003692 -2.8592572,-2.935222 -1.1966406,-2.04365 -1.2298996,-4.071586 -1.2298996,-4.071586 l -0.00103,-0.01395 v -0.01602 -11.696444 -0.01395 l 0.00362,-0.0083 c 0.05128,0.0487 0.1879857,0.16313 0.2418457,0.20774 l 5.4249921,4.545459 5.514909,4.571297 c 0.0844,0.06984 0.185312,0.130661 0.298173,0.182418 -5.07081,-4.173874 -9.542211,-7.882397 -11.4706242,-9.500169 -2.2977093,-1.895939 -4.3807977,-3.616666 -6.1055705,-5.042586 z"/>
|
||||
<path fill="url(#ul-r2)" d="m 39.930076,-36.730629 c -1.718284,1.420541 -3.7895,3.131915 -6.072498,5.015715 -1.93499,1.62329 -6.430112,5.351263 -11.522294,9.54257 0.08232,-0.04302 0.156783,-0.0915 0.221692,-0.145211 l 5.514909,-4.571297 5.48752,-4.573881 c 0.0199,-0.0164 0.04075,-0.03316 0.05994,-0.04806 l 0.0279,-0.02119 0.222726,-0.190169 v 11.598258 0.02894 c 0,0 -0.06656,2.081964 -1.311548,4.183724 -0.57202,0.96572 -1.445392,2.157957 -2.917652,2.998267 -1.47227,0.8403 -3.460398,1.330668 -6.271968,1.330668 h -4.581116 c -2.82381,0 -4.798652,-0.479403 -6.256982,-1.304313 -0.308682,-0.174605 -0.590588,-0.364744 -0.848011,-0.565341 -1.8055532,1.467965 -3.5132252,2.849693 -5.0307004,4.0674516 2.6425302,2.518552 6.6894504,4.460193 12.1212244,4.460193 h 4.580599 c 7.4177,0 12.269411,-3.665883 14.506091,-7.4419226 2.23668,-3.77604 2.270146,-7.635193 2.270146,-7.635193 l 0.0021,-0.03411 v -15.485354 c 1.06e-4,-0.420906 -0.07162,-0.827177 -0.202078,-1.209745 z"/>
|
||||
</g>
|
||||
<!-- Mask that reveals the logo's U as the stroke draws -->
|
||||
<mask id="ul-mask-u" maskUnits="userSpaceOnUse" x="-20" y="-20" width="100" height="100">
|
||||
<path id="ul-stroke" d="M7.73,8.31 L7.73,23.8 C7.73,30.6 13.6,34.65 22.57,34.65 C31.5,34.65 37.4,30.6 37.4,23.8 L37.4,8.31"
|
||||
pathLength="1" fill="none" stroke="#fff" stroke-width="9.6" stroke-linecap="round"
|
||||
stroke-dasharray="1 2" stroke-dashoffset="1"/>
|
||||
</mask>
|
||||
<!-- Logo colours limited to the top-flap area -->
|
||||
<!-- Area between the U's arms: inside it the logo's own fill is exactly the flap -->
|
||||
<clipPath id="ul-clip-flap"><path d="M10.093,11.066 L35.296,11.066 L35.296,12.1 L22.69,23.6 L10.093,12.1 Z"/></clipPath>
|
||||
<!-- Darkens the coloured flap while it is mid-fold -->
|
||||
<filter id="ul-dark" x="0" y="0" width="1" height="1">
|
||||
<feComponentTransfer>
|
||||
<feFuncR id="ul-dr" type="linear" slope="1"/><feFuncG id="ul-dg" type="linear" slope="1"/><feFuncB id="ul-db" type="linear" slope="1"/>
|
||||
</feComponentTransfer>
|
||||
</filter>
|
||||
</defs>
|
||||
|
||||
<!-- Envelope (white/grey paper) -->
|
||||
<g id="ul-env">
|
||||
<g id="ul-rest">
|
||||
<!-- Thin paper margin tucked under the U so no background shows along its inner edge -->
|
||||
<rect id="ul-under" x="9.8" y="12.1" width="25.8" height="20.4" fill="rgb(250,250,253)" opacity="0" mask="url(#ul-mask-u)"/>
|
||||
<rect x="10.093" y="11.066" width="25.203" height="21.064" fill="rgb(226,226,230)"/>
|
||||
<path id="ul-fl" filter="url(#ul-sh)"/>
|
||||
<path id="ul-fr" filter="url(#ul-sh)"/>
|
||||
<path id="ul-fb" filter="url(#ul-sh)"/>
|
||||
</g>
|
||||
<!-- Top flap: exact outline of the logo's V flap (shown folded); flipped about the top edge while folding -->
|
||||
<!-- Inside of the flap = white paper; outside = the logo's colours (clipped to the flap) -->
|
||||
<g id="ul-ftg" filter="url(#ul-sh)">
|
||||
<path id="ul-ft"
|
||||
d="M10.093,11.066 L35.296,11.066 L35.296,12.019 L35.208,12.089 L24.206,21.234 C23.554,21.773 21.927,21.773 21.275,21.234 L10.335,12.117 L10.093,11.910 Z"/>
|
||||
<g id="ul-fto" opacity="0" filter="url(#ul-dark)">
|
||||
<use href="#ul-logo" clip-path="url(#ul-clip-flap)"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- U drawn over the envelope: the real logo artwork (with its side gradients) revealed by a growing stroke -->
|
||||
<g id="ul-ug" opacity="0">
|
||||
<use href="#ul-logo" mask="url(#ul-mask-u)"/>
|
||||
</g>
|
||||
|
||||
<!-- Final logo + glow -->
|
||||
<use id="ul-glow" href="#ul-logo" filter="url(#ul-blur)" opacity="0"/>
|
||||
<use id="ul-final" href="#ul-logo" opacity="0"/>
|
||||
</svg>
|
||||
</div>
|
||||
<!-- ===================================================================== -->
|
||||
|
||||
<!-- Demo page content (remove on your site) -->
|
||||
<main class="demo">
|
||||
<p>Your website content</p>
|
||||
<button type="button" id="replay">Replay intro</button>
|
||||
</main>
|
||||
|
||||
<script>
|
||||
/* ======================= SPLASH SCRIPT (copy) ======================== */
|
||||
(function () {
|
||||
var ONCE_PER_SESSION = false; // true = show only on the first page view per browser session
|
||||
var SPEED = 1; // 1 = ~2.6 s to final logo; 0.8 = faster, 1.2 = slower
|
||||
var HOLD_MS = 450; // how long the finished logo stays before the splash fades
|
||||
|
||||
var root = document.getElementById('ultimo-splash');
|
||||
if (!root) return;
|
||||
var $ = function (id) { return document.getElementById(id); };
|
||||
var env = $('ul-env'), rest = $('ul-rest'), fl = $('ul-fl'), fr = $('ul-fr'), fb = $('ul-fb'), ft = $('ul-ft');
|
||||
var stroke = $('ul-stroke'), glow = $('ul-glow'), fin = $('ul-final');
|
||||
var ug = $('ul-ug'), under = $('ul-under');
|
||||
var ftg = $('ul-ftg'), fto = $('ul-fto'), dr = $('ul-dr'), dg = $('ul-dg'), db = $('ul-db');
|
||||
|
||||
// Envelope body edges (logo coordinates) and the four flaps.
|
||||
var L = 10.093, R = 35.296, T = 11.066, B = 32.13, CX = 22.69, CY = 21.6;
|
||||
var FLAPS = {
|
||||
l: { axis: 'x', h: L, a: T, b: B, mid: CY, depth: 12.2, dir: -1, r: 3.2 },
|
||||
r: { axis: 'x', h: R, a: T, b: B, mid: CY, depth: 12.2, dir: 1, r: 3.2 },
|
||||
b: { axis: 'y', h: B, a: L, b: R, mid: CX, depth: 14.0, dir: 1, r: 3.6 }
|
||||
};
|
||||
var END = 2450; // ms (at SPEED 1) when the logo is fully formed
|
||||
|
||||
function clamp(x) { return x < 0 ? 0 : x > 1 ? 1 : x; }
|
||||
function seg(t, a, b) { return clamp((t - a) / (b - a)); }
|
||||
function ease(x) { return x < .5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2; }
|
||||
function easeOut(x) { return 1 - Math.pow(1 - x, 3); }
|
||||
function n(v) { return Math.round(v * 1000) / 1000; }
|
||||
|
||||
// Flap outline: base fixed exactly on the body edge, apex swings over the hinge, tip rounded.
|
||||
function flapD(f, p) {
|
||||
var s = Math.cos(Math.PI * p), ax, ay, A, C;
|
||||
if (f.axis === 'x') { ax = f.h + f.dir * f.depth * s; ay = f.mid; A = [f.h, f.a]; C = [f.h, f.b]; }
|
||||
else { ax = f.mid; ay = f.h + f.dir * f.depth * s; A = [f.a, f.h]; C = [f.b, f.h]; }
|
||||
function toward(P) {
|
||||
var dx = P[0] - ax, dy = P[1] - ay, l = Math.hypot(dx, dy) || 1, r = Math.min(f.r, l * 0.45);
|
||||
return n(ax + dx * r / l) + ',' + n(ay + dy * r / l);
|
||||
}
|
||||
return 'M' + A[0] + ',' + A[1] + ' L' + toward(A) + ' Q' + n(ax) + ',' + n(ay) + ' ' + toward(C) +
|
||||
' L' + C[0] + ',' + C[1] + ' Z';
|
||||
}
|
||||
// Paper shading: inner face (grey) before the flip, outer face (white) after it.
|
||||
function faceC(p) {
|
||||
var lift = Math.sin(Math.PI * p);
|
||||
var c = Math.round(p < .5 ? 230 - 42 * lift : 250 - 36 * lift);
|
||||
return 'rgb(' + c + ',' + c + ',' + (c + 3) + ')';
|
||||
}
|
||||
function setFlap(el, f, p) {
|
||||
el.setAttribute('d', flapD(f, p));
|
||||
el.setAttribute('fill', faceC(p));
|
||||
}
|
||||
|
||||
function render(ms) {
|
||||
var intro = easeOut(seg(ms, 0, 220));
|
||||
setFlap(fl, FLAPS.l, ease(seg(ms, 180, 560)));
|
||||
setFlap(fr, FLAPS.r, ease(seg(ms, 240, 620)));
|
||||
setFlap(fb, FLAPS.b, ease(seg(ms, 560, 920)));
|
||||
var pt = ease(seg(ms, 880, 1260)), st = -Math.cos(Math.PI * pt);
|
||||
if (Math.abs(st) < 0.001) st = 0.001;
|
||||
// Top flap: white inside while open; once it flips past vertical its outside (logo colours) shows
|
||||
ftg.setAttribute('transform', 'translate(0 ' + T + ') scale(1 ' + n(st) + ') translate(0 ' + (-T) + ')');
|
||||
ft.setAttribute('fill', faceC(pt));
|
||||
ft.setAttribute('opacity', pt >= 0.5 ? 0 : 1); // white inside face only while it faces the viewer
|
||||
fto.setAttribute('opacity', pt >= 0.5 ? 1 : 0);
|
||||
var slope = n(1 - 0.35 * Math.sin(Math.PI * pt));
|
||||
dr.setAttribute('slope', slope); dg.setAttribute('slope', slope); db.setAttribute('slope', slope);
|
||||
|
||||
var draw = ease(seg(ms, 1180, 1800));
|
||||
var fade = ease(seg(ms, 2150, 2450)); // white envelope (all but the top flap) vanishes after the logo has formed
|
||||
var dis = ease(seg(ms, 1800, 2150)); // dissolve: U stroke + top flap -> final logo
|
||||
var sc = 0.9 + 0.1 * intro;
|
||||
env.setAttribute('transform', 'translate(22.5 22) scale(' + sc + ') translate(-22.5 -22)');
|
||||
env.setAttribute('opacity', intro);
|
||||
rest.setAttribute('opacity', 1 - fade);
|
||||
ftg.setAttribute('opacity', 1 - fade); // coloured flap sits under the logo until the envelope goes
|
||||
stroke.setAttribute('stroke-dashoffset', 1 - draw);
|
||||
// Stroke stays solid under the logo while it dissolves in, then is removed once fully covered
|
||||
ug.setAttribute('opacity', draw > 0 && dis < 1 ? 1 : 0);
|
||||
under.setAttribute('opacity', draw > 0 ? 1 : 0);
|
||||
fin.setAttribute('opacity', dis);
|
||||
glow.setAttribute('opacity', 0.35 * Math.sin(Math.PI * seg(ms, 1900, 2450)));
|
||||
}
|
||||
|
||||
var raf = 0, start = 0, hideTimer = 0;
|
||||
function finish() {
|
||||
cancelAnimationFrame(raf);
|
||||
render(99999);
|
||||
root.classList.add('is-done');
|
||||
document.dispatchEvent(new CustomEvent('ultimo-splash:done'));
|
||||
}
|
||||
function tick(now) {
|
||||
if (!start) start = now;
|
||||
var ms = (now - start) / SPEED;
|
||||
render(ms);
|
||||
if (ms < END) raf = requestAnimationFrame(tick);
|
||||
else hideTimer = setTimeout(finish, HOLD_MS);
|
||||
}
|
||||
function play() {
|
||||
cancelAnimationFrame(raf); clearTimeout(hideTimer);
|
||||
root.classList.remove('is-done');
|
||||
start = 0; render(0);
|
||||
if (window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
render(99999); hideTimer = setTimeout(finish, 700); return;
|
||||
}
|
||||
raf = requestAnimationFrame(tick);
|
||||
}
|
||||
|
||||
try {
|
||||
if (ONCE_PER_SESSION && sessionStorage.getItem('ultimo-splash-seen')) { root.classList.add('is-done'); return; }
|
||||
sessionStorage.setItem('ultimo-splash-seen', '1');
|
||||
} catch (e) {}
|
||||
|
||||
root.addEventListener('click', finish); // click / tap skips the intro
|
||||
window.ultimoSplash = { play: play, skip: finish, seek: function (ms) { cancelAnimationFrame(raf); clearTimeout(hideTimer); root.classList.remove('is-done'); render(ms); } };
|
||||
play();
|
||||
})();
|
||||
/* ===================================================================== */
|
||||
|
||||
// Demo only
|
||||
document.getElementById('replay').addEventListener('click', function () { window.ultimoSplash.play(); });
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user