AI-Studio/app/Tests/Tools/Fixtures/standalone_page.html
Peer Hogeterp fda42ac24b
Improved exports (#993)
Co-authored-by: Thorsten Sommer <SommerEngineering@users.noreply.github.com>
2026-09-23 15:02:33 +02:00

201 lines
9.7 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>Hello World</title>
<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=Fraunces:opsz,wght@9..144,300..900&display=swap" rel="stylesheet">
<style>
:root{
--bg:#0a0f10; --bone:#f4eee3; --ember:#e2653a; --mint:#7ac9b0;
--mx:50%; --my:50%; --gx:50%; --gy:50%;
}
*{box-sizing:border-box}
html,body{height:100%}
body{margin:0;overflow:hidden;background:var(--bg);color:var(--bone);
font-family:"IBM Plex Sans",system-ui,sans-serif;-webkit-font-smoothing:antialiased}
.stage{position:fixed;inset:0;display:grid;place-items:center;isolation:isolate}
.stage > *{grid-area:1/1}
.field{
position:absolute;inset:-20%;z-index:0;filter:blur(6px);
background:
radial-gradient(46% 40% at 24% 28%, rgba(226,101,58,.20), transparent 68%),
radial-gradient(52% 46% at 78% 72%, rgba(122,201,176,.16), transparent 70%),
radial-gradient(80% 70% at 50% 50%, #121a1b, #070b0c 78%);
animation:breathe 18s ease-in-out infinite alternate;
}
@keyframes breathe{
from{transform:scale(1) translate(-1%,1%)}
to{transform:scale(1.08) translate(1.5%,-1.5%)}
}
.grid-base{
position:absolute;inset:0;z-index:1;opacity:.07;
background-image:
linear-gradient(rgba(244,238,227,.9) 1px,transparent 1px),
linear-gradient(90deg,rgba(244,238,227,.9) 1px,transparent 1px);
background-size:46px 46px;
-webkit-mask-image:radial-gradient(70% 70% at 50% 50%,#000,transparent);
mask-image:radial-gradient(70% 70% at 50% 50%,#000,transparent);
}
.grid-scan{
position:absolute;inset:0;z-index:2;
background-image:
linear-gradient(rgba(122,201,176,.55) 1px,transparent 1px),
linear-gradient(90deg,rgba(122,201,176,.55) 1px,transparent 1px);
background-size:46px 46px;
-webkit-mask-image:radial-gradient(230px 230px at var(--mx) var(--my),#000 0%,rgba(0,0,0,.35) 45%,transparent 72%);
mask-image:radial-gradient(230px 230px at var(--mx) var(--my),#000 0%,rgba(0,0,0,.35) 45%,transparent 72%);
}
.glow{position:absolute;inset:0;z-index:2;mix-blend-mode:screen;
background:radial-gradient(280px 280px at var(--gx) var(--gy),rgba(226,101,58,.22),transparent 65%)}
.sweep{
position:absolute;inset:-30% -60%;z-index:2;pointer-events:none;filter:blur(22px);
background:linear-gradient(102deg,transparent 44%,rgba(244,238,227,.085) 50%,transparent 56%);
animation:sweep 15s linear infinite;
}
@keyframes sweep{from{transform:translateX(-26%)}to{transform:translateX(26%)}}
.tilt{position:relative;z-index:5;will-change:transform}
.hello{
position:relative;display:inline-block;margin:0;text-align:center;
font-family:"Fraunces",Georgia,serif;
font-variation-settings:"opsz" 120;font-weight:500;
font-size:clamp(2.4rem,12vw,10.5rem);line-height:.92;letter-spacing:-.025em;
animation:float 11s ease-in-out infinite alternate;
}
@keyframes float{from{transform:translateY(-.02em)}to{transform:translateY(.025em)}}
.ghost{
position:absolute;inset:0;pointer-events:none;color:transparent;
-webkit-text-stroke:.012em rgba(244,238,227,.20);
transform:translate(.045em,.045em);
animation:drift 13s ease-in-out infinite alternate;
}
.ghost.deep{-webkit-text-stroke:.01em rgba(122,201,176,.16);transform:translate(-.05em,-.035em);animation-duration:16s}
@keyframes drift{to{transform:translate(.075em,.02em)}}
.w{display:inline-block;white-space:nowrap}
.m{display:inline-block;overflow:hidden;padding-bottom:.1em;margin-bottom:-.1em;vertical-align:bottom}
.l{display:inline-block;transform:translateY(115%)}
.g{display:inline-block;transition:color .35s ease,transform .4s cubic-bezier(.2,.85,.2,1),text-shadow .4s ease}
.l:hover .g{color:var(--mint);transform:translateY(-.055em);text-shadow:0 0 26px rgba(122,201,176,.45)}
.caret{
display:inline-block;width:.055em;height:.72em;margin-left:.07em;vertical-align:-.02em;
background:var(--ember);box-shadow:0 0 22px rgba(226,101,58,.6);
animation:blink 1.15s steps(1,end) infinite;
}
@keyframes blink{0%,49%{opacity:1}50%,100%{opacity:0}}
body.play .l{animation:rise .82s var(--d) cubic-bezier(.16,1,.3,1) both}
@keyframes rise{from{transform:translateY(115%)}to{transform:translateY(0)}}
body.play .caret{animation:blink 1.15s steps(1,end) .95s infinite,caretin .5s .9s both}
@keyframes caretin{from{opacity:0;transform:scaleY(.2)}to{opacity:1;transform:scaleY(1)}}
body.play .ghost{animation:ghostin 1.4s .15s ease both,drift 13s 1.6s ease-in-out infinite alternate}
@keyframes ghostin{from{opacity:0}to{opacity:1}}
.frame{position:absolute;inset:clamp(14px,3.2vw,42px);z-index:4;border:1px solid rgba(244,238,227,.09);
clip-path:inset(0 0 100% 0);animation:open 1.5s .35s cubic-bezier(.16,1,.3,1) both}
@keyframes open{to{clip-path:inset(0 0 0 0)}}
.tick{position:absolute;width:14px;height:14px;z-index:4;opacity:0;animation:tickin .6s 1.2s ease both}
@keyframes tickin{from{opacity:0;transform:scale(.4)}to{opacity:1;transform:scale(1)}}
.tl{top:clamp(14px,3.2vw,42px);left:clamp(14px,3.2vw,42px);border-top:1px solid var(--ember);border-left:1px solid var(--ember)}
.tr{top:clamp(14px,3.2vw,42px);right:clamp(14px,3.2vw,42px);border-top:1px solid var(--mint);border-right:1px solid var(--mint)}
.bl{bottom:clamp(14px,3.2vw,42px);left:clamp(14px,3.2vw,42px);border-bottom:1px solid var(--mint);border-left:1px solid var(--mint)}
.br{bottom:clamp(14px,3.2vw,42px);right:clamp(14px,3.2vw,42px);border-bottom:1px solid var(--ember);border-right:1px solid var(--ember)}
.vignette{position:absolute;inset:0;z-index:3;
background:radial-gradient(75% 65% at 50% 50%,transparent 40%,rgba(3,6,7,.72) 100%)}
.grain{
position:absolute;inset:-50%;z-index:6;pointer-events:none;opacity:.055;mix-blend-mode:overlay;
background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.85' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
animation:grain 5s steps(5) infinite;
}
@keyframes grain{
0%{transform:translate(0,0)}20%{transform:translate(-3%,2%)}40%{transform:translate(2%,-3%)}
60%{transform:translate(-2%,-2%)}80%{transform:translate(3%,1%)}100%{transform:translate(0,0)}
}
@media (prefers-reduced-motion: reduce){
*{animation-duration:.001s!important;animation-iteration-count:1!important;transition-duration:.001s!important}
.l{transform:none}.caret{opacity:1;animation:none}.sweep,.grain,.field{animation:none}
}
</style>
</head>
<body>
<main class="stage">
<div class="field" aria-hidden="true"></div>
<div class="grid-base" aria-hidden="true"></div>
<div class="grid-scan" aria-hidden="true"></div>
<div class="glow" aria-hidden="true"></div>
<div class="sweep" aria-hidden="true"></div>
<div class="tilt">
<h1 class="hello" id="hello" aria-label="Hello World"></h1>
</div>
<div class="vignette" aria-hidden="true"></div>
<div class="frame" aria-hidden="true"></div>
<i class="tick tl" aria-hidden="true"></i><i class="tick tr" aria-hidden="true"></i>
<i class="tick bl" aria-hidden="true"></i><i class="tick br" aria-hidden="true"></i>
<div class="grain" aria-hidden="true"></div>
</main>
<script>
const TEXT = "Hello World";
const hello = document.getElementById("hello");
const reduce = matchMedia("(prefers-reduced-motion: reduce)").matches;
const words = TEXT.split(" ");
hello.innerHTML =
`<span class="ghost deep" aria-hidden="true">${TEXT}</span>` +
`<span class="ghost" aria-hidden="true">${TEXT}</span>`;
let i = 0;
words.forEach((word, wi) => {
const w = document.createElement("span");
w.className = "w";
w.setAttribute("aria-hidden", "true");
[...word].forEach(ch => {
const m = document.createElement("span"); m.className = "m";
const l = document.createElement("span"); l.className = "l";
l.style.setProperty("--d", (0.28 + i * 0.052) + "s");
const g = document.createElement("span"); g.className = "g"; g.textContent = ch;
m.appendChild(l); l.appendChild(g); w.appendChild(m); i++;
});
hello.appendChild(w);
if (wi < words.length - 1) hello.appendChild(document.createTextNode(" "));
});
const caret = document.createElement("i");
caret.className = "caret"; caret.setAttribute("aria-hidden", "true");
hello.appendChild(caret);
const play = () => {
document.body.classList.remove("play");
void document.body.offsetWidth;
document.body.classList.add("play");
};
play();
addEventListener("pointerdown", play);
const root = document.documentElement, tilt = document.querySelector(".tilt");
let tx = innerWidth / 2, ty = innerHeight / 2, gx = tx, gy = ty, px = 0, py = 0;
addEventListener("pointermove", e => {
tx = e.clientX; ty = e.clientY;
root.style.setProperty("--mx", tx + "px");
root.style.setProperty("--my", ty + "px");
}, {passive:true});
addEventListener("pointerleave", () => { tx = innerWidth/2; ty = innerHeight/2; });
(function raf(){
gx += (tx - gx) * 0.07; gy += (ty - gy) * 0.07;
px += ((tx / innerWidth - .5) - px) * 0.05;
py += ((ty / innerHeight - .5) - py) * 0.05;
root.style.setProperty("--gx", gx + "px");
root.style.setProperty("--gy", gy + "px");
if (!reduce) tilt.style.transform =
`perspective(900px) rotateY(${px * 9}deg) rotateX(${-py * 7}deg) translate3d(${px * 16}px,${py * 12}px,0)`;
requestAnimationFrame(raf);
})();
</script>
</body>
</html>