{
  "sha": "5013d8c5285bc14526bf6995832ed69a22a3cd78",
  "redacted": false,
  "message": "Fix logo flight remnant and animate team mark (#408)",
  "author": {
    "login": "nnneeeooo-nnneeeooo",
    "name": "neo",
    "email": "125815713+nnneeeooo-nnneeeooo@users.noreply.github.com"
  },
  "committer": {
    "login": "web-flow",
    "name": "GitHub",
    "email": "noreply@github.com"
  },
  "created_at": "2026-10-26T15:29:05Z",
  "files": [
    {
      "filename": "pipeline/build.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 0,
      "changes": 1
    },
    {
      "filename": "static/site.css",
      "previous_filename": "",
      "status": "modified",
      "additions": 1,
      "deletions": 1,
      "changes": 2
    },
    {
      "filename": "static/team-logo.js",
      "previous_filename": "",
      "status": "added",
      "additions": 37,
      "deletions": 0,
      "changes": 37
    },
    {
      "filename": "static/team.css",
      "previous_filename": "",
      "status": "modified",
      "additions": 61,
      "deletions": 0,
      "changes": 61
    },
    {
      "filename": "templates/team.html",
      "previous_filename": "",
      "status": "modified",
      "additions": 6,
      "deletions": 1,
      "changes": 7
    },
    {
      "filename": "tests/test_logo_assets.py",
      "previous_filename": "",
      "status": "modified",
      "additions": 6,
      "deletions": 0,
      "changes": 6
    }
  ],
  "diff": "diff --git a/pipeline/build.py b/pipeline/build.py\\nindex 163571efe..5f0743122 100644\\n--- a/pipeline/build.py\\n+++ b/pipeline/build.py\\n@@ -109,6 +109,7 @@ def static_asset_version() -> str:\\n         \"briefing.css\",\\n         \"briefing.js\",\\n         \"team.css\",\\n+        \"team-logo.js\",\\n         \"team-neo.jpg\",\\n         \"radar.css\",\\n         \"radar.js\",\\ndiff --git a/static/site.css b/static/site.css\\nindex 248dea933..a56631aa8 100644\\n--- a/static/site.css\\n+++ b/static/site.css\\n@@ -73,7 +73,7 @@ a.ticker-item:hover .ticker-time { color: var(--color-accent); }\\n   object-fit: contain; background: transparent;\\n }\\n .brand-link.is-flying { z-index: 3; }\\n-.brand-link.is-flying .brand-logo { clip-path: inset(0 0 0 23.5%); }\\n+.brand-link.is-flying .brand-logo { clip-path: inset(0 0 0 25%); }\\n .brand-flight-sprite {\\n   position: absolute; top: 0; left: 0; width: 25%; height: 100%;\\n   visibility: hidden; pointer-events: none;\\ndiff --git a/static/team-logo.js b/static/team-logo.js\\nnew file mode 100644\\nindex 000000000..d570c2079\\n--- /dev/null\\n+++ b/static/team-logo.js\\n@@ -0,0 +1,37 @@\\n+/* A brief radar-like flourish for the founder's wing mark. */\\n+(function () {\\n+  \"use strict\";\\n+\\n+  var trigger = document.querySelector(\"[data-team-logo-play]\");\\n+  if (!trigger || !window.matchMedia) return;\\n+  var reduced = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\\n+  var timer = 0;\\n+\\n+  function play(restart) {\\n+    if (reduced.matches || document.documentElement.dataset.theme === \"8bit\") return;\\n+    if (trigger.classList.contains(\"is-playing\")) {\\n+      if (!restart) return;\\n+      trigger.classList.remove(\"is-playing\");\\n+      void trigger.offsetWidth;\\n+    }\\n+    trigger.classList.add(\"is-playing\");\\n+    window.clearTimeout(timer);\\n+    timer = window.setTimeout(function () {\\n+      trigger.classList.remove(\"is-playing\");\\n+    }, 2400);\\n+  }\\n+\\n+  trigger.addEventListener(\"pointerenter\", function (event) {\\n+    if (event.pointerType === \"mouse\" || event.pointerType === \"pen\") play(false);\\n+  });\\n+  trigger.addEventListener(\"click\", function () { play(true); });\\n+\\n+  if (\"IntersectionObserver\" in window) {\\n+    var observer = new IntersectionObserver(function (entries) {\\n+      if (!entries.some(function (entry) { return entry.isIntersecting; })) return;\\n+      observer.disconnect();\\n+      window.setTimeout(function () { play(false); }, 350);\\n+    }, { threshold: .6 });\\n+    observer.observe(trigger);\\n+  }\\n+})();\\ndiff --git a/static/team.css b/static/team.css\\nindex 4c367f96a..e1851456c 100644\\n--- a/static/team.css\\n+++ b/static/team.css\\n@@ -13,7 +13,68 @@\\n .team-introduction { grid-area: intro; min-width: 0; align-self: end; padding-top: 20px; }\\n .team-introduction h2 { display: flex; align-items: center; gap: 16px; margin: 0 0 24px; font-size: clamp(88px, 9vw, 120px); font-weight: 800; line-height: 1; letter-spacing: -.065em; }\\n .team-name-logo { display: block; width: .9em; height: .9em; flex: 0 0 auto; object-fit: contain; }\\n+.team-logo-trigger {\\n+  position: relative; display: block; flex: 0 0 .9em; width: .9em; height: .9em;\\n+  padding: 0; border: 0; background: transparent; color: inherit;\\n+  font: inherit; line-height: 1; cursor: pointer; isolation: isolate;\\n+  -webkit-tap-highlight-color: transparent;\\n+}\\n+.team-logo-trigger .team-name-logo { position: relative; z-index: 2; width: 100%; height: 100%; }\\n+.team-logo-trigger::before, .team-logo-trigger::after {\\n+  content: \"\"; position: absolute; inset: -.09em; border: 2px solid #f2643c;\\n+  border-radius: 50%; opacity: 0; pointer-events: none;\\n+}\\n+.team-logo-trigger::after { border-color: #f2a23a; }\\n+.team-logo-orbit {\\n+  position: absolute; inset: -.16em; z-index: 1; border: 2px solid transparent;\\n+  border-top-color: #ec3013; border-right-color: #f2643c;\\n+  border-bottom-color: #f2a23a; border-radius: 50%; opacity: 0;\\n+  pointer-events: none;\\n+}\\n+.team-logo-signal {\\n+  position: absolute; top: 17%; left: 78%; z-index: 1;\\n+  width: 6px; height: 6px; border-radius: 50%; background: #f2a23a;\\n+  box-shadow: 0 0 0 3px color-mix(in srgb, #f2a23a 22%, transparent);\\n+  opacity: 0; pointer-events: none;\\n+}\\n+.team-logo-trigger.is-playing .team-name-logo { animation: team-logo-wake 1.8s cubic-bezier(.2,.75,.2,1) both; }\\n+.team-logo-trigger.is-playing::before { animation: team-logo-echo 1.45s .12s ease-out both; }\\n+.team-logo-trigger.is-playing::after { animation: team-logo-echo 1.45s .36s ease-out both; }\\n+.team-logo-trigger.is-playing .team-logo-orbit { animation: team-logo-orbit 2.1s ease-in-out both; }\\n+.team-logo-trigger.is-playing .team-logo-signal { animation: team-logo-signal 1.8s .13s ease-in-out both; }\\n+.team-logo-trigger:focus-visible { outline: 3px solid var(--color-accent); outline-offset: 12px; border-radius: 50%; }\\n+@keyframes team-logo-wake {\\n+  0% { transform: translateY(8px) rotate(-22deg) scale(.72); }\\n+  24% { transform: translateY(-8px) rotate(11deg) scale(1.11); }\\n+  47% { transform: translateY(2px) rotate(-7deg) scale(.94); }\\n+  68% { transform: translateY(-3px) rotate(4deg) scale(1.04); }\\n+  100% { transform: none; }\\n+}\\n+@keyframes team-logo-echo {\\n+  0% { transform: scale(.38); opacity: .85; }\\n+  100% { transform: scale(1.52); opacity: 0; }\\n+}\\n+@keyframes team-logo-orbit {\\n+  0% { transform: rotate(-100deg) scale(.65); opacity: 0; }\\n+  17% { opacity: .9; }\\n+  70% { opacity: .85; }\\n+  100% { transform: rotate(440deg) scale(1.08); opacity: 0; }\\n+}\\n+@keyframes team-logo-signal {\\n+  0%, 100% { transform: rotate(-90deg) translateX(0) scale(.3); opacity: 0; }\\n+  20% { opacity: 1; }\\n+  65% { transform: rotate(225deg) translateX(65px) scale(1); opacity: 1; }\\n+  85% { opacity: 0; }\\n+}\\n+@media (prefers-reduced-motion: reduce) {\\n+  .team-logo-trigger.is-playing .team-name-logo,\\n+  .team-logo-trigger.is-playing::before,\\n+  .team-logo-trigger.is-playing::after,\\n+  .team-logo-trigger.is-playing .team-logo-orbit,\\n+  .team-logo-trigger.is-playing .team-logo-signal { animation: none; }\\n+}\\n .team-name-logo--pixel { display: none; }\\n+[data-theme=\"8bit\"] .team-logo-trigger { display: none; }\\n [data-theme=\"8bit\"] .team-name-logo--classic { display: none; }\\n [data-theme=\"8bit\"] .team-name-logo--pixel { display: block; image-rendering: pixelated; }\\n .team-page .team-role { margin: 0 0 26px; font-size: 14px; font-weight: 700; line-height: 1.8; }\\ndiff --git a/templates/team.html b/templates/team.html\\nindex 28324c27f..5ac651362 100644\\n--- a/templates/team.html\\n+++ b/templates/team.html\\n@@ -1,6 +1,7 @@\\n {% extends \"base.html\" %}\\n {% block head_extra %}\\n <link rel=\"stylesheet\" href=\"{{ base }}/assets/team.css?v={{ asset_version }}\">\\n+<script src=\"{{ base }}/assets/team-logo.js?v={{ asset_version }}\" defer></script>\\n {% endblock %}\\n {% block content %}\\n <main class=\"team-page\" id=\"team-main\">\\n@@ -19,7 +20,11 @@\\n \\n       <div class=\"team-introduction\">\\n         <h2 id=\"team-name\">\\n-          <img class=\"team-name-logo team-name-logo--classic\" src=\"{{ base }}/assets/skytical-team-mark.svg?v={{ asset_version }}\" width=\"88\" height=\"88\" alt=\"\" aria-hidden=\"true\">\\n+          <button class=\"team-logo-trigger\" type=\"button\" data-team-logo-play aria-label=\"{{ '重播翼標動畫' if lang == 'zh' else 'Replay wing animation' }}\">\\n+            <span class=\"team-logo-orbit\" aria-hidden=\"true\"></span>\\n+            <span class=\"team-logo-signal\" aria-hidden=\"true\"></span>\\n+            <img class=\"team-name-logo team-name-logo--classic\" src=\"{{ base }}/assets/skytical-team-mark.svg?v={{ asset_version }}\" width=\"88\" height=\"88\" alt=\"\" aria-hidden=\"true\">\\n+          </button>\\n           <img class=\"team-name-logo team-name-logo--pixel\" src=\"{{ base }}/assets/skytical-mark-8bit.svg?v={{ asset_version }}\" width=\"88\" height=\"88\" alt=\"\" aria-hidden=\"true\">\\n           <span>{{ team.name }}</span>\\n         </h2>\\ndiff --git a/tests/test_logo_assets.py b/tests/test_logo_assets.py\\nindex 6de054d6b..963222e54 100644\\n--- a/tests/test_logo_assets.py\\n+++ b/tests/test_logo_assets.py\\n@@ -92,6 +92,12 @@ def main() -> None:\\n     assert 'data-brand-sprite' in home\\n     assert f'/assets/logo-flight.js?v={build.ASSET_VERSION}' in home\\n     assert (ROOT / \"site\" / \"assets\" / \"logo-flight.js\").is_file()\\n+    assert '.brand-logo { clip-path: inset(0 0 0 25%); }' in css\\n+    team = (ROOT / \"site\" / \"team\" / \"index.html\").read_text(encoding=\"utf-8\")\\n+    assert 'data-team-logo-play' in team\\n+    assert f'/assets/team-logo.js?v={build.ASSET_VERSION}' in team\\n+    assert (ROOT / \"site\" / \"assets\" / \"team-logo.js\").is_file()\\n+    assert 'class=\"team-inline-logo team-name-logo--classic\"' in team\\n     assert f'/assets/skytical-mark.svg?v={build.ASSET_VERSION}' in home\\n     assert f'/assets/skytical-social.png?v={build.ASSET_VERSION}' in home\\n     assert \"/assets/logo-fix.css\" not in home"
}
