diff --git a/.github/workflows/check_links.yml b/.github/workflows/check_links.yml
new file mode 100644
index 00000000..fc085811
--- /dev/null
+++ b/.github/workflows/check_links.yml
@@ -0,0 +1,24 @@
+name: check_image_links
+
+# Broken image links are easy to introduce and hard to spot: the published site
+# keeps serving stale copies uploaded by earlier builds, so a page can look fine
+# long after the image was moved or deleted from the repo. Check it here instead.
+
+on:
+ push:
+ branches: [ main ]
+ pull_request:
+ workflow_dispatch:
+
+jobs:
+ check-image-links:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v4
+
+ - uses: actions/setup-python@v5
+ with:
+ python-version: '3.11'
+
+ - name: Check that referenced images exist
+ run: python3 tools/check_image_links.py --list
diff --git a/README.md b/README.md
index 754f0301..32e8a1bb 100755
--- a/README.md
+++ b/README.md
@@ -18,7 +18,7 @@ More contribute doc :
## Preview locally
```bash
-git clone https://github.com/sipeed/sipeed_wiki.git
+git clone --depth=1 https://github.com/sipeed/sipeed_wiki.git
cd sipeed_wiki
uv sync
uv run teedoc serve
@@ -27,6 +27,23 @@ uv run teedoc serve
`uv sync` creates/updates the `.venv` from [`pyproject.toml`](./pyproject.toml) and `uv.lock`, which already includes `teedoc` and all plugins used by this site (so `teedoc install` is not needed).
Install [uv](https://docs.astral.sh/uv/) first if you don't have it.
+### Why `--depth=1`
+
+This is a documentation site, so most of the repo is images. Git keeps every past revision
+of every one of them forever, including large originals that have since been replaced by
+compressed versions. A full clone therefore downloads roughly **1 GB of history** even
+though you only ever need the current version of each file.
+
+`--depth=1` fetches just the latest commit and skips all of that. Editing docs and opening
+a pull request works exactly the same from a shallow clone. Use a full clone only when you
+actually need the history (`git log`, `git blame`, `git bisect`) — you can always deepen a
+shallow clone later with `git fetch --unshallow`.
+
+When adding images, please keep them small (ideally under 300 KB, 500 KB max): anything
+committed here stays in the history for good, so an oversized image costs every future
+contributor bandwidth even after it is deleted. See [AGENTS.md](./AGENTS.md) for the full
+image and authoring guidelines.
+
More build tool usage see [teedoc](http://github.com/teedoc/teedoc)
## For administrator
diff --git a/tools/check_image_links.py b/tools/check_image_links.py
new file mode 100644
index 00000000..7e474f8d
--- /dev/null
+++ b/tools/check_image_links.py
@@ -0,0 +1,145 @@
+#!/usr/bin/env python3
+"""Check that every image referenced by the docs actually exists.
+
+Broken image links are easy to introduce (moving an assets/ folder, deleting a
+file without touching the pages that use it) and hard to notice, because the
+published site keeps serving stale copies that were uploaded by earlier builds.
+This script catches them at CI time instead.
+
+Only real image references are checked: markdown ```` and HTML
+``
``. Fenced and inline code is stripped first, so example commands
+like ``convert in.jpg out.jpg`` are not mistaken for links.
+
+Usage:
+ python3 tools/check_image_links.py # fail on anything not in the baseline
+ python3 tools/check_image_links.py --list # print every broken link
+ python3 tools/check_image_links.py --update-baseline
+"""
+
+import argparse
+import json
+import os
+import re
+import sys
+import urllib.parse
+
+ROOT = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+BASELINE = os.path.join(ROOT, "tools", "known_broken_images.txt")
+SCAN_DIRS = ["docs", "news", "pages", "layout"]
+SKIP_DIRS = {".git", "out", ".venv", "node_modules", "__pycache__"}
+PAGE_EXTS = {".md", ".html"}
+IMG_EXTS = {".png", ".jpg", ".jpeg", ".gif", ".webp", ".svg", ".bmp", ".ico"}
+
+FENCED = re.compile(r"```.*?```|~~~.*?~~~", re.S)
+INLINE_CODE = re.compile(r"`[^`\n]*`")
+MD_IMG = re.compile(r"!\[[^\]]*\]\(\s*([^)\s>]+)")
+HTML_IMG = re.compile(r"
]*?\bsrc\s*=\s*[\"']([^\"']+)", re.I)
+
+
+def load_routes():
+ """Map URL prefixes to on-disk directories, as teedoc does when building."""
+ with open(os.path.join(ROOT, "site_config.json"), encoding="utf-8") as f:
+ cfg = json.load(f)
+ routes = {}
+ for group in ("docs", "pages", "assets"):
+ for url, path in (cfg.get("route", {}).get(group) or {}).items():
+ routes[url] = path
+ for url, targets in (cfg.get("translate", {}).get("docs") or {}).items():
+ for t in targets:
+ routes[t["url"]] = t["src"]
+ return dict(sorted(routes.items(), key=lambda kv: -len(kv[0])))
+
+
+def resolve(ref, page_path, routes):
+ """Return the on-disk path a reference points at, or None if it is external."""
+ ref = ref.strip().strip("'\"")
+ if ref.startswith(("http://", "https://", "//", "data:", "mailto:")):
+ return None
+ ref = urllib.parse.unquote(ref.split("#")[0].split("?")[0])
+ if not ref or os.path.splitext(ref)[1].lower() not in IMG_EXTS:
+ return None
+ if ref.startswith("/"):
+ for url, path in routes.items():
+ if ref.startswith(url):
+ return os.path.normpath(os.path.join(ROOT, path, ref[len(url):]))
+ return os.path.normpath(os.path.join(ROOT, ref.lstrip("/")))
+ return os.path.normpath(os.path.join(os.path.dirname(page_path), ref))
+
+
+def scan():
+ routes = load_routes()
+ broken = []
+ for top in SCAN_DIRS:
+ for dirpath, dirnames, filenames in os.walk(os.path.join(ROOT, top)):
+ dirnames[:] = [d for d in dirnames if d not in SKIP_DIRS]
+ for name in filenames:
+ if os.path.splitext(name)[1].lower() not in PAGE_EXTS:
+ continue
+ page = os.path.join(dirpath, name)
+ try:
+ text = open(page, encoding="utf-8").read()
+ except (OSError, UnicodeDecodeError):
+ continue
+ text = INLINE_CODE.sub(" ", FENCED.sub(" ", text))
+ rel_page = os.path.relpath(page, ROOT)
+ for ref in set(MD_IMG.findall(text) + HTML_IMG.findall(text)):
+ if "{{" in ref or "{%" in ref: # jinja template expression
+ continue
+ target = resolve(ref, page, routes)
+ if target and not os.path.exists(target):
+ broken.append(f"{rel_page}\t{ref}")
+ return sorted(set(broken))
+
+
+def read_baseline():
+ if not os.path.exists(BASELINE):
+ return set()
+ with open(BASELINE, encoding="utf-8") as f:
+ return {l.rstrip("\n") for l in f if l.strip() and not l.startswith("#")}
+
+
+def main():
+ ap = argparse.ArgumentParser()
+ ap.add_argument("--list", action="store_true", help="print all broken links")
+ ap.add_argument("--update-baseline", action="store_true",
+ help="record the current broken links as accepted")
+ args = ap.parse_args()
+
+ broken = scan()
+ if args.update_baseline:
+ with open(BASELINE, "w", encoding="utf-8") as f:
+ f.write("# Image links that are already broken and not yet fixed.\n"
+ "# CI fails on anything NOT listed here, so this file may shrink\n"
+ "# but must never grow. Regenerate with:\n"
+ "# python3 tools/check_image_links.py --update-baseline\n")
+ f.write("\n".join(broken) + "\n")
+ print(f"baseline updated: {len(broken)} known broken links")
+ return 0
+
+ baseline = read_baseline()
+ new = [b for b in broken if b not in baseline]
+ fixed = [b for b in baseline if b not in broken]
+
+ if args.list:
+ for b in broken:
+ page, ref = b.split("\t")
+ print(f"{page}: {ref}")
+
+ print(f"{len(broken)} broken image links "
+ f"({len(baseline)} known, {len(new)} new, {len(fixed)} fixed since baseline)")
+
+ if fixed:
+ print(f"\n{len(fixed)} link(s) in the baseline are fixed - please run "
+ f"`python3 tools/check_image_links.py --update-baseline` and commit.")
+ if new:
+ print(f"\nNew broken image links introduced by this change:\n")
+ for b in new:
+ page, ref = b.split("\t")
+ print(f" {page}\n -> {ref}")
+ print("\nFix the path, add the missing image, or remove the reference.")
+ return 1
+ return 0
+
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tools/known_broken_images.txt b/tools/known_broken_images.txt
new file mode 100644
index 00000000..0783d3aa
--- /dev/null
+++ b/tools/known_broken_images.txt
@@ -0,0 +1,125 @@
+# Image links that are already broken and not yet fixed.
+# CI fails on anything NOT listed here, so this file may shrink
+# but must never grow. Regenerate with:
+# python3 tools/check_image_links.py --update-baseline
+docs/hardware/en/kvm/NanoKVM/user_guide.md ./../../../assets/NanoKVM/guide/atx_2024.png
+docs/hardware/en/kvm/NanoKVM_Pro/extended.md ./../../../assets/NanoKVM/pro/extended/hdmi.jpg
+docs/hardware/en/kvm/NanoKVM_Pro/ledstrip.md ../../../assets/NanoKVM/pro/ledstrip/ledstrip_show.png
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_architecture.png
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_box.png
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_install_goldfinger.png
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_install_install_lm3a.png
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_install_slot.jpeg
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_power_atx20_cable.png
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_power_jumpwire.png
+docs/hardware/en/lichee/K1/lc3a/lc3a.md ./assets/lc3a/lc3a_top.png
+docs/hardware/en/lichee/K1/lpi3a/3_burn_image.md ./assets/images/deepin.jpg
+docs/hardware/en/lichee/K1/lpi3a/3_burn_image.md ./assets/images/fedora.png
+docs/hardware/en/lichee/K1/lpi3a/3_burn_image.md ./assets/images/irradium.png
+docs/hardware/en/lichee/K1/lpi3a/3_burn_image.md ./assets/images/openkylin.png
+docs/hardware/en/lichee/RV_Nano/2_unbox.md ./../assets/RV_Nano/unbox/1.jpg
+docs/hardware/en/lichee/th1520/lbook4a/lbook4a.md ./assets/lbook/lbook.jpg
+docs/hardware/en/lichee/th1520/lpi4a/8_application.md /images/kubernetes.png
+docs/hardware/en/logic_analyzer/slogic16u3/Software_User_Guide.md ./assets/Screenshots/SLogic_Connect_1.png
+docs/hardware/en/maix/modules_spmod/spmod_bt.md ../../assets/spmod/spmod_bt/back.jpg
+docs/hardware/en/maix/modules_spmod/spmod_bt.md ../../assets/spmod/spmod_bt/connection.png
+docs/hardware/en/maix/modules_spmod/spmod_eink.md ../../assets/spmod/spmod_eink/connection.png
+docs/hardware/en/maix/modules_spmod/spmod_eink.md ../../assets/spmod/spmod_eink/sp_eink_c.png
+docs/hardware/en/maix/modules_spmod/spmod_lcd1.14.md ../../assets/spmod/spmod_lcd1.14//connection.png
+docs/hardware/en/maix/modules_spmod/spmod_lcd1.14.md ../../assets/spmod/spmod_lcd1.14/back.png
+docs/hardware/en/maix/modules_spmod/spmod_lora.md ../../assets/spmod/spmod_lora//connection.png
+docs/hardware/en/maix/modules_spmod/spmod_lora.md ../../assets/spmod/spmod_lora/back.png
+docs/hardware/en/maix/modules_spmod/spmod_rfid.md ../../assets/spmod/spmod_rfid/back.png
+docs/hardware/en/maix/modules_spmod/spmod_rfid.md ../../assets/spmod/spmod_rfid/connection.png
+docs/hardware/en/maix/modules_spmod/spmod_rfid.md ../../assets/spmod/spmod_rfid/sp_rfid.png
+docs/hardware/en/maix/modules_spmod/spmod_tof.md ../../assets/spmod/spmod_tof/back.png
+docs/hardware/en/maix/modules_spmod/spmod_tof.md ../../assets/spmod/spmod_tof/connection.png
+docs/hardware/en/maix/modules_spmod/spmod_tof.md ../../assets/spmod/spmod_tof/log_c.png
+docs/hardware/en/maix/modules_spmod/spmod_weather.md ../../assets/spmod/spmod_weather/back.png
+docs/hardware/en/maix/modules_spmod/spmod_weather.md ../../assets/spmod/spmod_weather/connection.png
+docs/hardware/en/maix/modules_spmod/spmod_weather.md ../../assets/spmod/spmod_weather/demo.gif
+docs/hardware/en/maix/modules_spmod/spmod_weather.md ../../assets/spmod/spmod_weather/log_c.png
+docs/hardware/en/maixcam/README.md ../../assets/maixcam/maixcam.jpeg
+docs/hardware/en/maixface/mf_ml_module/mf0_ml_module.md ../assets/mf_module/mf1_bottom_v2(nand).png
+docs/hardware/en/maixface/mf_ml_module/mf1_ml_module.md ../../assests/front.png
+docs/hardware/en/maixface/mf_ml_module/mf1_ml_module.md assests/bottom.jpg
+docs/hardware/en/maixface/mf_ml_module/mf_update_firmwave.md ../../assets/mf_module/mf1/mf_dual_camera_2.png
+docs/hardware/en/modules_spmod/spmod_template.md ../../assets/spmod/sipeed_spmod_xxx.jpg
+docs/hardware/en/tang/tang-mega-138k/mega-138k-pro.md ./assets/partno_138K_Pro.png
+docs/hardware/maixface/en/core_modules/k210_core_modules.md ../../assets/mf_module/m1_m1w/sipeed_m1_module.png
+docs/hardware/maixface/en/core_modules/k210_core_modules.md ../../assets/mf_module/m1_m1w/sipeed_m1w_module.png
+docs/hardware/maixface/en/core_modules/k210_core_modules.md ../../assets/mf_module/m1n/sipeed_m1n_module.png
+docs/hardware/maixface/en/core_modules/k210_core_modules.md ../../assets/mf_module/mf0_mf0dock/sipeed_mf0_module.png
+docs/hardware/maixface/en/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_buttom.png
+docs/hardware/maixface/en/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_dock_flash.png
+docs/hardware/maixface/en/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_flash.png
+docs/hardware/maixface/en/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_pin_map.png
+docs/hardware/maixface/en/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_upgrade_firmwave.png
+docs/hardware/maixface/en/mf_ml_module/mf0_ml_module.md ../assets/mf_module/mf1_bottom_v2(nand).png
+docs/hardware/maixface/en/mf_ml_module/mf1_ml_module.md ../../assests/front.png
+docs/hardware/maixface/en/mf_ml_module/mf1_ml_module.md ../../assets/mf_module/mf1/mf1_pin_map.png
+docs/hardware/maixface/en/mf_ml_module/mf1_ml_module.md assests/bottom.jpg
+docs/hardware/maixface/en/mf_ml_module/mf_update_firmwave.md ../../assets/mf_module/mf1/mf1_view.png
+docs/hardware/maixface/en/mf_ml_module/mf_update_firmwave.md ../../assets/mf_module/mf1/mf_dual_camera_1.jpg
+docs/hardware/maixface/en/mf_ml_module/mf_update_firmwave.md ../../assets/mf_module/mf1/mf_dual_camera_2.png
+docs/hardware/maixface/zh/core_modules/k210_core_modules.md ../../assets/mf_module/m1_m1w/K210_boot_reference.png
+docs/hardware/maixface/zh/core_modules/k210_core_modules.md ../../assets/mf_module/m1_m1w/sipeed_m1_module.png
+docs/hardware/maixface/zh/core_modules/k210_core_modules.md ../../assets/mf_module/m1_m1w/sipeed_m1w_module.png
+docs/hardware/maixface/zh/core_modules/k210_core_modules.md ../../assets/mf_module/m1n/sipeed_m1n_module.png
+docs/hardware/maixface/zh/core_modules/k210_core_modules.md ../../assets/mf_module/mf0_mf0dock/sipeed_mf0_module.png
+docs/hardware/maixface/zh/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_buttom.png
+docs/hardware/maixface/zh/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_dock_flash.png
+docs/hardware/maixface/zh/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_flash.png
+docs/hardware/maixface/zh/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_pin_map.png
+docs/hardware/maixface/zh/mf_ml_module/mf0_ml_module.md ../../assets/mf_module/mf0_mf0dock/mf0_upgrade_firmwave.png
+docs/hardware/maixface/zh/mf_ml_module/mf0_ml_module.md ../assets/mf_module/mf1_bottom_v2(nand).png
+docs/hardware/maixface/zh/mf_ml_module/mf1_ml_module.md ../../assests/front.png
+docs/hardware/maixface/zh/mf_ml_module/mf1_ml_module.md ../../assets/mf_module/mf1/mf1_pin_map.png
+docs/hardware/maixface/zh/mf_ml_module/mf1_ml_module.md assests/bottom.jpg
+docs/hardware/maixface/zh/mf_ml_module/mf_precautions.md ../../assets/other/mf_precautions.png
+docs/hardware/maixface/zh/mf_ml_module/mf_update_firmwave.md ../../assets/mf_module/mf1/mf1_view.png
+docs/hardware/maixface/zh/mf_ml_module/mf_update_firmwave.md ../../assets/mf_module/mf1/mf_dual_camera_1.jpg
+docs/hardware/maixface/zh/mf_ml_module/mf_update_firmwave.md ../../assets/mf_module/mf1/mf_dual_camera_2.png
+docs/hardware/zh/lichee/th1520/lbook4a/lbook4a.md ./assets/lbook/lbook.jpg
+docs/hardware/zh/logic_analyzer/slogic16u3/Software_User_Guide.md ../../../en/logic_analyzer/slogic16u3/assets/Screenshots/SLogic_Connect_1.png
+docs/hardware/zh/maixII/M2/usage.md /news/MaixPy3/v831_usage/assest/adb.jpg
+docs/hardware/zh/maixII/M2/usage.md /news/MaixPy3/v831_usage/assest/delete.jpg
+docs/hardware/zh/maixcam/README.md ../../assets/maixcam/maixcam.jpeg
+docs/hardware/zh/maixface/mf_ml_module/mf0_ml_module.md ../assets/mf_module/mf1_bottom_v2(nand).png
+docs/hardware/zh/maixface/mf_ml_module/mf0_ml_module.md ./../zh/maixface/assets/mf_module/mf0_mf0dock/mf0_dock_flash.png
+docs/hardware/zh/maixface/mf_ml_module/mf1_ml_module.md ./../assests/front.png
+docs/hardware/zh/maixface/mf_ml_module/mf1_ml_module.md assests/bottom.jpg
+docs/hardware/zh/modules_spmod/spmod_template.md ../../assets/spmod/sipeed_spmod_xxx.jpg
+docs/hardware/zh/tang/Tang-Nano-9K/examples/LCD.md ./../../Tang-Nano-1K/Nano_1K_examples/led/assets/add_ip_file_in_project.png
+docs/hardware/zh/tang/tang-PMOD/FPGA_PMOD.md ./assets/PMOD_HUb75E_bot.jpg
+docs/soft/Lichee/zh/Lichee-RV/contribution/Dual_screen_display/environment.md ./assets/enviroument.png
+docs/soft/Lichee/zh/Lichee-RV/get_started/user.md ./../assets/RV/wifi-1.jpg
+docs/soft/Lichee/zh/Lichee-RV/get_started/user.md ./../assets/RV/wifi-2.jpg
+docs/soft/Lichee/zh/MaixII/V83X/contribution/v83x_sdk_compile/V83x_sdk_kernel_compile.md ./assets/11.png
+docs/soft/Lichee/zh/Zero-Doc/System_Development/SPI_falsh_doc4.md ./../static/System_Development/uboot_conf_9.gif
+docs/soft/Tang/en/Gowin/Tang_nano/README.md ../assets/sipeed_longan_logo.jpg
+docs/soft/Tang/zh/Tang-Common-Doc/examples/4K_led.md ./../../../Nano-4K/4K-led-2.png
+docs/soft/maixpy/en/course/others/maixui.md ./image/app_main.gif
+docs/soft/maixpy/en/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_bit/BiT.png
+docs/soft/maixpy/en/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_cube/maixcube_2020-06-13_06-31-29.png
+docs/soft/maixpy/en/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_dock/Dan_Dock.png
+docs/soft/maixpy/en/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_duino/maixduino_0.png
+docs/soft/maixpy/en/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_go/Go.jpg
+docs/soft/maixpy/en/get_started/knowledge_git_github.md /assets/other/github_star.jpg
+docs/soft/maixpy/en/get_started/upgrade_esp8285_firmware.md ../../assets/hardware/maix_dock/sipeed_maix_dock_m1w_2.png
+docs/soft/maixpy/en/get_started/upgrade_maixpy_firmware.md ../../assets/kflash_gui_screenshot_download.png
+docs/soft/maixpy/zh/course/others/maixui.md ./image/app_main.gif
+docs/soft/maixpy/zh/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_bit/BiT.png
+docs/soft/maixpy/zh/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_cube/maixcube_2020-06-13_06-31-29.png
+docs/soft/maixpy/zh/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_dock/Dan_Dock.png
+docs/soft/maixpy/zh/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_duino/maixduino_0.png
+docs/soft/maixpy/zh/develop_kit_board/develop_kit_board.md ../../assets/dk_board/maix_go/Go.jpg
+docs/soft/maixpy3/zh/api/maix/gpio.md ./asserts/v831_gpio.png
+docs/soft/maixpy3/zh/api/maix/gpio.md ./asserts/v831_pin_maps.png
+docs/soft/maixpy3/zh/usage/hardware/back/GPIO_1.md ./../asserts/M2Dock_pin.png
+docs/soft/maixpy3/zh/usage/hardware/back/I2C_1.md ./../asserts/M2Dock_pin.png
+docs/soft/maixpy3/zh/usage/hardware/back/PWM_1.md ./../asserts/pwm.gif
+docs/soft/maixpy3/zh/usage/hardware/back/PWM_1.md ./../asserts/pwm.jpg
+news/MaixPy3/v831_usage/v831_usage.md /news/MaixPy3/v831_usage/assest/usb.jpg
+news/others/831_ssh/831_ssh.md ./assets/succeed_login.png
+pages/index/zh/threshold.html demo_color.jpg