examples/07-Animations.ps1

# 07 - Animations: a live dashboard with CSS keyframes, the Web Animations API and a canvas loop.
#
# Windows Server 2025 (and any Windows with "Animation effects" turned off) tells web pages
# "prefers-reduced-motion: reduce" (Server 2025 has them off by default). That is only a REQUEST: CSS keyframes,
# the Web Animations API and requestAnimationFrame keep running. This page ignores the setting simply by never
# asking for it - it has no @media (prefers-reduced-motion) rule and no matchMedia check that stops motion.
# (Measured 2026-11-12 on Windows Server 2025, WebView2 157: animation effects off, the page sees "reduce",
# all four endless animations run and the canvas draws at ~30 fps.) Avoid CSS or libraries that honour the
# setting if motion must always play. The page reports what really ran; it is printed when the window closes.
if (-not (Get-Command Show-NativeWebView -ErrorAction Ignore)) { Import-Module NativeWebView -ErrorAction Stop }

$Html = @'
<!doctype html>
<html><head><style>
  :root { --navy: #154363; --blue: #095495; --sky: #a2bfd6; --text: #454545; --green: #4F8035; }
  * { box-sizing: border-box; }
  body { margin: 0; font-family: Verdana, 'Segoe UI', Arial, sans-serif; font-size: 13px; color: var(--text);
         background: #F9F9F9; overflow: hidden; }
  header { padding: 18px 24px; color: white;
           background: linear-gradient(120deg, var(--navy), var(--blue), var(--sky), var(--blue), var(--navy));
           background-size: 300% 100%; animation: sweep 8s linear infinite; }
  header h1 { margin: 0; font-size: 20px; letter-spacing: 1px; }
  header p { margin: 4px 0 0; opacity: .85; }
  @keyframes sweep { from { background-position: 0% 0; } to { background-position: 300% 0; } }

  .grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; padding: 18px 24px 0; }
  .card { background: white; border: 1px solid #CCCCCC; border-radius: 8px; padding: 14px; opacity: 0;
          animation: rise .7s cubic-bezier(.2,.8,.2,1) forwards; }
  .card:nth-child(1) { animation-delay: .1s; } .card:nth-child(2) { animation-delay: .25s; } .card:nth-child(3) { animation-delay: .4s; }
  @keyframes rise { from { opacity: 0; transform: translateY(24px) scale(.96); } to { opacity: 1; transform: none; } }
  .card h3 { margin: 0 0 8px; color: var(--navy); font-size: 13px; }
  .big { font-size: 28px; font-weight: bold; color: var(--blue); font-variant-numeric: tabular-nums; }

  .ring { width: 56px; height: 56px; border-radius: 50%; border: 6px solid #e4e4e4; border-top-color: var(--blue);
          animation: spin 1s linear infinite; margin: 4px auto; }
  @keyframes spin { to { transform: rotate(360deg); } }
  .pulse { display: inline-block; width: 10px; height: 10px; border-radius: 50%; background: var(--green);
           margin-right: 6px; animation: pulse 1.4s ease-in-out infinite; }
  @keyframes pulse { 0%,100% { transform: scale(1); opacity: 1; } 50% { transform: scale(1.8); opacity: .3; } }

  .bar { height: 12px; border-radius: 6px; background: #e4e4e4; overflow: hidden; margin-top: 10px; }
  .fill { height: 100%; width: 100%; transform-origin: left; background: linear-gradient(90deg, var(--blue), var(--sky)); }

  canvas { display: block; margin: 14px 24px 0; width: calc(100% - 48px); height: 120px; background: white;
           border: 1px solid #CCCCCC; border-radius: 8px; }
  footer { display: flex; justify-content: space-between; align-items: center; padding: 12px 24px; }
  #status { color: var(--green); }
  button { padding: 8px 18px; background: var(--blue); color: white; border: 0; border-radius: 4px; font: inherit; cursor: pointer;
           transition: background .2s, transform .1s; }
  button:hover { background: var(--navy); } button:active { transform: scale(.96); }
</style></head>
<body>
  <header><h1>NativeWebView live dashboard</h1><p>CSS keyframes, Web Animations and a canvas loop - from PowerShell</p></header>
  <div class="grid">
    <div class="card"><h3>Requests</h3><div class="big" id="count">0</div><div class="bar"><div class="fill" id="fill"></div></div></div>
    <div class="card"><h3>Working</h3><div class="ring"></div></div>
    <div class="card"><h3>Status</h3><p><span class="pulse"></span>Online</p><p id="clock">-</p></div>
  </div>
  <canvas id="wave" width="900" height="240"></canvas>
  <footer><span id="status">Measuring animations...</span><button id="close">Close</button></footer>
  <script>
    // Web Animations API: the progress bar fills and empties forever.
    document.getElementById("fill").animate(
      [{ transform: "scaleX(0)" }, { transform: "scaleX(1)" }, { transform: "scaleX(0)" }],
      { duration: 3000, iterations: Infinity, easing: "ease-in-out" });

    // requestAnimationFrame: a counter and a two-colour wave on a canvas.
    var canvas = document.getElementById("wave"), g = canvas.getContext("2d"), frames = 0, start = performance.now();
    function draw(now) {
      frames++;
      var t = (now - start) / 1000, w = canvas.width, h = canvas.height;
      g.clearRect(0, 0, w, h);
      [["#095495", 1, 0], ["#a2bfd6", 0.6, 1.3]].forEach(function (s) {
        g.beginPath(); g.lineWidth = 4; g.strokeStyle = s[0];
        for (var x = 0; x <= w; x += 6) {
          var y = h / 2 + Math.sin(x / 60 + t * 2 + s[2]) * h * 0.3 * s[1] * Math.sin(t * 0.7 + x / 300);
          if (x === 0) { g.moveTo(x, y); } else { g.lineTo(x, y); }
        }
        g.stroke();
      });
      document.getElementById("count").textContent = Math.floor(t * 37).toLocaleString();
      document.getElementById("clock").textContent = new Date().toLocaleTimeString();
      requestAnimationFrame(draw);
    }
    requestAnimationFrame(draw);

    // After 2 s: measure what really moved and tell PowerShell.
    setTimeout(function () {
      var css = document.getAnimations().filter(function (a) { return a.playState === "running" && a.currentTime > 0; }).length;
      var reduced = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
      var fps = Math.round(frames / ((performance.now() - start) / 1000));
      document.getElementById("status").textContent = "Running: " + css + " animations, " + fps + " fps" +
        (reduced ? " (system asks for reduced motion - ignored)" : "");
      kHost.send({ action: "report", running: css, fps: fps, reducedMotion: reduced, agent: navigator.userAgent });
    }, 2000);

    document.getElementById("close").onclick = function () { kHost.send({ action: "close" }); };
  </script>
</body></html>
'@


$Report = @{}
Show-NativeWebView -Html $Html -Title "Live dashboard" -Width 760 -Height 520 -OnMessage {
    param($Message, $Window)
    $Data = $Message | ConvertFrom-Json
    if ($Data.action -eq "report") { $Report.Running = $Data.running; $Report.Fps = $Data.fps; $Report.Reduced = $Data.reducedMotion; $Report.Agent = $Data.agent }
    if ($Data.action -eq "close") { $Window.Close() }
}.GetNewClosure()
if ($Report.Count -gt 0) {
    Write-Host ("Animations running: " + $Report.Running + ", " + $Report.Fps + " fps, page saw reduced motion: " + $Report.Reduced)
}