From 7007587a5e27845d34f5263856e5a71d7c695519 Mon Sep 17 00:00:00 2001 From: halka Date: Sun, 2 Aug 2026 13:22:51 +0900 Subject: [PATCH 1/2] Add browser firmware installer --- .github/workflows/arduino.yml | 49 +++++++ README.md | 8 ++ scripts/prepare_web_flasher.sh | 55 +++++++ web/.nojekyll | 1 + web/index.html | 93 ++++++++++++ web/manifest.json.in | 18 +++ web/styles.css | 254 +++++++++++++++++++++++++++++++++ 7 files changed, 478 insertions(+) create mode 100755 scripts/prepare_web_flasher.sh create mode 100644 web/.nojekyll create mode 100644 web/index.html create mode 100644 web/manifest.json.in create mode 100644 web/styles.css diff --git a/.github/workflows/arduino.yml b/.github/workflows/arduino.yml index 174cbc9..5b145ad 100644 --- a/.github/workflows/arduino.yml +++ b/.github/workflows/arduino.yml @@ -56,13 +56,29 @@ jobs: --warnings all \ src/bb-link + - name: Prepare Web Flasher + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + run: ./scripts/prepare_web_flasher.sh build/atomlite build/web-flasher + + - name: Upload Web Flasher + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + uses: actions/upload-artifact@v4 + with: + name: web-flasher + path: build/web-flasher + include-hidden-files: true + if-no-files-found: error + - name: Prepare release artifacts if: startsWith(github.ref, 'refs/tags/v') run: | VERSION="${GITHUB_REF_NAME#v}" BINARY="bb-link-atomlite-${VERSION}.bin" + FACTORY_BINARY="bb-link-atomlite-${VERSION}-factory.bin" cp build/atomlite/bb-link.ino.bin "build/atomlite/${BINARY}" + cp build/atomlite/bb-link.ino.merged.bin "build/atomlite/${FACTORY_BINARY}" sha256sum "build/atomlite/${BINARY}" | cut -d ' ' -f 1 > "build/atomlite/${BINARY}.sha256.txt" + sha256sum "build/atomlite/${FACTORY_BINARY}" | cut -d ' ' -f 1 > "build/atomlite/${FACTORY_BINARY}.sha256.txt" mkdir -p build/ota/atomlite SHA256="$(cat "build/atomlite/${BINARY}.sha256.txt")" printf '{\n "version": "%s",\n "url": "https://github.com/%s/releases/download/v%s/%s",\n "sha256": "%s"\n}\n' \ @@ -87,6 +103,39 @@ jobs: path: build/ota/atomlite/beta.json if-no-files-found: error + deploy-web-flasher: + if: github.event_name == 'push' && github.ref == 'refs/heads/master' + needs: build + runs-on: ubuntu-latest + permissions: + contents: read + pages: write + id-token: write + environment: + name: github-pages + url: ${{ steps.deployment.outputs.page_url }} + concurrency: + group: github-pages + cancel-in-progress: false + steps: + - name: Download Web Flasher + uses: actions/download-artifact@v4 + with: + name: web-flasher + path: web-flasher + + - name: Configure GitHub Pages + uses: actions/configure-pages@v5 + + - name: Upload GitHub Pages artifact + uses: actions/upload-pages-artifact@v4 + with: + path: web-flasher + + - name: Deploy GitHub Pages + id: deployment + uses: actions/deploy-pages@v4 + release: if: startsWith(github.ref, 'refs/tags/v') needs: build diff --git a/README.md b/README.md index d015d37..3fd5058 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,14 @@ This repository is optimized for ATOM-Lite. [![ATOM LITE](https://raw.githubusercontent.com/halka/bb-link/refs/heads/master/assets/atomlite.webp)](https://docs.m5stack.com/en/core/ATOM%20Lite) +## Install from a Browser + +Open the [B.B. Link ATOM Lite Web Flasher](https://halka.github.io/bb-link/) in a desktop version of Google Chrome or Microsoft Edge, connect the ATOM Lite with a data-capable USB cable, and select **Install B.B. Link**. No Arduino toolchain is required. + +The browser installer writes a complete ESP32 image and performs a factory installation. Saved radio, pairing, and adapter settings are erased. Web Serial is not available to this installer on iPhone or iPad. The page is served over HTTPS by GitHub Pages and uses the upstream [ESP Web Tools](https://esphome.github.io/esp-web-tools/) installer. + +Each successful build of `master` deploys the matching firmware to the Web Flasher. Tagged releases also include a `bb-link-atomlite--factory.bin` complete image for recovery or external flashing. The repository's GitHub Pages source must be set to **GitHub Actions** once before the first deployment. + ## Hardware The adapter is based on the ESP32 microcontroller, which provides support for both Bluetooth Classic and Bluetooth Low Energy (BLE). diff --git a/scripts/prepare_web_flasher.sh b/scripts/prepare_web_flasher.sh new file mode 100755 index 0000000..af4110a --- /dev/null +++ b/scripts/prepare_web_flasher.sh @@ -0,0 +1,55 @@ +#!/usr/bin/env bash + +set -euo pipefail + +if [[ $# -ne 2 ]]; then + echo "Usage: $0 BUILD_DIRECTORY OUTPUT_DIRECTORY" >&2 + exit 64 +fi + +build_dir="$1" +output_dir="$2" +project_name="bb-link.ino" +merged_binary="${build_dir}/${project_name}.merged.bin" + +if [[ ! -f "${merged_binary}" ]]; then + echo "Merged firmware not found: ${merged_binary}" >&2 + exit 66 +fi + +read_define() { + local name="$1" + local value + value="$(awk -v name="${name}" '$1 == "#define" && $2 == name { print $3 }' src/bb-link/Adapter.h)" + if [[ ! "${value}" =~ ^[0-9]+$ ]]; then + echo "Unable to read numeric ${name} from src/bb-link/Adapter.h" >&2 + exit 65 + fi + printf '%s' "${value}" +} + +version="$(read_define FIRMWARE_VERSION_MAJOR).$(read_define FIRMWARE_VERSION_MINOR).$(read_define FIRMWARE_VERSION_PATCH)" +firmware_file="bb-link-atomlite-${version}-factory.bin" + +mkdir -p "${output_dir}/firmware" +cp web/index.html "${output_dir}/index.html" +cp web/styles.css "${output_dir}/styles.css" +cp web/.nojekyll "${output_dir}/.nojekyll" +cp assets/atomlite.webp "${output_dir}/atomlite.webp" +cp "${merged_binary}" "${output_dir}/firmware/${firmware_file}" + +sed \ + -e "s/@FIRMWARE_VERSION@/${version}/g" \ + -e "s/@FIRMWARE_FILE@/${firmware_file}/g" \ + web/manifest.json.in > "${output_dir}/manifest.json" + +sed \ + -e "s/@FIRMWARE_VERSION@/${version}/g" \ + "${output_dir}/index.html" > "${output_dir}/index.html.tmp" +mv "${output_dir}/index.html.tmp" "${output_dir}/index.html" + +sha256sum "${output_dir}/firmware/${firmware_file}" \ + | cut -d ' ' -f 1 \ + > "${output_dir}/firmware/${firmware_file}.sha256.txt" + +echo "Prepared B.B. Link ATOM Lite Web Flasher v${version} in ${output_dir}" diff --git a/web/.nojekyll b/web/.nojekyll new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/web/.nojekyll @@ -0,0 +1 @@ + diff --git a/web/index.html b/web/index.html new file mode 100644 index 0000000..aed8898 --- /dev/null +++ b/web/index.html @@ -0,0 +1,93 @@ + + + + + + + + + B.B. Link ATOM Lite Web Flasher + + + + +
+
+
B.B. Link
+

ATOM Lite
Web Flasher

+

+ アプリのインストールは不要です。ATOM LiteをUSBで接続し、 + ChromeまたはEdgeからファームウェアを書き込めます。 +

+
+ +
+ M5Stack ATOM Lite +
+
Firmware v@FIRMWARE_VERSION@
+

B.B. Link for ATOM Lite

+

+ このインストールではフラッシュ全体を書き換えます。保存済みの無線機、 + ペアリング情報、設定は消去されます。 +

+ + +

+ Web Serialに対応したデスクトップ版ChromeまたはEdgeで開いてください。 + iPhone/iPadのブラウザには対応していません。 +

+

+ Web Serialを利用するため、HTTPSで公開されたページから開いてください。 +

+
+

+ シリアルポートへのアクセスは、ブラウザの確認画面で選択した端末に限られます。 +

+
+
+ +
+

書き込み手順

+
    +
  1. + 1 +
    USBで接続データ通信対応ケーブルでATOM LiteをPCへ接続します。
    +
  2. +
  3. + 2 +
    書き込みを開始上のボタンを押し、ATOM Liteのシリアルポートを選択します。
    +
  4. +
  5. + 3 +
    完了まで待機USBケーブルを抜かず、完了表示後に端末を再起動します。
    +
  6. +
+
+ + + + +
+ + diff --git a/web/manifest.json.in b/web/manifest.json.in new file mode 100644 index 0000000..805170e --- /dev/null +++ b/web/manifest.json.in @@ -0,0 +1,18 @@ +{ + "name": "B.B. Link ATOM Lite", + "version": "@FIRMWARE_VERSION@", + "new_install_prompt_erase": false, + "new_install_improv_wait_time": 0, + "builds": [ + { + "chipFamily": "ESP32", + "improv": false, + "parts": [ + { + "path": "firmware/@FIRMWARE_FILE@", + "offset": 0 + } + ] + } + ] +} diff --git a/web/styles.css b/web/styles.css new file mode 100644 index 0000000..6964048 --- /dev/null +++ b/web/styles.css @@ -0,0 +1,254 @@ +:root { + font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, + "Segoe UI", "Noto Sans JP", sans-serif; + color: #f7f9fc; + background: #09111f; + font-synthesis: none; + --accent: #66e4ca; + --accent-strong: #23c8a8; + --panel: rgba(18, 31, 51, 0.86); + --line: rgba(153, 181, 211, 0.2); + --muted: #aebdd0; + --esp-tools-button-color: var(--accent); + --esp-tools-button-text-color: #061611; + --esp-tools-button-border-radius: 999px; +} + +* { + box-sizing: border-box; +} + +body { + min-width: 320px; + min-height: 100vh; + margin: 0; + background: + radial-gradient(circle at 20% 0%, rgba(35, 200, 168, 0.18), transparent 34rem), + radial-gradient(circle at 100% 35%, rgba(39, 113, 217, 0.17), transparent 30rem), + #09111f; +} + +main { + width: min(100% - 32px, 1040px); + margin: 0 auto; + padding: 72px 0 40px; +} + +.hero { + max-width: 760px; + margin-bottom: 48px; +} + +.eyebrow, +.version { + color: var(--accent); + font-size: 0.78rem; + font-weight: 800; + letter-spacing: 0.16em; + text-transform: uppercase; +} + +h1, +h2, +p { + margin-top: 0; +} + +h1 { + margin-bottom: 24px; + font-size: clamp(3rem, 9vw, 6.8rem); + line-height: 0.92; + letter-spacing: -0.065em; +} + +h2 { + margin-bottom: 16px; + font-size: clamp(1.3rem, 3vw, 2rem); + letter-spacing: -0.03em; +} + +.lede { + max-width: 680px; + color: #d4deea; + font-size: clamp(1rem, 2vw, 1.22rem); + line-height: 1.8; +} + +.device-card { + display: grid; + grid-template-columns: minmax(240px, 0.82fr) minmax(300px, 1.18fr); + gap: 48px; + align-items: center; + padding: clamp(24px, 5vw, 52px); + border: 1px solid var(--line); + border-radius: 28px; + background: var(--panel); + box-shadow: 0 28px 80px rgba(0, 0, 0, 0.24); + backdrop-filter: blur(18px); +} + +.device-card img { + display: block; + width: 100%; + height: auto; + filter: drop-shadow(0 22px 28px rgba(0, 0, 0, 0.34)); +} + +.device-copy > p { + color: var(--muted); + line-height: 1.75; +} + +.version { + margin-bottom: 12px; +} + +esp-web-install-button { + display: block; + margin-top: 26px; +} + +esp-web-install-button button { + width: 100%; + padding: 16px 24px; + border: 0; + border-radius: 999px; + color: #061611; + background: var(--accent); + font: inherit; + font-weight: 800; + cursor: pointer; + transition: background 160ms ease, transform 160ms ease; +} + +esp-web-install-button button:hover { + background: #91f0dd; + transform: translateY(-1px); +} + +esp-web-install-button button:focus-visible, +a:focus-visible { + outline: 3px solid #ffffff; + outline-offset: 4px; +} + +.privacy, +.compatibility-error { + margin: 12px 0 0; + font-size: 0.82rem; + line-height: 1.6; +} + +.compatibility-error { + padding: 12px 14px; + border: 1px solid rgba(255, 151, 151, 0.38); + border-radius: 12px; + color: #ffd0d0; + background: rgba(105, 22, 35, 0.34); +} + +.instructions { + padding: 64px 0 32px; +} + +.instructions ol { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 18px; + margin: 0; + padding: 0; + list-style: none; +} + +.instructions li { + min-height: 180px; + padding: 24px; + border: 1px solid var(--line); + border-radius: 20px; + color: var(--muted); + background: rgba(15, 27, 45, 0.68); + line-height: 1.65; +} + +.instructions li > span { + display: grid; + width: 32px; + height: 32px; + margin-bottom: 24px; + border-radius: 50%; + place-items: center; + color: #061611; + background: var(--accent); + font-weight: 900; +} + +.instructions strong { + display: block; + margin-bottom: 4px; + color: #f7f9fc; +} + +.note { + padding: 28px 30px; + border-left: 3px solid var(--accent-strong); + border-radius: 0 16px 16px 0; + background: rgba(22, 39, 61, 0.72); +} + +.note h2 { + margin-bottom: 8px; + font-size: 1.1rem; +} + +.note p { + margin-bottom: 0; + color: var(--muted); + line-height: 1.7; +} + +footer { + display: flex; + gap: 12px; + justify-content: center; + padding-top: 56px; + color: #72849b; + font-size: 0.88rem; +} + +a { + color: #b9c9dc; + text-underline-offset: 4px; +} + +@media (max-width: 720px) { + main { + padding-top: 44px; + } + + .device-card { + grid-template-columns: 1fr; + gap: 24px; + } + + .device-card img { + width: min(80%, 300px); + margin: 0 auto; + } + + .instructions ol { + grid-template-columns: 1fr; + } + + .instructions li { + min-height: 0; + } +} + +@media (prefers-reduced-motion: reduce) { + *, + *::before, + *::after { + scroll-behavior: auto !important; + transition: none !important; + } +} From 29df72375ef18dca05c8994e880501398d169133 Mon Sep 17 00:00:00 2001 From: halka Date: Sun, 2 Aug 2026 13:33:02 +0900 Subject: [PATCH 2/2] Remove browser firmware installer --- .github/workflows/arduino.yml | 49 ------- README.md | 8 -- scripts/prepare_web_flasher.sh | 55 ------- web/.nojekyll | 1 - web/index.html | 93 ------------ web/manifest.json.in | 18 --- web/styles.css | 254 --------------------------------- 7 files changed, 478 deletions(-) delete mode 100755 scripts/prepare_web_flasher.sh delete mode 100644 web/.nojekyll delete mode 100644 web/index.html delete mode 100644 web/manifest.json.in delete mode 100644 web/styles.css diff --git a/.github/workflows/arduino.yml b/.github/workflows/arduino.yml index 5b145ad..174cbc9 100644 --- a/.github/workflows/arduino.yml +++ b/.github/workflows/arduino.yml @@ -56,29 +56,13 @@ jobs: --warnings all \ src/bb-link - - name: Prepare Web Flasher - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - run: ./scripts/prepare_web_flasher.sh build/atomlite build/web-flasher - - - name: Upload Web Flasher - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - uses: actions/upload-artifact@v4 - with: - name: web-flasher - path: build/web-flasher - include-hidden-files: true - if-no-files-found: error - - name: Prepare release artifacts if: startsWith(github.ref, 'refs/tags/v') run: | VERSION="${GITHUB_REF_NAME#v}" BINARY="bb-link-atomlite-${VERSION}.bin" - FACTORY_BINARY="bb-link-atomlite-${VERSION}-factory.bin" cp build/atomlite/bb-link.ino.bin "build/atomlite/${BINARY}" - cp build/atomlite/bb-link.ino.merged.bin "build/atomlite/${FACTORY_BINARY}" sha256sum "build/atomlite/${BINARY}" | cut -d ' ' -f 1 > "build/atomlite/${BINARY}.sha256.txt" - sha256sum "build/atomlite/${FACTORY_BINARY}" | cut -d ' ' -f 1 > "build/atomlite/${FACTORY_BINARY}.sha256.txt" mkdir -p build/ota/atomlite SHA256="$(cat "build/atomlite/${BINARY}.sha256.txt")" printf '{\n "version": "%s",\n "url": "https://github.com/%s/releases/download/v%s/%s",\n "sha256": "%s"\n}\n' \ @@ -103,39 +87,6 @@ jobs: path: build/ota/atomlite/beta.json if-no-files-found: error - deploy-web-flasher: - if: github.event_name == 'push' && github.ref == 'refs/heads/master' - needs: build - runs-on: ubuntu-latest - permissions: - contents: read - pages: write - id-token: write - environment: - name: github-pages - url: ${{ steps.deployment.outputs.page_url }} - concurrency: - group: github-pages - cancel-in-progress: false - steps: - - name: Download Web Flasher - uses: actions/download-artifact@v4 - with: - name: web-flasher - path: web-flasher - - - name: Configure GitHub Pages - uses: actions/configure-pages@v5 - - - name: Upload GitHub Pages artifact - uses: actions/upload-pages-artifact@v4 - with: - path: web-flasher - - - name: Deploy GitHub Pages - id: deployment - uses: actions/deploy-pages@v4 - release: if: startsWith(github.ref, 'refs/tags/v') needs: build diff --git a/README.md b/README.md index 3fd5058..d015d37 100644 --- a/README.md +++ b/README.md @@ -14,14 +14,6 @@ This repository is optimized for ATOM-Lite. [![ATOM LITE](https://raw.githubusercontent.com/halka/bb-link/refs/heads/master/assets/atomlite.webp)](https://docs.m5stack.com/en/core/ATOM%20Lite) -## Install from a Browser - -Open the [B.B. Link ATOM Lite Web Flasher](https://halka.github.io/bb-link/) in a desktop version of Google Chrome or Microsoft Edge, connect the ATOM Lite with a data-capable USB cable, and select **Install B.B. Link**. No Arduino toolchain is required. - -The browser installer writes a complete ESP32 image and performs a factory installation. Saved radio, pairing, and adapter settings are erased. Web Serial is not available to this installer on iPhone or iPad. The page is served over HTTPS by GitHub Pages and uses the upstream [ESP Web Tools](https://esphome.github.io/esp-web-tools/) installer. - -Each successful build of `master` deploys the matching firmware to the Web Flasher. Tagged releases also include a `bb-link-atomlite--factory.bin` complete image for recovery or external flashing. The repository's GitHub Pages source must be set to **GitHub Actions** once before the first deployment. - ## Hardware The adapter is based on the ESP32 microcontroller, which provides support for both Bluetooth Classic and Bluetooth Low Energy (BLE). diff --git a/scripts/prepare_web_flasher.sh b/scripts/prepare_web_flasher.sh deleted file mode 100755 index af4110a..0000000 --- a/scripts/prepare_web_flasher.sh +++ /dev/null @@ -1,55 +0,0 @@ -#!/usr/bin/env bash - -set -euo pipefail - -if [[ $# -ne 2 ]]; then - echo "Usage: $0 BUILD_DIRECTORY OUTPUT_DIRECTORY" >&2 - exit 64 -fi - -build_dir="$1" -output_dir="$2" -project_name="bb-link.ino" -merged_binary="${build_dir}/${project_name}.merged.bin" - -if [[ ! -f "${merged_binary}" ]]; then - echo "Merged firmware not found: ${merged_binary}" >&2 - exit 66 -fi - -read_define() { - local name="$1" - local value - value="$(awk -v name="${name}" '$1 == "#define" && $2 == name { print $3 }' src/bb-link/Adapter.h)" - if [[ ! "${value}" =~ ^[0-9]+$ ]]; then - echo "Unable to read numeric ${name} from src/bb-link/Adapter.h" >&2 - exit 65 - fi - printf '%s' "${value}" -} - -version="$(read_define FIRMWARE_VERSION_MAJOR).$(read_define FIRMWARE_VERSION_MINOR).$(read_define FIRMWARE_VERSION_PATCH)" -firmware_file="bb-link-atomlite-${version}-factory.bin" - -mkdir -p "${output_dir}/firmware" -cp web/index.html "${output_dir}/index.html" -cp web/styles.css "${output_dir}/styles.css" -cp web/.nojekyll "${output_dir}/.nojekyll" -cp assets/atomlite.webp "${output_dir}/atomlite.webp" -cp "${merged_binary}" "${output_dir}/firmware/${firmware_file}" - -sed \ - -e "s/@FIRMWARE_VERSION@/${version}/g" \ - -e "s/@FIRMWARE_FILE@/${firmware_file}/g" \ - web/manifest.json.in > "${output_dir}/manifest.json" - -sed \ - -e "s/@FIRMWARE_VERSION@/${version}/g" \ - "${output_dir}/index.html" > "${output_dir}/index.html.tmp" -mv "${output_dir}/index.html.tmp" "${output_dir}/index.html" - -sha256sum "${output_dir}/firmware/${firmware_file}" \ - | cut -d ' ' -f 1 \ - > "${output_dir}/firmware/${firmware_file}.sha256.txt" - -echo "Prepared B.B. Link ATOM Lite Web Flasher v${version} in ${output_dir}" diff --git a/web/.nojekyll b/web/.nojekyll deleted file mode 100644 index 8b13789..0000000 --- a/web/.nojekyll +++ /dev/null @@ -1 +0,0 @@ - diff --git a/web/index.html b/web/index.html deleted file mode 100644 index aed8898..0000000 --- a/web/index.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - - - B.B. Link ATOM Lite Web Flasher - - - - -
-
-
B.B. Link
-

ATOM Lite
Web Flasher

-

- アプリのインストールは不要です。ATOM LiteをUSBで接続し、 - ChromeまたはEdgeからファームウェアを書き込めます。 -

-
- -
- M5Stack ATOM Lite -
-
Firmware v@FIRMWARE_VERSION@
-

B.B. Link for ATOM Lite

-

- このインストールではフラッシュ全体を書き換えます。保存済みの無線機、 - ペアリング情報、設定は消去されます。 -

- - -

- Web Serialに対応したデスクトップ版ChromeまたはEdgeで開いてください。 - iPhone/iPadのブラウザには対応していません。 -

-

- Web Serialを利用するため、HTTPSで公開されたページから開いてください。 -

-
-

- シリアルポートへのアクセスは、ブラウザの確認画面で選択した端末に限られます。 -

-
-
- -
-

書き込み手順

-
    -
  1. - 1 -
    USBで接続データ通信対応ケーブルでATOM LiteをPCへ接続します。
    -
  2. -
  3. - 2 -
    書き込みを開始上のボタンを押し、ATOM Liteのシリアルポートを選択します。
    -
  4. -
  5. - 3 -
    完了まで待機USBケーブルを抜かず、完了表示後に端末を再起動します。
    -
  6. -
-
- - - - -
- - diff --git a/web/manifest.json.in b/web/manifest.json.in deleted file mode 100644 index 805170e..0000000 --- a/web/manifest.json.in +++ /dev/null @@ -1,18 +0,0 @@ -{ - "name": "B.B. Link ATOM Lite", - "version": "@FIRMWARE_VERSION@", - "new_install_prompt_erase": false, - "new_install_improv_wait_time": 0, - "builds": [ - { - "chipFamily": "ESP32", - "improv": false, - "parts": [ - { - "path": "firmware/@FIRMWARE_FILE@", - "offset": 0 - } - ] - } - ] -} diff --git a/web/styles.css b/web/styles.css deleted file mode 100644 index 6964048..0000000 --- a/web/styles.css +++ /dev/null @@ -1,254 +0,0 @@ -:root { - font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, - "Segoe UI", "Noto Sans JP", sans-serif; - color: #f7f9fc; - background: #09111f; - font-synthesis: none; - --accent: #66e4ca; - --accent-strong: #23c8a8; - --panel: rgba(18, 31, 51, 0.86); - --line: rgba(153, 181, 211, 0.2); - --muted: #aebdd0; - --esp-tools-button-color: var(--accent); - --esp-tools-button-text-color: #061611; - --esp-tools-button-border-radius: 999px; -} - -* { - box-sizing: border-box; -} - -body { - min-width: 320px; - min-height: 100vh; - margin: 0; - background: - radial-gradient(circle at 20% 0%, rgba(35, 200, 168, 0.18), transparent 34rem), - radial-gradient(circle at 100% 35%, rgba(39, 113, 217, 0.17), transparent 30rem), - #09111f; -} - -main { - width: min(100% - 32px, 1040px); - margin: 0 auto; - padding: 72px 0 40px; -} - -.hero { - max-width: 760px; - margin-bottom: 48px; -} - -.eyebrow, -.version { - color: var(--accent); - font-size: 0.78rem; - font-weight: 800; - letter-spacing: 0.16em; - text-transform: uppercase; -} - -h1, -h2, -p { - margin-top: 0; -} - -h1 { - margin-bottom: 24px; - font-size: clamp(3rem, 9vw, 6.8rem); - line-height: 0.92; - letter-spacing: -0.065em; -} - -h2 { - margin-bottom: 16px; - font-size: clamp(1.3rem, 3vw, 2rem); - letter-spacing: -0.03em; -} - -.lede { - max-width: 680px; - color: #d4deea; - font-size: clamp(1rem, 2vw, 1.22rem); - line-height: 1.8; -} - -.device-card { - display: grid; - grid-template-columns: minmax(240px, 0.82fr) minmax(300px, 1.18fr); - gap: 48px; - align-items: center; - padding: clamp(24px, 5vw, 52px); - border: 1px solid var(--line); - border-radius: 28px; - background: var(--panel); - box-shadow: 0 28px 80px rgba(0, 0, 0, 0.24); - backdrop-filter: blur(18px); -} - -.device-card img { - display: block; - width: 100%; - height: auto; - filter: drop-shadow(0 22px 28px rgba(0, 0, 0, 0.34)); -} - -.device-copy > p { - color: var(--muted); - line-height: 1.75; -} - -.version { - margin-bottom: 12px; -} - -esp-web-install-button { - display: block; - margin-top: 26px; -} - -esp-web-install-button button { - width: 100%; - padding: 16px 24px; - border: 0; - border-radius: 999px; - color: #061611; - background: var(--accent); - font: inherit; - font-weight: 800; - cursor: pointer; - transition: background 160ms ease, transform 160ms ease; -} - -esp-web-install-button button:hover { - background: #91f0dd; - transform: translateY(-1px); -} - -esp-web-install-button button:focus-visible, -a:focus-visible { - outline: 3px solid #ffffff; - outline-offset: 4px; -} - -.privacy, -.compatibility-error { - margin: 12px 0 0; - font-size: 0.82rem; - line-height: 1.6; -} - -.compatibility-error { - padding: 12px 14px; - border: 1px solid rgba(255, 151, 151, 0.38); - border-radius: 12px; - color: #ffd0d0; - background: rgba(105, 22, 35, 0.34); -} - -.instructions { - padding: 64px 0 32px; -} - -.instructions ol { - display: grid; - grid-template-columns: repeat(3, 1fr); - gap: 18px; - margin: 0; - padding: 0; - list-style: none; -} - -.instructions li { - min-height: 180px; - padding: 24px; - border: 1px solid var(--line); - border-radius: 20px; - color: var(--muted); - background: rgba(15, 27, 45, 0.68); - line-height: 1.65; -} - -.instructions li > span { - display: grid; - width: 32px; - height: 32px; - margin-bottom: 24px; - border-radius: 50%; - place-items: center; - color: #061611; - background: var(--accent); - font-weight: 900; -} - -.instructions strong { - display: block; - margin-bottom: 4px; - color: #f7f9fc; -} - -.note { - padding: 28px 30px; - border-left: 3px solid var(--accent-strong); - border-radius: 0 16px 16px 0; - background: rgba(22, 39, 61, 0.72); -} - -.note h2 { - margin-bottom: 8px; - font-size: 1.1rem; -} - -.note p { - margin-bottom: 0; - color: var(--muted); - line-height: 1.7; -} - -footer { - display: flex; - gap: 12px; - justify-content: center; - padding-top: 56px; - color: #72849b; - font-size: 0.88rem; -} - -a { - color: #b9c9dc; - text-underline-offset: 4px; -} - -@media (max-width: 720px) { - main { - padding-top: 44px; - } - - .device-card { - grid-template-columns: 1fr; - gap: 24px; - } - - .device-card img { - width: min(80%, 300px); - margin: 0 auto; - } - - .instructions ol { - grid-template-columns: 1fr; - } - - .instructions li { - min-height: 0; - } -} - -@media (prefers-reduced-motion: reduce) { - *, - *::before, - *::after { - scroll-behavior: auto !important; - transition: none !important; - } -}