2026-10-24 SAT UTC 7:56 PM TPE 3:56 AM Auto-updates hourly 中文 EN
LIVE WIRE
7:07 PM Emirates cancels three nonstop A380 routes this winter.↗▪7:07 PM European countries consider broader counter-drone powers for infrastructure operators↗▪7:07 PM Saudia and Riyadh Air route Riyadh passengers via Al Qassim↗▪6:54 PM Taiwan CAA says domestic flights will be canceled tomorrow; international schedules may change.↗▪6:54 PM Chery unveils two Lingxiao eVTOL concepts.↗▪6:54 PM TRV-150 intercepts multiple attack drones in Florida trial↗▪6:54 PM Singapore Airlines announces KrisWorld IFE upgrades↗▪6:54 PM Armenia’s first L-39 Skyfox appears in Air Force markings↗▪6:54 PM IATA and OneVasco plan to integrate visa services with Timatic.↗▪1:39 AM Airbus Helicopters completes H140 US hot-and-high tests↗ 7:07 PM Emirates cancels three nonstop A380 routes this winter.↗▪7:07 PM European countries consider broader counter-drone powers for infrastructure operators↗▪7:07 PM Saudia and Riyadh Air route Riyadh passengers via Al Qassim↗▪6:54 PM Taiwan CAA says domestic flights will be canceled tomorrow; international schedules may change.↗▪6:54 PM Chery unveils two Lingxiao eVTOL concepts.↗▪6:54 PM TRV-150 intercepts multiple attack drones in Florida trial↗▪6:54 PM Singapore Airlines announces KrisWorld IFE upgrades↗▪6:54 PM Armenia’s first L-39 Skyfox appears in Air Force markings↗▪6:54 PM IATA and OneVasco plan to integrate visa services with Timatic.↗▪1:39 AM Airbus Helicopters completes H140 US hot-and-high tests↗
← Back to the site changelog Site Changelog

Original commit record

新增 SKYTICAL 團隊頁與創辦人介紹 (#368)

SHA
f4760026bbcea265b523824ff54bdc6f5bbe66ea
Commit date
Author
CHI HSUN YANG · 125815713+nnneeeooo-nnneeeooo@users.noreply.github.com
Committer
GitHub · noreply@github.com

Changed files (8)

  • config/team.json added · +80 / −0
  • docs/open-source-scouting.md modified · +12 / −0
  • pipeline/build.py modified · +18 / −4
  • static/site.css modified · +2 / −1
  • static/team-nwe.jpg added · +0 / −0
  • static/team.css added · +47 / −0
  • templates/team.html added · +48 / −0
  • tests/test_seo.py modified · +25 / −0

Full diff

@@ -0,0 +1,80 @@\n+{\n+  "zh": {\n+    "title": "SKYTICAL 團隊",\n+    "description": "認識 SKYTICAL 創辦人 nwe,以及這個由一人獨立營運、免費分享航空新聞的網站背後的故事。",\n+    "heading": "航空世界很大,從一份好奇開始。",\n+    "name": "nwe",\n+    "role": "創辦人・首席網站營運與維護顧問",\n+    "intro": "你好,我是 nwe。從自己想看的新聞網站開始,SKYTICAL 慢慢成為與大家分享航空大小事的地方。",\n+    "photoAlt": "SKYTICAL 創辦人 nwe 的個人照片",\n+    "photoCaption": "航空、攝影、音樂,還有路上的風景。",\n+    "contactTitle": "與我聯絡",\n+    "instagramLabel": "Instagram · @ks.17___",\n+    "emailLabel": "寄信給我",\n+    "sections": [\n+      {\n+        "title": "從自己想看的新聞網站開始",\n+        "body": [\n+          "我一直對航空業發生的大小事充滿興趣,但航空資訊往往散落在各家航空公司、各國民航主管機關與媒體之間。許多消息又具有時效性,等到偶然看到時,可能早已錯過最值得關注的時刻。",\n+          "因此,我建立了 SKYTICAL,希望透過全自動新聞聚合,將分散的航空消息整理在一起,免費提供給讀者,讓大家更方便掌握航空界的重要動態、產業消息,以及令人好奇的話題與幕後故事。",\n+          "最初,這只是一個做給自己看的新聞網站。隨著功能逐漸增加,網站也越來越實用,我才開始希望將它分享給更多讀者,以及同樣關心航空世界的朋友。"\n+        ]\n+      },\n+      {\n+        "title": "一個人營運,也持續往前",\n+        "body": [\n+          "從網站開發、功能規劃,到日常營運、更新與維護,目前都由我一人負責。",\n+          "身為學生,能投入的時間有限,但我會持續在課業與網站之間找到平衡,盡力讓 SKYTICAL 保持更新、穩定運作,也讓每一次調整都更貼近讀者的需要。"\n+        ]\n+      },\n+      {\n+        "title": "航空之外的我",\n+        "body": [\n+          "目前是一名大學生。比起單純以「航空迷」介紹自己,我更覺得自己是對國內外整個航空產業都充滿好奇的愛好者。",\n+          "航空之外,我也喜歡攝影、音樂與旅遊。最近尤其沉迷於 neo-soul,常聽 Frank Ocean 和 Daniel Caesar,讓音樂陪著自己度過日常,也陪著網站一點一點成長。"\n+        ]\n+      }\n+    ],\n+    "closing": "謝謝你來到 SKYTICAL,一起關心航空世界的下一件事。",\n+    "aboutLabel": "了解內容如何產製"\n+  },\n+  "en": {\n+    "title": "SKYTICAL Team",\n+    "description": "Meet nwe, the founder behind SKYTICAL, an independently operated website sharing aviation news free of charge.",\n+    "heading": "A world of aviation. A spark of curiosity.",\n+    "name": "nwe",\n+    "role": "Founder · Chief Website Operations & Maintenance Consultant",\n+    "intro": "Hi, I'm nwe. What began as a news website I wanted to read has gradually become a place to share the aviation world with others.",\n+    "photoAlt": "Portrait of nwe, founder of SKYTICAL",\n+    "photoCaption": "Aviation, photography, music, and the places along the way.",\n+    "contactTitle": "Get in touch",\n+    "instagramLabel": "Instagram · @ks.17___",\n+    "emailLabel": "Email me",\n+    "sections": [\n+      {\n+        "title": "It started with the news I wanted to read",\n+        "body": [\n+          "I've always been curious about what's happening across aviation. But the information is scattered among airlines, civil aviation authorities around the world, and the media. Much of it is time-sensitive; by the time I happened to find a story, the moment to follow it had often passed.",\n+          "That's why I built SKYTICAL: a fully automated news aggregator that brings those scattered updates together, free for readers. I wanted to make it easier to keep up with major developments, industry news, interesting conversations, and the stories behind them.",\n+          "At first, this was simply a news website for myself. As its features grew and it became more useful, I began to want to share it with other readers and anyone equally curious about aviation."\n+        ]\n+      },\n+      {\n+        "title": "One person, keeping things moving",\n+        "body": [\n+          "From development and feature planning to day-to-day operations, updates, and maintenance, I currently look after the whole website myself.",\n+          "As a student, my time is limited. I'm working to balance my studies with the website, keeping SKYTICAL updated and running reliably, and making each improvement more useful to its readers."\n+        ]\n+      },\n+      {\n+        "title": "Beyond aviation",\n+        "body": [\n+          "I'm currently a university student. Rather than simply calling myself an aviation fan, I think of myself as someone curious about the entire industry, both in Taiwan and abroad.",\n+          "Outside aviation, I enjoy photography, music, and travel. Lately I've been especially drawn to neo-soul, with Frank Ocean and Daniel Caesar often on repeat as I go about my day and keep building this website."\n+        ]\n+      }\n+    ],\n+    "closing": "Thanks for being here. Let's keep following what happens next in aviation.",\n+    "aboutLabel": "How our stories are produced"\n+  }\n+}\n@@ -2,6 +2,18 @@\n \n 更新日期:2026-10-23\n \n+## 團隊頁與創辦人介紹(2026-10-23)\n+\n+透過 GitHub repository search、官方 API 與 README 比較三個仍在維護的個人網站主題。僅參考內容層級與版面設計,未複製程式碼或新增依賴。\n+\n+| 專案 | 最近提交 | Stars | 語言/授權 | 架構、借鑑方向與導入成本 |\n+| --- | --- | --- | --- | --- |\n+| [al-folio](https://github.com/alshedivat/al-folio) | 2026-10-19 | 16,268 | HTML/MIT | Jekyll 個人網站;參考照片、個人介紹與內容分段。整套導入需要 Ruby/Jekyll,與現有建站流程重複。 |\n+| [Minimal Mistakes](https://github.com/mmistakes/minimal-mistakes) | 2026-10-09 | 13,588 | HTML/MIT | Jekyll 雙欄個人網站;參考介紹與主要內容的閱讀層級。需要 Jekyll 與 include-cache 外掛,不適合只為一頁加入。 |\n+| [Blowfish](https://github.com/nunocoracao/blowfish) | 2026-10-07 | 2,916 | HTML/MIT | Hugo 與 Tailwind CSS 4;參考響應式介紹頁。需要另一套建站與 CSS 工具,不導入。最近推送為 2026-10-21,與最近主分支提交日期分開記錄。 |\n+\n+SKYTICAL 已有 Jinja2 共用導覽、雙語路徑與主題色票;保留現況,用一份雙語設定、一個版型與頁面限定 CSS 完成。使用創辦人提供的照片,以 CSS 控制顯示範圍,保留原始影像。沒有需要另開 issue 導入的套件。\n+\n ## 日本突發飛安搜尋與來源零筆提示(2026-10-23)\n \n 本次透過 GitHub API 查證維護狀態、授權與 README,並實測日本語 RSS:\n@@ -102,6 +102,8 @@ def static_asset_version() -> str:\n         "skytical-social.png",\n         "briefing.css",\n         "briefing.js",\n+        "team.css",\n+        "team-nwe.jpg",\n     ):\n         digest.update((STATIC_DIR / name).read_bytes())\n     return digest.hexdigest()[:10]\n@@ -196,7 +198,7 @@ def ga4_measurement_id() -> str:\n         "footerAbout": "SKYTICAL 是以可追溯來源為基礎的自動化航空新聞彙整服務;每篇報導附原始來源,原始報導與更正公告優先。",\n         "footerSources": "資料來源 Data Sources",\n         "footerChangelog": "網站更新紀錄",\n-        "nav": ["最新", "全部新聞", "快報", "航班雷達", "事故資料庫", "來源", "關於 SKYTICAL"],\n+        "nav": ["最新", "全部新聞", "快報", "航班雷達", "事故資料庫", "來源", "關於 SKYTICAL", "SKYTICAL 團隊"],\n         "cats": ["全部", "事故", "法規", "商業", "營運", "軍事"],\n         "searchKicker": "News Search", "searchTitle": "搜尋新聞",\n         "newsKicker": "News Index", "newsTitle": "全部新聞",\n@@ -350,7 +352,7 @@ def ga4_measurement_id() -> str:\n         "footerSources": "Data Sources",\n         "footerChangelog": "Site changelog",\n         "nav": ["Latest", "All news", "Briefings", "Flight Radar", "Incident DB", "Sources",\n-                 "About SKYTICAL"],\n+                 "About SKYTICAL", "SKYTICAL Team"],\n         "cats": ["All", "Safety", "Regulation", "Business", "Operations",\n                  "Military"],\n         "searchKicker": "News Search", "searchTitle": "Search news",\n@@ -3626,7 +3628,8 @@ def base_ctx(lang, page, sub, *, title, description, ticker, build,\n     nav_defs = [("home", ""), ("news", "news/"), ("briefings", "briefings/"),\n                 ("radar", "radar/"),\n                 ("incidents", "incidents/"),\n-                ("sources", "sources/"), ("about", "about/")]\n+                ("sources", "sources/"), ("about", "about/"),\n+                ("team", "team/")]\n     return {\n         "lang": lang, "t": t, "base": BASE_PATH, "favicon": FAVICON,\n         "asset_version": ASSET_VERSION,\n@@ -3860,7 +3863,7 @@ def add_url(lang: str, sub: str, modified=None) -> None:\n     ]\n     latest_briefing_modified = (\n         max(briefing_modified) if briefing_modified else None)\n-    static_sections = ("radar/", "incidents/", "sources/", "about/",\n+    static_sections = ("radar/", "incidents/", "sources/", "about/", "team/",\n                        "editorial-policy/", "changelog/")\n     for lang in ("zh", "en"):\n         localized = [\n@@ -4368,6 +4371,17 @@ def main() -> int:\n         render(env, "about.html", rel_path(lang, "about/index.html"), ctx)\n         pages += 1\n \n+        team = load_json(CONFIG_DIR / "team.json", {})[lang]\n+        ctx = base_ctx(lang, "team", "team/",\n+                       title=f"{team['title']}|{t['siteName']}",\n+                       description=team["description"], ticker=ticker, build=build)\n+        ctx["structured_data"] = breadcrumb_structured_data(lang, [\n+            (t["siteName"], ""), (team["title"], "team/"),\n+        ])\n+        ctx.update(team=team)\n+        render(env, "team.html", rel_path(lang, "team/index.html"), ctx)\n+        pages += 1\n+\n         policy = EDITORIAL_POLICY[lang]\n         ctx = base_ctx(\n             lang, "policy", "editorial-policy/",\n@@ -183,7 +183,8 @@ a.ticker-item:hover .ticker-time { color: var(--color-accent); }\n .nav-link:hover { color: var(--color-accent); }\n .nav-link.active { color: var(--color-accent); border-bottom-color: var(--color-accent); }\n .site-nav {\n-  display: flex; gap: 2px; margin-left: auto; white-space: nowrap;\n+  display: flex; flex-wrap: wrap; justify-content: flex-end;\n+  gap: 2px; margin-left: auto; white-space: nowrap;\n }\n \n /* — segmented controls (language links / theme + filter buttons) — */\n@@ -0,0 +1,47 @@\n+/* The founder's portrait leads; the rest uses SKYTICAL's existing theme tokens. */\n+.team-page {\n+  --team-muted: color-mix(in srgb, var(--color-text) 74%, var(--color-bg));\n+  padding: clamp(28px, 4vw, 56px) clamp(20px, 5vw, 72px) 64px;\n+}\n+.team-page *, .team-page *::before, .team-page *::after { box-sizing: border-box; }\n+.team-header { display: flex; justify-content: space-between; align-items: baseline; gap: 20px; margin-bottom: 32px; }\n+.team-header h1 { margin: 0; font-size: clamp(23px, 2.5vw, 32px); line-height: 1.4; }\n+.team-header p { margin: 0; font-size: 14px; color: var(--team-muted); }\n+.team-hero { display: grid; grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: clamp(32px, 5vw, 72px); align-items: center; }\n+.team-portrait { margin: 0; min-width: 0; }\n+.team-portrait img {\n+  display: block; width: 100%; height: auto; aspect-ratio: 4 / 5;\n+  object-fit: cover; object-position: 50% 70%; border-radius: 6px 6px 48px 6px;\n+}\n+.team-portrait figcaption { margin-top: 12px; font-size: 12px; line-height: 1.8; color: var(--team-muted); }\n+.team-introduction { min-width: 0; padding: 16px 0 36px; }\n+.team-introduction h2 { margin: 0 0 20px; font-size: clamp(80px, 10vw, 136px); font-weight: 800; line-height: .95; letter-spacing: -.065em; }\n+.team-page .team-role { margin: 0 0 28px; font-size: 15px; font-weight: 700; line-height: 1.8; }\n+.team-lead { max-width: 26em; margin: 0; font-size: clamp(18px, 2vw, 23px); line-height: 1.85; text-wrap: pretty; }\n+.team-contact { margin-top: 36px; padding-top: 24px; border-top: 2px solid var(--color-accent); }\n+.team-contact h3 { margin: 0 0 10px; font-size: 13px; color: var(--team-muted); font-weight: 500; }\n+.team-contact a { display: flex; justify-content: space-between; align-items: baseline; flex-wrap: wrap; gap: 4px 16px; padding: 12px 0; min-height: 44px; border-bottom: 1px solid var(--color-divider); color: var(--color-text); font-size: 14px; font-weight: 600; text-decoration: none; overflow-wrap: anywhere; }\n+.team-contact a:hover { color: var(--color-accent); }\n+.team-email { font-size: 13px; font-weight: 400; }\n+.team-story { margin-top: clamp(44px, 6vw, 80px); }\n+.team-chapter { display: grid; grid-template-columns: minmax(0, .8fr) minmax(0, 1.2fr); gap: clamp(24px, 5vw, 72px); padding: 36px 0; border-top: 1px solid var(--color-divider); }\n+.team-chapter h3 { margin: 0; max-width: 12em; font-size: clamp(22px, 2.4vw, 28px); line-height: 1.6; text-wrap: balance; }\n+.team-chapter p { margin: 0 0 18px; max-width: 42em; font-size: 16px; line-height: 2; color: var(--team-muted); }\n+.team-chapter p:last-child { margin-bottom: 0; }\n+.team-closing { display: flex; flex-wrap: wrap; justify-content: space-between; align-items: baseline; gap: 20px; margin-top: 12px; padding-top: 28px; border-top: 2px solid var(--color-accent); }\n+.team-closing p { margin: 0; max-width: 34em; font-size: 17px; font-weight: 600; line-height: 1.8; text-wrap: pretty; }\n+.team-closing a { display: inline-flex; align-items: center; gap: 16px; min-height: 44px; color: var(--color-text); font-size: 14px; text-underline-offset: 5px; }\n+.team-page a:focus-visible { outline: 3px solid var(--color-accent); outline-offset: 4px; }\n+.team-page a { touch-action: manipulation; }\n+@media (max-width: 720px) {\n+  .team-header { display: block; margin-bottom: 24px; }\n+  .team-header p { margin-top: 10px; line-height: 1.8; }\n+  .team-hero { grid-template-columns: minmax(0, 1fr); gap: 28px; }\n+  .team-portrait { max-width: 480px; width: 100%; margin: 0 auto; }\n+  .team-introduction { padding: 0; }\n+  .team-introduction h2 { font-size: 88px; margin-bottom: 16px; }\n+  .team-page .team-role { margin-bottom: 20px; }\n+  .team-contact { margin-top: 24px; }\n+  .team-chapter { grid-template-columns: minmax(0, 1fr); gap: 18px; padding: 28px 0; }\n+  .team-chapter h3 { max-width: none; }\n+}\n@@ -0,0 +1,48 @@\n+{% extends "base.html" %}\n+{% block head_extra %}\n+<link rel="stylesheet" href="{{ base }}/assets/team.css?v={{ asset_version }}">\n+{% endblock %}\n+{% block content %}\n+<main class="team-page" id="team-main">\n+  <header class="team-header">\n+    <h1>{{ team.title }}</h1>\n+    <p>{{ team.heading }}</p>\n+  </header>\n+  <article class="team-profile" aria-labelledby="team-name">\n+    <div class="team-hero">\n+      <figure class="team-portrait">\n+        <img src="{{ base }}/assets/team-nwe.jpg?v={{ asset_version }}"\n+             alt="{{ team.photoAlt }}" width="720" height="1280"\n+             fetchpriority="high" decoding="async">\n+        <figcaption>{{ team.photoCaption }}</figcaption>\n+      </figure>\n+      <div class="team-introduction">\n+        <h2 id="team-name">{{ team.name }}</h2>\n+        <p class="team-role">{{ team.role }}</p>\n+        <p class="team-lead">{{ team.intro }}</p>\n+        <div class="team-contact" aria-labelledby="team-contact-title">\n+          <h3 id="team-contact-title">{{ team.contactTitle }}</h3>\n+          <a href="https://www.instagram.com/ks.17___/" target="_blank" rel="noopener noreferrer">\n+            {{ team.instagramLabel }}<span aria-hidden="true">↗</span>\n+          </a>\n+          <a href="mailto:kass09221477@gmail.com">\n+            {{ team.emailLabel }}<span class="team-email">kass09221477@gmail.com</span>\n+          </a>\n+        </div>\n+      </div>\n+    </div>\n+    <div class="team-story">\n+      {% for section in team.sections %}\n+      <section class="team-chapter" aria-labelledby="team-chapter-{{ loop.index }}">\n+        <h3 id="team-chapter-{{ loop.index }}">{{ section.title }}</h3>\n+        <div>{% for paragraph in section.body %}<p>{{ paragraph }}</p>{% endfor %}</div>\n+      </section>\n+      {% endfor %}\n+    </div>\n+  </article>\n+  <div class="team-closing">\n+    <p>{{ team.closing }}</p>\n+    <a href="{{ about_url }}">{{ team.aboutLabel }}<span aria-hidden="true">↗</span></a>\n+  </div>\n+</main>\n+{% endblock %}\n@@ -228,6 +228,31 @@ def main() -> None:\n     assert "SKYTICAL/issues/new" in policy\n     assert 'href="/editorial-policy/"' in article\n \n+    for lang, sub, label in (("zh", "", "SKYTICAL 團隊"),\n+                             ("en", "en/", "SKYTICAL Team")):\n+        team_url = f"{build.SITE_ORIGIN}{build.BASE_PATH}/{sub}team/"\n+        assert team_url in locations\n+        team = (ROOT / "site" / sub / "team" / "index.html").read_text(\n+            encoding="utf-8")\n+        assert f"<h1>{label}</h1>" in team\n+        assert f'<link rel="canonical" href="{team_url}">' in team\n+        assert 'hreflang="zh-Hant-TW"' in team and 'hreflang="en"' in team\n+        assert '<h2 id="team-name">nwe</h2>' in team\n+        assert 'href="https://www.instagram.com/ks.17___/"' in team\n+        assert 'href="mailto:kass09221477@gmail.com"' in team\n+        assert "Frank Ocean" in team and "Daniel Caesar" in team\n+        assert "/assets/team-nwe.jpg?v=" in team\n+        nav = re.search(r'<nav class="site-nav".*?</nav>', team,\n+                        flags=re.DOTALL).group(0)\n+        assert nav.index(f'/{sub}about/') < nav.index(f'/{sub}team/')\n+        assert f'class="nav-link active" href="{build.BASE_PATH}/{sub}team/"' in nav\n+        assert any(entity.get("@type") == "BreadcrumbList"\n+                   for entity in _schema_entities(team))\n+    assert "創辦人・首席網站營運與維護顧問" in (\n+        ROOT / "site/team/index.html").read_text(encoding="utf-8")\n+    assert (ROOT / "site/assets/team-nwe.jpg").read_bytes() == (\n+        ROOT / "static/team-nwe.jpg").read_bytes()\n+\n     print("test_seo: OK")\n \n 

You found a secret theme!

8-bit mode is unlocked. Refresh to apply it now. You can switch themes anytime using the theme selector.