-
Notifications
You must be signed in to change notification settings - Fork 8
feat(site): add download landing page #992
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 1 commit
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
af3836a
feat(site): add download landing page
Astro-Han 40959ad
ci(labeler): route site/ to the ui label
Astro-Han 64a1281
ci: add Cloudflare Pages deploy workflow for site/
Astro-Han 14af166
fix(site): address Gemini review feedback
Astro-Han 7971c78
ci(site): pin Bun to 1.3.14 and add PR build-check
Astro-Han b473641
fix(site): register deploy-site workflow with bun-version pin guard
Astro-Han 33175f5
fix(ci): use valid wrangler-action ref and gate deploy to dev branch
Astro-Han 5e89e1d
fix(site): keep header controls on one row on phones
Astro-Han 3318805
Merge remote-tracking branch 'origin/dev' into claude/download-landing
Astro-Han c454a3f
fix(site): apply review fixes for landing page
Astro-Han File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| # build output | ||
| dist/ | ||
| # generated types | ||
| .astro/ | ||
| # dependencies | ||
| node_modules/ | ||
| # logs | ||
| npm-debug.log* | ||
| # environment | ||
| .env | ||
| .env.production | ||
| # macOS | ||
| .DS_Store |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| # PawWork site | ||
|
|
||
| Download landing page for PawWork. Built with [Astro](https://astro.build/) (plain CSS, no UI framework). Deploys as a static site to Cloudflare Pages, independent of the desktop app build. | ||
|
|
||
| ## Develop | ||
|
|
||
| ```sh | ||
| bun install | ||
| bun run dev # http://localhost:4321 | ||
| bun run build # outputs to dist/ | ||
| bun run preview # serve the production build locally | ||
| ``` | ||
|
|
||
| ## Structure | ||
|
|
||
| ``` | ||
| src/ | ||
| pages/index.astro page markup; English first paint + client-side CN/EN switch | ||
| layouts/Base.astro <head>, SEO tags, anti-flash theme script | ||
| styles/global.css all styling; light/dark via [data-theme], CN/EN via [data-lang] | ||
| i18n.ts EN/CN copy dictionary (single source of truth) | ||
| config.ts download links and repo URLs | ||
| public/ | ||
| app-icon.svg favicon + brand mark | ||
| ``` | ||
|
|
||
| ## Notes | ||
|
|
||
| - **Language**: first paint renders English for basic SEO; the client switches to Chinese based on browser language or the EN/中 toggle. Choice persists in `localStorage`. Per-language routes for SEO are deferred. | ||
| - **Download links**: `config.ts` currently points every button at the GitHub Releases page. Swap in China-hosted direct links (R2 / COS) once the updater fallback (issue #219) lands. | ||
| - **OG image**: `Base.astro` uses the app icon as a placeholder; replace with a dedicated 1200×630 share image. | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| import { defineConfig } from "astro/config"; | ||
|
|
||
| // Landing page; deploys as a static site to Cloudflare Pages, decoupled from the | ||
| // desktop app build. `site` provides the absolute origin for canonical / og:url; | ||
| // the production domain may change once registration is sorted out. | ||
| export default defineConfig({ | ||
| site: "https://pawwork.ai", | ||
| }); |
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| "name": "@pawwork/site", | ||
| "type": "module", | ||
| "version": "0.0.0", | ||
| "private": true, | ||
| "description": "PawWork download landing page", | ||
| "scripts": { | ||
| "dev": "astro dev", | ||
| "build": "astro build", | ||
| "preview": "astro preview", | ||
| "check": "astro check" | ||
| }, | ||
| "dependencies": { | ||
| "astro": "6.4.2" | ||
| } | ||
| } |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // Site-level constants. Download links currently point at the GitHub Releases | ||
| // page, where users pick the installer themselves. Once China-hosted storage | ||
| // (R2 / COS) and the updater fallback (issue #219) land, swap mac / macIntel / | ||
| // win for the per-platform direct links — nothing else on the page changes. | ||
|
|
||
| export const REPO_URL = "https://github.com/Astro-Han/pawwork"; | ||
| export const RELEASES_URL = `${REPO_URL}/releases/latest`; | ||
|
|
||
| export const DOWNLOAD = { | ||
| mac: RELEASES_URL, | ||
| macIntel: RELEASES_URL, | ||
| win: RELEASES_URL, | ||
| }; |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| // EN/CN copy dictionary: single source of truth. | ||
| // The server renders EN on first paint (basic SEO); the client swaps to the | ||
| // chosen language. Values carry a few inline tags (<b> <span> <u> <br>) and are | ||
| // injected as HTML. | ||
|
|
||
| export type Lang = "en" | "cn"; | ||
|
|
||
| export type Dict = Record<string, string>; | ||
|
|
||
| export const I18N: Record<Lang, Dict> = { | ||
| en: { | ||
| brand: "PawWork", | ||
| "nav.feat": "What it does", | ||
| tag: "Open-source · Free to use", | ||
| h1: 'Real work, done on your <span class="o">desktop</span>', | ||
|
Astro-Han marked this conversation as resolved.
|
||
| sub: "<b>No terminal. No API key. No paid plan.</b> Open PawWork, choose a folder, and ask in plain language.", | ||
| "dl.mac.t": "Download for macOS", | ||
| "dl.mac.s": "Apple Silicon", | ||
| "dl.intel.t": "macOS", | ||
| "dl.intel.s": "Intel", | ||
| "dl.win.t": "Windows", | ||
| "dl.win.s": "x64", | ||
| gh2: "On GitHub? <u>Grab the latest release →</u>", | ||
| wnote: | ||
| '<b>Windows:</b> If SmartScreen shows a warning on first launch, click "More info" → "Run anyway". The macOS build is signed and notarized — just open and go.', | ||
| shotnote: "Illustration, not a screenshot", | ||
| "mock.title": "PawWork — new task", | ||
| "mock.you": "Turn these 12 invoices into a spreadsheet I can review.", | ||
| "mock.ch": "Working on it…", | ||
| "mock.s1": "Read 12 PDFs", | ||
| "mock.s2": "Extracted vendor, date, total", | ||
| "mock.s3": "Building spreadsheet…", | ||
| "mock.rd": "ready to review", | ||
| "cap1.h": "Documents & data", | ||
| "cap1.p": "Extract invoice fields into a spreadsheet, generate a CSV summary, merge multiple PDFs.", | ||
| "cap2.h": "Research & writing", | ||
| "cap2.p": "Search the web, compare multiple pages and compile a memo, turn rough notes into a clean draft.", | ||
| "cap3.h": "Code & technical", | ||
| "cap3.p": "Understand a codebase, review a pull request, debug issues using logs and source code.", | ||
| foot: "Apache-2.0 · Built on OpenCode", | ||
| }, | ||
| cn: { | ||
| brand: "爪印", | ||
| "nav.feat": "功能", | ||
| tag: "开源 · 下载即用", | ||
| h1: '<span class="o">真能干活</span>,<br>跑在你电脑上', | ||
|
Astro-Han marked this conversation as resolved.
|
||
| sub: "<b>不用终端,不用 API key,不用付费。</b>打开爪印,选个文件夹,直接告诉它你要什么。", | ||
| "dl.mac.t": "下载 macOS 版", | ||
| "dl.mac.s": "Apple 芯片", | ||
| "dl.intel.t": "macOS", | ||
| "dl.intel.s": "Intel", | ||
| "dl.win.t": "Windows", | ||
| "dl.win.s": "x64", | ||
| gh2: "有 GitHub?<u>去 Releases 下最新版 →</u>", | ||
| wnote: | ||
| "<b>Windows 用户</b>首次打开时如果弹出 SmartScreen 提示,点「更多信息」→「仍要运行」。macOS 版已签名公证,不会出现此提示。", | ||
| shotnote: "示意,非实拍", | ||
| "mock.title": "爪印 — 新任务", | ||
| "mock.you": "帮我把这 12 张发票整理成一张表格,方便逐笔核对。", | ||
| "mock.ch": "正在处理…", | ||
| "mock.s1": "读完 12 个 PDF", | ||
| "mock.s2": "抽出供应商、日期、金额", | ||
| "mock.s3": "正在生成表格…", | ||
| "mock.rd": "待核对", | ||
| "cap1.h": "文档与数据", | ||
| "cap1.p": "从发票提取信息填入表格、为 CSV 生成摘要、合并多个 PDF。", | ||
| "cap2.h": "研究与写作", | ||
| "cap2.p": "上网查资料、对比多篇网页整理成备忘、把零散笔记写成一篇稿子。", | ||
| "cap3.h": "代码与技术", | ||
| "cap3.p": "理解一个项目、review 他人的 PR、根据日志和源码定位错误。", | ||
| foot: "Apache-2.0 · 基于 OpenCode", | ||
| }, | ||
| }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| --- | ||
| // Page shell: basic SEO tags in <head> plus the anti-flash theme script. | ||
| // Language switching happens on the client; first paint renders English. | ||
| import "../styles/global.css"; | ||
|
|
||
| interface Props { | ||
| title: string; | ||
| description: string; | ||
| image?: string; | ||
| } | ||
|
|
||
| const { title, description, image = "/app-icon.svg" } = Astro.props; | ||
| const canonical = new URL(Astro.url.pathname, Astro.site).href; | ||
| const ogImage = new URL(image, Astro.site).href; | ||
| --- | ||
|
|
||
| <!doctype html> | ||
| <html lang="en" data-theme="light" data-lang="en"> | ||
| <head> | ||
| <meta charset="utf-8" /> | ||
| <meta name="viewport" content="width=device-width, initial-scale=1" /> | ||
| <link rel="icon" type="image/svg+xml" href="/app-icon.svg" /> | ||
| <link rel="canonical" href={canonical} /> | ||
| <title>{title}</title> | ||
| <meta name="description" content={description} /> | ||
| <meta name="theme-color" content="#ff5910" /> | ||
|
|
||
| <meta property="og:type" content="website" /> | ||
| <meta property="og:site_name" content="PawWork" /> | ||
| <meta property="og:title" content={title} /> | ||
| <meta property="og:description" content={description} /> | ||
| <meta property="og:url" content={canonical} /> | ||
| {/* TODO: replace with a dedicated 1200×630 share image; app icon is a placeholder */} | ||
| <meta property="og:image" content={ogImage} /> | ||
| <meta name="twitter:card" content="summary_large_image" /> | ||
| <meta name="twitter:title" content={title} /> | ||
| <meta name="twitter:description" content={description} /> | ||
| <meta name="twitter:image" content={ogImage} /> | ||
|
|
||
| {/* Set the theme before first paint so dark-mode users don't see a light flash */} | ||
| <script is:inline> | ||
| (function () { | ||
| try { | ||
| var qs = new URLSearchParams(location.search); | ||
| var stored = localStorage.getItem("pw-theme"); | ||
| var mq = window.matchMedia("(prefers-color-scheme: dark)"); | ||
| var theme = qs.get("theme") || stored || (mq.matches ? "dark" : "light"); | ||
| if (theme !== "dark" && theme !== "light") theme = "light"; | ||
| document.documentElement.setAttribute("data-theme", theme); | ||
| } catch (e) {} | ||
| })(); | ||
| </script> | ||
| </head> | ||
| <body> | ||
| <slot /> | ||
| </body> | ||
| </html> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,150 @@ | ||
| --- | ||
| import Base from "../layouts/Base.astro"; | ||
| import { I18N } from "../i18n"; | ||
| import { DOWNLOAD, REPO_URL, RELEASES_URL } from "../config"; | ||
|
|
||
| // First paint renders English (basic SEO); the client then switches by browser | ||
| // language or the user's toggle. | ||
| const t = I18N.en; | ||
|
|
||
| const seoTitle = "PawWork — Real work, done on your desktop"; | ||
| const seoDesc = | ||
| "Free, open-source AI desktop app for macOS and Windows. No terminal, no API key, no paid plan — open PawWork, choose a folder, and ask in plain language."; | ||
|
Astro-Han marked this conversation as resolved.
|
||
| --- | ||
|
|
||
| <Base title={seoTitle} description={seoDesc}> | ||
| <svg width="0" height="0" style="position:absolute" | ||
| ><defs> | ||
| <g id="gh" | ||
| ><path | ||
| d="M8 0C3.58 0 0 3.58 0 8c0 3.54 2.29 6.53 5.47 7.59.4.07.55-.17.55-.38 0-.19-.01-.82-.01-1.49-2.01.37-2.53-.49-2.69-.94-.09-.23-.48-.94-.82-1.13-.28-.15-.68-.52-.01-.53.63-.01 1.08.58 1.23.82.72 1.21 1.87.87 2.33.66.07-.52.28-.87.51-1.07-1.78-.2-3.64-.89-3.64-3.95 0-.87.31-1.59.82-2.15-.08-.2-.36-1.02.08-2.12 0 0 .67-.21 2.2.82.64-.18 1.32-.27 2-.27.68 0 1.36.09 2 .27 1.53-1.04 2.2-.82 2.2-.82.44 1.1.16 1.92.08 2.12.51.56.82 1.27.82 2.15 0 3.07-1.87 3.75-3.65 3.95.29.25.54.73.54 1.48 0 1.07-.01 1.93-.01 2.2 0 .21.15.46.55.38A8.01 8.01 0 0016 8c0-4.42-3.58-8-8-8z" | ||
| ></path></g | ||
| > | ||
| <g id="apple" | ||
| ><path | ||
| d="M16.36 12.78c-.02-2.05 1.67-3.03 1.75-3.08-.95-1.4-2.44-1.59-2.97-1.61-1.26-.13-2.46.74-3.1.74-.64 0-1.63-.72-2.68-.7-1.38.02-2.65.8-3.36 2.03-1.43 2.49-.37 6.17 1.03 8.19.68.99 1.49 2.1 2.55 2.06 1.02-.04 1.41-.66 2.65-.66 1.23 0 1.58.66 2.66.64 1.1-.02 1.79-1 2.47-1.99.78-1.14 1.1-2.25 1.12-2.31-.02-.01-2.15-.83-2.17-3.27zM14.3 6.84c.56-.68.94-1.62.84-2.56-.81.03-1.79.54-2.37 1.22-.52.6-.98 1.56-.86 2.48.9.07 1.83-.46 2.39-1.14z" | ||
| ></path></g | ||
| > | ||
| <g id="win" | ||
| ><path | ||
| d="M3 5.4 10.2 4.4v6.9H3V5.4zM11.1 4.27 21 3v8.3h-9.9V4.27zM3 12.2h7.2v6.9L3 18.1v-5.9zM11.1 12.2H21V21l-9.9-1.3v-7.5z" | ||
| ></path></g | ||
| > | ||
| <g id="paw" | ||
| ><circle cx="24.8" cy="22" r="4.4"></circle><circle cx="39.2" cy="22" r="4.4"></circle><circle | ||
| cx="18.3" | ||
| cy="30.75" | ||
| r="3.8"></circle><circle cx="45.75" cy="30.75" r="3.8"></circle><path | ||
| d="M32 29.2C24.2 29.2 19.8 37.6 19.8 42.6c0 3.8 3.5 5.3 8.5 3.5 1.8-.7 5.6-.7 7.5 0 5 1.8 8.4.3 8.4-3.5 0-5-4.4-13.4-12.2-13.4z" | ||
| ></path></g | ||
| > | ||
| <g id="ck" | ||
| ><path | ||
| d="M20 6 9 17l-5-5" | ||
| fill="none" | ||
| stroke="currentColor" | ||
| stroke-width="3" | ||
| stroke-linecap="round" | ||
| stroke-linejoin="round"></path></g | ||
| > | ||
| </defs></svg | ||
| > | ||
|
|
||
| <div class="in"> | ||
| <div class="bar"> | ||
| <span class="mark"><img src="/app-icon.svg" alt="" /><span data-i18n="brand" set:html={t["brand"]} /></span> | ||
| <nav class="nav"> | ||
| <a class="lk" href="#feat" data-i18n="nav.feat" set:html={t["nav.feat"]}></a> | ||
| <a class="lk gh" href={REPO_URL}><svg viewBox="0 0 16 16" fill="currentColor"><use href="#gh"></use></svg>GitHub</a> | ||
| <button class="toggle" id="lg" aria-label="Switch language"><span class="ll-en">EN</span><span class="ll-cn">中</span></button> | ||
| <button class="toggle" id="tg" aria-label="Switch theme"> | ||
| <svg class="sun" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2M12 20v2M4.9 4.9l1.4 1.4M17.7 17.7l1.4 1.4M2 12h2M20 12h2M4.9 19.1l1.4-1.4M17.7 6.3l1.4-1.4"></path></svg> | ||
| <svg class="moon" viewBox="0 0 24 24" fill="currentColor"><path d="M21 12.8A9 9 0 1 1 11.2 3a7 7 0 0 0 9.8 9.8z"></path></svg> | ||
| </button> | ||
| </nav> | ||
| </div> | ||
|
|
||
| <section class="hero"> | ||
| <div> | ||
| <span class="tag"><span class="dot"></span><span data-i18n="tag" set:html={t["tag"]} /></span> | ||
| <h1 data-i18n="h1" set:html={t["h1"]}></h1> | ||
| <p class="sub" data-i18n="sub" set:html={t["sub"]}></p> | ||
| <div class="dlblock"> | ||
| <div class="dl"> | ||
| <a class="dlbtn lead" href={DOWNLOAD.mac}><svg viewBox="0 0 24 24" fill="currentColor"><use href="#apple"></use></svg><span class="t"><b data-i18n="dl.mac.t" set:html={t["dl.mac.t"]}></b><span data-i18n="dl.mac.s" set:html={t["dl.mac.s"]}></span></span></a> | ||
| <a class="dlbtn" href={DOWNLOAD.macIntel}><svg viewBox="0 0 24 24" fill="currentColor"><use href="#apple"></use></svg><span class="t"><b data-i18n="dl.intel.t" set:html={t["dl.intel.t"]}></b><span data-i18n="dl.intel.s" set:html={t["dl.intel.s"]}></span></span></a> | ||
| <a class="dlbtn" href={DOWNLOAD.win}><svg viewBox="0 0 24 24" fill="currentColor"><use href="#win"></use></svg><span class="t"><b data-i18n="dl.win.t" set:html={t["dl.win.t"]}></b><span data-i18n="dl.win.s" set:html={t["dl.win.s"]}></span></span></a> | ||
| </div> | ||
| <div class="meta"><a class="gh2" href={RELEASES_URL}><svg viewBox="0 0 16 16" fill="currentColor"><use href="#gh"></use></svg> <span data-i18n="gh2" set:html={t["gh2"]} /></a></div> | ||
| <p class="wnote" data-i18n="wnote" set:html={t["wnote"]}></p> | ||
| </div> | ||
| </div> | ||
|
|
||
| <div class="stage"> | ||
| <span class="shotnote" data-i18n="shotnote" set:html={t["shotnote"]}></span> | ||
| <div class="mock"> | ||
| <div class="top"><span class="dots"><i></i><i></i><i></i></span><span class="ttl" data-i18n="mock.title" set:html={t["mock.title"]}></span></div> | ||
| <div class="body"> | ||
| <span class="you" data-i18n="mock.you" set:html={t["mock.you"]}></span> | ||
| <div class="card"> | ||
| <div class="ch"><span class="sp"></span> <span data-i18n="mock.ch" set:html={t["mock.ch"]}></span></div> | ||
| <div class="step"><svg class="ck" viewBox="0 0 24 24"><use href="#ck"></use></svg> <span data-i18n="mock.s1" set:html={t["mock.s1"]}></span></div> | ||
| <div class="step"><svg class="ck" viewBox="0 0 24 24"><use href="#ck"></use></svg> <span data-i18n="mock.s2" set:html={t["mock.s2"]}></span></div> | ||
| <div class="step run"><span class="rb"></span> <span data-i18n="mock.s3" set:html={t["mock.s3"]}></span></div> | ||
| </div> | ||
| <div class="out"><svg class="x" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="3" width="16" height="18" rx="2"></rect><path d="M8 9h8M8 13h8M8 17h5"></path></svg> invoices.xlsx <span class="rd" data-i18n="mock.rd" set:html={t["mock.rd"]}></span></div> | ||
| </div> | ||
| </div> | ||
| <span class="pawchip"><svg viewBox="0 0 64 64" fill="currentColor"><use href="#paw"></use></svg></span> | ||
| </div> | ||
| </section> | ||
|
|
||
| <section class="caps" id="feat"> | ||
| <div class="cap"><span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="4" y="3" width="16" height="18" rx="2"></rect><path d="M8 8h8M8 12h8M8 16h5"></path></svg></span><h3 data-i18n="cap1.h" set:html={t["cap1.h"]}></h3><p data-i18n="cap1.p" set:html={t["cap1.p"]}></p></div> | ||
| <div class="cap"><span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="11" cy="11" r="7"></circle><path d="M21 21l-4.3-4.3"></path></svg></span><h3 data-i18n="cap2.h" set:html={t["cap2.h"]}></h3><p data-i18n="cap2.p" set:html={t["cap2.p"]}></p></div> | ||
| <div class="cap"><span class="ic"><svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M8 6l-6 6 6 6M16 6l6 6-6 6"></path></svg></span><h3 data-i18n="cap3.h" set:html={t["cap3.h"]}></h3><p data-i18n="cap3.p" set:html={t["cap3.p"]}></p></div> | ||
| </section> | ||
| </div> | ||
|
|
||
| <footer><div class="foot"> | ||
| <span class="mark"><img src="/app-icon.svg" alt="" /><span data-i18n="brand" set:html={t["brand"]} /></span> | ||
| <span data-i18n="foot" set:html={t["foot"]}></span> | ||
| </div></footer> | ||
|
|
||
| <script define:vars={{ I18N }}> | ||
| const root = document.documentElement; | ||
| const tg = document.getElementById("tg"); | ||
| const lg = document.getElementById("lg"); | ||
| const qs = new URLSearchParams(location.search); | ||
|
|
||
| function applyLang(l) { | ||
| root.setAttribute("data-lang", l); | ||
| root.setAttribute("lang", l === "cn" ? "zh" : "en"); | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| const dict = I18N[l]; | ||
| document.querySelectorAll("[data-i18n]").forEach((el) => { | ||
| const key = el.getAttribute("data-i18n"); | ||
| if (dict[key] != null) el.innerHTML = dict[key]; | ||
| }); | ||
| try { | ||
| localStorage.setItem("pw-lang", l); | ||
|
Astro-Han marked this conversation as resolved.
|
||
| } catch (e) {} | ||
| } | ||
|
|
||
| function applyTheme(theme) { | ||
| root.setAttribute("data-theme", theme); | ||
| try { | ||
| localStorage.setItem("pw-theme", theme); | ||
| } catch (e) {} | ||
| } | ||
|
|
||
| // Initial language: URL param > localStorage > browser language, fall back to English | ||
| let lang = qs.get("lang") || localStorage.getItem("pw-lang"); | ||
| if (lang !== "cn" && lang !== "en") { | ||
| lang = (navigator.language || "").toLowerCase().indexOf("zh") === 0 ? "cn" : "en"; | ||
| } | ||
| applyLang(lang); | ||
|
Astro-Han marked this conversation as resolved.
Outdated
|
||
|
|
||
| tg.addEventListener("click", () => applyTheme(root.getAttribute("data-theme") === "dark" ? "light" : "dark")); | ||
| lg.addEventListener("click", () => applyLang(root.getAttribute("data-lang") === "cn" ? "en" : "cn")); | ||
|
Astro-Han marked this conversation as resolved.
Outdated
|
||
| </script> | ||
| </Base> | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.