From b77377ec3dd1dfc0111e5e8bb0468436de3918ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sat, 25 Apr 2026 03:19:16 +0800 Subject: [PATCH 01/11] opt.: new website Fixes #1137 --- Makefile | 26 +- scripts/release/package-dmg-from-xcarchive.sh | 2 +- scripts/release/release-macos-dmg.sh | 51 +- scripts/release/sync-homebrew-cask.sh | 112 +++ website/.gitignore | 26 + website/.typesafe-i18n.json | 7 + website/.vscode/extensions.json | 3 + website/bun.lock | 242 ++++++ website/components.json | 20 + website/index.html | 17 + website/jsconfig.json | 38 + website/package.json | 31 + website/public/favicon.svg | 13 + website/public/icons.svg | 24 + website/src/App.svelte | 306 ++++++++ website/src/app.css | 719 ++++++++++++++++++ website/src/assets/serverbox/app_icon.png | Bin 0 -> 15362 bytes website/src/i18n/en/index.ts | 152 ++++ website/src/i18n/formatters.ts | 11 + website/src/i18n/i18n-svelte.ts | 12 + website/src/i18n/i18n-types.ts | 690 +++++++++++++++++ website/src/i18n/i18n-util.async.ts | 27 + website/src/i18n/i18n-util.sync.ts | 26 + website/src/i18n/i18n-util.ts | 38 + website/src/i18n/zh-CN/index.ts | 151 ++++ website/src/lib/Counter.svelte | 5 + website/src/lib/i18n.js | 38 + website/src/lib/utils.js | 8 + website/src/main.js | 9 + website/svelte.config.js | 2 + website/vite.config.js | 17 + 31 files changed, 2817 insertions(+), 6 deletions(-) create mode 100755 scripts/release/sync-homebrew-cask.sh create mode 100644 website/.gitignore create mode 100644 website/.typesafe-i18n.json create mode 100644 website/.vscode/extensions.json create mode 100644 website/bun.lock create mode 100644 website/components.json create mode 100644 website/index.html create mode 100644 website/jsconfig.json create mode 100644 website/package.json create mode 100644 website/public/favicon.svg create mode 100644 website/public/icons.svg create mode 100644 website/src/App.svelte create mode 100644 website/src/app.css create mode 100644 website/src/assets/serverbox/app_icon.png create mode 100644 website/src/i18n/en/index.ts create mode 100644 website/src/i18n/formatters.ts create mode 100644 website/src/i18n/i18n-svelte.ts create mode 100644 website/src/i18n/i18n-types.ts create mode 100644 website/src/i18n/i18n-util.async.ts create mode 100644 website/src/i18n/i18n-util.sync.ts create mode 100644 website/src/i18n/i18n-util.ts create mode 100644 website/src/i18n/zh-CN/index.ts create mode 100644 website/src/lib/Counter.svelte create mode 100644 website/src/lib/i18n.js create mode 100644 website/src/lib/utils.js create mode 100644 website/src/main.js create mode 100644 website/svelte.config.js create mode 100644 website/vite.config.js diff --git a/Makefile b/Makefile index fb6a25caa..936f153e2 100644 --- a/Makefile +++ b/Makefile @@ -10,10 +10,12 @@ PLATFORM ?= ENV_FILE ?= XCARCHIVE_PATH ?= APP_PATH ?= +TAP_REPO_PATH ?= .PHONY: help deps pub-get run run-device analyze test test-one coverage \ gen gen-build gen-build-clean gen-l10n build build-android build-ios \ - build-macos build-linux build-windows clean release-macos-dmg package-dmg + build-macos build-linux build-windows clean release-macos-dmg package-dmg \ + sync-homebrew-cask help: @printf '%s\n' \ @@ -48,7 +50,11 @@ help: ' Optional: make release-macos-dmg ENV_FILE=.env.release' \ ' package-dmg Run scripts/release/package-dmg-from-xcarchive.sh' \ ' Example: make package-dmg APP_PATH="/path/Server Box.app"' \ - ' Example: make package-dmg XCARCHIVE_PATH=/path/Runner.xcarchive' + ' Example: make package-dmg XCARCHIVE_PATH=/path/Runner.xcarchive' \ + ' sync-homebrew-cask Generate ~/proj/homebrew-taps/Casks/serverbox.rb from a built DMG' \ + ' Example: make sync-homebrew-cask APP_PATH="/path/Server Box.app"' \ + ' Example: make sync-homebrew-cask XCARCHIVE_PATH=/path/Runner.xcarchive' \ + ' Example: make sync-homebrew-cask DMG_PATH=build/artifacts/ServerBox-1.0.1365.dmg' deps pub-get: $(FLUTTER) pub get @@ -134,3 +140,19 @@ package-dmg: else \ XCARCHIVE_PATH="$(XCARCHIVE_PATH)" bash scripts/release/package-dmg-from-xcarchive.sh; \ fi + +sync-homebrew-cask: + @if [ -z "$(APP_PATH)" ] && [ -z "$(XCARCHIVE_PATH)" ] && [ -z "$(DMG_PATH)" ]; then \ + echo 'APP_PATH, XCARCHIVE_PATH, or DMG_PATH is required.'; \ + echo 'Example: make sync-homebrew-cask APP_PATH="/path/Server Box.app"'; \ + echo 'Example: make sync-homebrew-cask XCARCHIVE_PATH=/path/Runner.xcarchive'; \ + echo 'Example: make sync-homebrew-cask DMG_PATH=build/artifacts/ServerBox-1.0.1365.dmg'; \ + exit 1; \ + fi + @if [ -n "$(APP_PATH)" ]; then \ + APP_PATH="$(APP_PATH)" DMG_PATH="$(DMG_PATH)" TAP_REPO_PATH="$(TAP_REPO_PATH)" bash scripts/release/sync-homebrew-cask.sh; \ + elif [ -n "$(XCARCHIVE_PATH)" ]; then \ + XCARCHIVE_PATH="$(XCARCHIVE_PATH)" DMG_PATH="$(DMG_PATH)" TAP_REPO_PATH="$(TAP_REPO_PATH)" bash scripts/release/sync-homebrew-cask.sh; \ + else \ + DMG_PATH="$(DMG_PATH)" TAP_REPO_PATH="$(TAP_REPO_PATH)" bash scripts/release/sync-homebrew-cask.sh; \ + fi diff --git a/scripts/release/package-dmg-from-xcarchive.sh b/scripts/release/package-dmg-from-xcarchive.sh index f68b0d3a7..c07972a5b 100755 --- a/scripts/release/package-dmg-from-xcarchive.sh +++ b/scripts/release/package-dmg-from-xcarchive.sh @@ -41,7 +41,7 @@ fi APP_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$INFO_PLIST")" APP_BUILD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$INFO_PLIST")" -DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${APP_VERSION}-${APP_BUILD}}" +DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${APP_VERSION}}" DMG_PATH="${DMG_PATH:-$ARTIFACTS_PATH/${DMG_BASENAME}.dmg}" mkdir -p "$ARTIFACTS_PATH" diff --git a/scripts/release/release-macos-dmg.sh b/scripts/release/release-macos-dmg.sh index 20fae1cd1..de0f59a31 100755 --- a/scripts/release/release-macos-dmg.sh +++ b/scripts/release/release-macos-dmg.sh @@ -47,6 +47,39 @@ require_cmd() { fi } +warn_pre_notary_spctl_rejection() { + local app_path="$1" + + echo "Skipping pre-notarization spctl enforcement for $app_path" + echo 'Developer ID signed apps can be rejected by Gatekeeper before notarization with source=Unnotarized Developer ID' +} + +ensure_macos_pods_synced() { + local macos_dir="$REPO_ROOT/macos" + local podfile_lock="$macos_dir/Podfile.lock" + local manifest_lock="$macos_dir/Pods/Manifest.lock" + local pods_project="$macos_dir/Pods/Pods.xcodeproj" + + require_cmd pod + + if [[ ! -f "$manifest_lock" || ! -d "$pods_project" ]]; then + echo 'CocoaPods sandbox is incomplete, running pod install' + ( + cd "$macos_dir" + pod install + ) + return + fi + + if [[ -f "$podfile_lock" ]] && ! cmp -s "$podfile_lock" "$manifest_lock"; then + echo 'CocoaPods sandbox is out of sync, running pod install' + ( + cd "$macos_dir" + pod install + ) + fi +} + normalize_abs_path() { local path="$1" if [[ -z "$path" ]]; then @@ -232,13 +265,14 @@ if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then fi require_file "$WORKSPACE_PATH" +ensure_macos_pods_synced read -r DEFAULT_MARKETING_VERSION DEFAULT_CURRENT_PROJECT_VERSION <<<"$(read_pubspec_versions)" MARKETING_VERSION="${MARKETING_VERSION_OVERRIDE:-$DEFAULT_MARKETING_VERSION}" CURRENT_PROJECT_VERSION="${CURRENT_PROJECT_VERSION_OVERRIDE:-$DEFAULT_CURRENT_PROJECT_VERSION}" RELEASE_TAG="${RELEASE_TAG:-v${MARKETING_VERSION}}" RELEASE_TITLE="${RELEASE_TITLE:-$RELEASE_TAG}" -DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${MARKETING_VERSION}-${CURRENT_PROJECT_VERSION}}" +DMG_BASENAME="${DMG_BASENAME:-${APP_ASSET_NAME}-${MARKETING_VERSION}}" DMG_PATH="${DMG_PATH:-$ARTIFACTS_PATH/${DMG_BASENAME}.dmg}" validate_allowed_path 'BUILD_ROOT' "$BUILD_ROOT" >/dev/null @@ -303,7 +337,7 @@ if [[ ! -d "$APP_PATH" ]]; then fi codesign --verify --deep --strict --verbose=2 "$APP_PATH" -spctl -a -t exec -vv "$APP_PATH" +warn_pre_notary_spctl_rejection "$APP_PATH" APP_PATH="$APP_PATH" \ APP_NAME="$APP_NAME" \ @@ -313,7 +347,7 @@ ARTIFACTS_PATH="$ARTIFACTS_PATH" \ DMG_STAGING_PATH="$DMG_STAGING_PATH" \ DMG_BASENAME="$DMG_BASENAME" \ DMG_PATH="$DMG_PATH" \ -REQUIRE_SPCTL=1 \ +REQUIRE_SPCTL=0 \ bash "$SCRIPT_DIR/package-dmg-from-xcarchive.sh" codesign --force --sign "$SIGNING_IDENTITY" --timestamp "$DMG_PATH" @@ -325,6 +359,7 @@ xcrun notarytool submit "$DMG_PATH" \ xcrun stapler staple "$DMG_PATH" xcrun stapler validate "$DMG_PATH" +spctl -a -t open --context context:primary-signature -vv "$DMG_PATH" if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then if gh release view "$RELEASE_TAG" --repo "$APP_REPO_SLUG" >/dev/null 2>&1; then @@ -343,6 +378,13 @@ if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then --clobber fi +if [[ "${SYNC_HOMEBREW_CASK:-1}" == "1" ]]; then + APP_PATH="$APP_PATH" \ + DMG_PATH="$DMG_PATH" \ + TAP_REPO_PATH="${TAP_REPO_PATH:-$HOME/proj/homebrew-taps}" \ + bash "$SCRIPT_DIR/sync-homebrew-cask.sh" +fi + echo "Release complete" echo "Marketing version: $MARKETING_VERSION" echo "Build number: $CURRENT_PROJECT_VERSION" @@ -352,3 +394,6 @@ echo "DMG: $DMG_PATH" if [[ "$PUBLISH_GITHUB_RELEASE" == "1" ]]; then echo "GitHub release: $APP_REPO_SLUG $RELEASE_TAG" fi +if [[ "${SYNC_HOMEBREW_CASK:-1}" == "1" ]]; then + echo "Homebrew cask: ${TAP_REPO_PATH:-$HOME/proj/homebrew-taps}/Casks/serverbox.rb" +fi diff --git a/scripts/release/sync-homebrew-cask.sh b/scripts/release/sync-homebrew-cask.sh new file mode 100755 index 000000000..14ed6bd7b --- /dev/null +++ b/scripts/release/sync-homebrew-cask.sh @@ -0,0 +1,112 @@ +#!/bin/bash +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)" +APP_NAME="${APP_NAME:-Server Box}" +CASK_NAME="${CASK_NAME:-serverbox}" +CASK_DISPLAY_NAME="${CASK_DISPLAY_NAME:-ServerBox}" +CASK_DESC="${CASK_DESC:-App for monitoring server status with SSH terminal, SFTP, Container management}" +APP_REPO_SLUG="${APP_REPO_SLUG:-lollipopkit/flutter_server_box}" +TAP_REPO_PATH="${TAP_REPO_PATH:-$HOME/proj/homebrew-taps}" +TAP_CASK_PATH="${TAP_CASK_PATH:-}" +EXPLICIT_TAP_CASK_PATH="${TAP_CASK_PATH:-}" +XCARCHIVE_PATH="${1:-${XCARCHIVE_PATH:-}}" + +if [[ -n "$XCARCHIVE_PATH" ]]; then + APP_PATH="${APP_PATH:-$XCARCHIVE_PATH/Products/Applications/${APP_NAME}.app}" +else + APP_PATH="${APP_PATH:-}" +fi + +if [[ -n "$APP_PATH" ]]; then + INFO_PLIST="$APP_PATH/Contents/Info.plist" +else + INFO_PLIST="${INFO_PLIST:-$REPO_ROOT/macos/Runner/Info.plist}" +fi + +if [[ -f "$INFO_PLIST" ]]; then + APP_VERSION="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleShortVersionString' "$INFO_PLIST")" + APP_BUILD="$(/usr/libexec/PlistBuddy -c 'Print :CFBundleVersion' "$INFO_PLIST")" +else + APP_VERSION="" + APP_BUILD="" +fi + +if [[ -n "$APP_VERSION" && "$APP_VERSION" != '$('* ]]; then + DMG_BASENAME="${DMG_BASENAME:-ServerBox-${APP_VERSION}}" +fi + +if [[ -z "${DMG_PATH:-}" ]]; then + if [[ -z "${DMG_BASENAME:-}" ]]; then + echo "DMG_PATH requires DMG_BASENAME when version is unavailable" >&2 + echo "Provide DMG_PATH directly, or provide XCARCHIVE_PATH/APP_PATH so DMG_BASENAME can be resolved." >&2 + exit 1 + fi + DMG_PATH="$REPO_ROOT/build/artifacts/${DMG_BASENAME}.dmg" +fi + +if [[ ! -f "$DMG_PATH" ]]; then + echo "DMG not found: $DMG_PATH" >&2 + echo "Run package-dmg-from-xcarchive.sh first or provide DMG_PATH." >&2 + exit 1 +fi + +if [[ -z "$APP_VERSION" || "$APP_VERSION" == '$('* ]]; then + dmg_filename="$(basename "$DMG_PATH")" + if [[ "$dmg_filename" =~ ^ServerBox-([0-9]+(\.[0-9]+){1,2})\.dmg$ ]]; then + APP_VERSION="${BASH_REMATCH[1]}" + APP_BUILD="${APP_BUILD:-}" + elif [[ "$dmg_filename" =~ ^ServerBox-([0-9]+(\.[0-9]+){1,2})-([0-9]+)\.dmg$ ]]; then + APP_VERSION="${BASH_REMATCH[1]}" + APP_BUILD="${BASH_REMATCH[3]}" + else + echo "unable to determine version from $DMG_PATH" >&2 + echo "Provide XCARCHIVE_PATH, APP_PATH, or a DMG named ServerBox-.dmg." >&2 + exit 1 + fi +fi + +RELEASE_TAG="${RELEASE_TAG:-v${APP_VERSION}}" +DMG_BASENAME="${DMG_BASENAME:-ServerBox-${APP_VERSION}}" + +if [[ -z "$TAP_CASK_PATH" && -n "$TAP_REPO_PATH" ]]; then + TAP_CASK_PATH="$TAP_REPO_PATH/Casks/${CASK_NAME}.rb" +fi + +if [[ -z "$TAP_CASK_PATH" ]]; then + echo "TAP_REPO_PATH or TAP_CASK_PATH is required" >&2 + exit 1 +fi + +if [[ -z "$EXPLICIT_TAP_CASK_PATH" && -n "$TAP_REPO_PATH" && ! -d "$TAP_REPO_PATH" ]]; then + echo "TAP_REPO_PATH does not exist: $TAP_REPO_PATH" >&2 + exit 1 +fi + +SHA256="$(shasum -a 256 "$DMG_PATH" | awk '{print $1}')" + +mkdir -p "$(dirname "$TAP_CASK_PATH")" +cat > "$TAP_CASK_PATH" <=3.0.0", "tailwindcss": "*" }, "optionalPeers": ["tailwind-merge"] }, "sha512-Mi4kHeMTLvKlM98XPnK+7HoBPmf4gygdFmqQPaDivc3DpYS6aIY6KiG/PgThrGvii5YZJqRsPz0aPyhoFzmZgg=="], + + "tailwindcss": ["tailwindcss@4.2.4", "https://registry.npmmirror.com/tailwindcss/-/tailwindcss-4.2.4.tgz", {}, "sha512-HhKppgO81FQof5m6TEnuBWCZGgfRAWbaeOaGT00KOy/Pf/j6oUihdvBpA7ltCeAvZpFhW3j0PTclkxsd4IXYDA=="], + + "tapable": ["tapable@2.3.3", "https://registry.npmmirror.com/tapable/-/tapable-2.3.3.tgz", {}, "sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A=="], + + "tinyglobby": ["tinyglobby@0.2.16", "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.16.tgz", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + + "tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + + "tw-animate-css": ["tw-animate-css@1.4.0", "https://registry.npmmirror.com/tw-animate-css/-/tw-animate-css-1.4.0.tgz", {}, "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ=="], + + "typesafe-i18n": ["typesafe-i18n@5.27.1", "", { "peerDependencies": { "typescript": ">=3.5.1" }, "bin": { "typesafe-i18n": "cli/typesafe-i18n.mjs" } }, "sha512-749uWo2ZXETT//kWjVYPm8QPYR8xLh8G0wLfoAyCAtAmysX67uCaAyLjAjAWojL6fuJpE5B6yIjwvO9orXzUPg=="], + + "typescript": ["typescript@6.0.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw=="], + + "vite": ["vite@8.0.10", "https://registry.npmmirror.com/vite/-/vite-8.0.10.tgz", { "dependencies": { "lightningcss": "^1.32.0", "picomatch": "^4.0.4", "postcss": "^8.5.10", "rolldown": "1.0.0-rc.17", "tinyglobby": "^0.2.16" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", "@vitejs/devtools": "^0.1.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", "sass": "^1.70.0", "sass-embedded": "^1.70.0", "stylus": ">=0.54.8", "sugarss": "^5.0.0", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "@vitejs/devtools", "esbuild", "jiti", "less", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-rZuUu9j6J5uotLDs+cAA4O5H4K1SfPliUlQwqa6YEwSrWDZzP4rhm00oJR5snMewjxF5V/K3D4kctsUTsIU9Mw=="], + + "vitefu": ["vitefu@1.1.3", "https://registry.npmmirror.com/vitefu/-/vitefu-1.1.3.tgz", { "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" }, "optionalPeers": ["vite"] }, "sha512-ub4okH7Z5KLjb6hDyjqrGXqWtWvoYdU3IGm/NorpgHncKoLTCfRIbvlhBm7r0YstIaQRYlp4yEbFqDcKSzXSSg=="], + + "zimmerframe": ["zimmerframe@1.1.4", "https://registry.npmmirror.com/zimmerframe/-/zimmerframe-1.1.4.tgz", {}, "sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/core": ["@emnapi/core@1.10.0", "https://registry.npmmirror.com/@emnapi/core/-/core-1.10.0.tgz", { "dependencies": { "@emnapi/wasi-threads": "1.2.1", "tslib": "^2.4.0" }, "bundled": true }, "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/runtime": ["@emnapi/runtime@1.10.0", "https://registry.npmmirror.com/@emnapi/runtime/-/runtime-1.10.0.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA=="], + + "@tailwindcss/oxide-wasm32-wasi/@emnapi/wasi-threads": ["@emnapi/wasi-threads@1.2.1", "https://registry.npmmirror.com/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w=="], + + "@tailwindcss/oxide-wasm32-wasi/@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.1.4", "https://registry.npmmirror.com/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", { "dependencies": { "@tybys/wasm-util": "^0.10.1" }, "peerDependencies": { "@emnapi/core": "^1.7.1", "@emnapi/runtime": "^1.7.1" }, "bundled": true }, "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow=="], + + "@tailwindcss/oxide-wasm32-wasi/@tybys/wasm-util": ["@tybys/wasm-util@0.10.1", "https://registry.npmmirror.com/@tybys/wasm-util/-/wasm-util-0.10.1.tgz", { "dependencies": { "tslib": "^2.4.0" }, "bundled": true }, "sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg=="], + + "@tailwindcss/oxide-wasm32-wasi/tslib": ["tslib@2.8.1", "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz", { "bundled": true }, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], + } +} diff --git a/website/components.json b/website/components.json new file mode 100644 index 000000000..47fd6335c --- /dev/null +++ b/website/components.json @@ -0,0 +1,20 @@ +{ + "$schema": "https://shadcn-svelte.com/schema.json", + "tailwind": { + "css": "src/app.css", + "baseColor": "taupe" + }, + "aliases": { + "components": "$lib/components", + "utils": "$lib/utils", + "ui": "$lib/components/ui", + "hooks": "$lib/hooks", + "lib": "$lib" + }, + "typescript": false, + "registry": "https://shadcn-svelte.com/registry", + "style": "nova", + "iconLibrary": "lucide", + "menuColor": "default", + "menuAccent": "subtle" +} diff --git a/website/index.html b/website/index.html new file mode 100644 index 000000000..e3ea29aba --- /dev/null +++ b/website/index.html @@ -0,0 +1,17 @@ + + + + + + + + ServerBox — Server status, SSH, and operations in one Flutter app + + +
+ + + diff --git a/website/jsconfig.json b/website/jsconfig.json new file mode 100644 index 000000000..4c32d569c --- /dev/null +++ b/website/jsconfig.json @@ -0,0 +1,38 @@ +{ + "compilerOptions": { + "moduleResolution": "bundler", + "target": "ESNext", + "module": "ESNext", + /** + * svelte-preprocess cannot figure out whether you have + * a value or a type, so tell TypeScript to enforce using + * `import type` instead of `import` for Types. + */ + "verbatimModuleSyntax": true, + "isolatedModules": true, + "resolveJsonModule": true, + /** + * To have warnings / errors of the Svelte compiler at the + * correct position, enable source maps by default. + */ + "sourceMap": true, + "esModuleInterop": true, + "baseUrl": ".", + "paths": { + "$lib": ["src/lib"], + "$lib/*": ["src/lib/*"] + }, + "types": ["vite/client"], + "skipLibCheck": true, + /** + * Typecheck JS in `.svelte` and `.js` files by default. + * Disable this if you'd like to use dynamic types. + */ + "checkJs": true + }, + /** + * Use global.d.ts instead of compilerOptions.types + * to avoid limiting type declarations. + */ + "include": ["src/**/*.d.ts", "src/**/*.js", "src/**/*.svelte"] +} diff --git a/website/package.json b/website/package.json new file mode 100644 index 000000000..582feee13 --- /dev/null +++ b/website/package.json @@ -0,0 +1,31 @@ +{ + "name": "serverbox-website", + "private": true, + "version": "0.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "typesafe-i18n": "typesafe-i18n --no-watch", + "build": "bun run typesafe-i18n && vite build", + "preview": "vite preview" + }, + "devDependencies": { + "@fontsource-variable/figtree": "^5.2.10", + "@lucide/svelte": "^1.11.0", + "@sveltejs/vite-plugin-svelte": "^7.0.0", + "@tailwindcss/vite": "^4.2.4", + "clsx": "^2.1.1", + "shadcn-svelte": "^1.2.7", + "svelte": "^5.55.5", + "tailwind-merge": "^3.5.0", + "tailwind-variants": "^3.2.2", + "tailwindcss": "^4.2.4", + "tw-animate-css": "^1.4.0", + "typescript": "^6.0.3", + "vite": "^8.0.10" + }, + "dependencies": { + "gsap": "^3.15.0", + "typesafe-i18n": "^5.27.1" + } +} diff --git a/website/public/favicon.svg b/website/public/favicon.svg new file mode 100644 index 000000000..69faf4108 --- /dev/null +++ b/website/public/favicon.svg @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/website/public/icons.svg b/website/public/icons.svg new file mode 100644 index 000000000..e9522193d --- /dev/null +++ b/website/public/icons.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/website/src/App.svelte b/website/src/App.svelte new file mode 100644 index 000000000..e4c5a92d0 --- /dev/null +++ b/website/src/App.svelte @@ -0,0 +1,306 @@ + + +{#if locale && isMounted} +
+ + +
+

{$LL.hero.titlePrefix()}
{$LL.hero.titleSuffix()}

+

+ {$LL.hero.subtitle()} +

+ + + +
+ +
+
+

{$LL.features.title()}

+

+ {$LL.features.subtitle()} +

+
+ +
+ {#each features as feature} +
+
{feature.icon}
+

{$LL.features[feature.key].title()}

+

{$LL.features[feature.key].description()}

+
+ {/each} +
+
+ +
+
+

{$LL.capabilities.title()}

+

+ {$LL.capabilities.subtitle()} +

+
+ +
+ {#each capabilities as item} + {item} + {/each} +
+ +
+ {$LL.capabilities.installIosPrompt()} + App Store: apps.apple.com/app/id1586449703 + {$LL.capabilities.installReleasePrompt()} + github.com/lollipopkit/flutter_server_box/releases +
+
+ +
+
+

{$LL.download.title()}

+

+ {$LL.download.subtitle()} +

+
+ +
+ {#each downloadGroups as group} + + {/each} +
+ +

{$LL.download.note()}

+
+ +
+
+

{$LL.testimonials.title()}

+
+ +
+ {#each testimonials as t} +
+

“{$LL.testimonials[t.key].quote()}”

+
+

{t.name}

+

{$LL.testimonials[t.key].role()}

+
+
+ {/each} +
+
+ +
+
+

{$LL.cta.title()}

+

+ {$LL.cta.subtitle()} +

+ +
+
+ + +
+{/if} diff --git a/website/src/app.css b/website/src/app.css new file mode 100644 index 000000000..d708d58fa --- /dev/null +++ b/website/src/app.css @@ -0,0 +1,719 @@ +@import 'https://api.fontshare.com/v2/css?f[]=cabinet-grotesk@400,500,700,800&display=swap'; + +:root { + color-scheme: light; + + --black: #000000; + --near-black: #262626; + --dark-surface: #090909; + --stone: #737373; + --mid-gray: #525252; + --silver: #a3a3a3; + --border-light: #d4d4d4; + --light-gray: #e5e5e5; + --snow: #fafafa; + --white: #ffffff; + --ring-blue: rgba(59, 130, 246, 0.5); + + --radius-container: 12px; + --radius-pill: 9999px; + + --font-display: 'Cabinet Grotesk', 'SF Pro Rounded', system-ui, -apple-system, sans-serif; + --font-body: 'Cabinet Grotesk', ui-sans-serif, system-ui, -apple-system, sans-serif; + --font-mono: ui-monospace, 'SFMono-Regular', 'Menlo', 'Monaco', 'Consolas', monospace; +} + +@media (prefers-color-scheme: dark) { + :root { + color-scheme: dark; + + --black: #f5f5f5; + --near-black: #e5e5e5; + --dark-surface: #0f0f0f; + --stone: #a3a3a3; + --mid-gray: #737373; + --silver: #d4d4d4; + --border-light: #404040; + --light-gray: #262626; + --snow: #171717; + --white: #090909; + --ring-blue: rgba(147, 197, 253, 0.55); + } +} + +*, +*::before, +*::after { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + font-family: var(--font-body); + background: var(--white); + color: var(--black); + font-size: 16px; + line-height: 1.5; + letter-spacing: normal; + overflow-x: hidden; +} + +#app { + width: 100%; + max-width: 100%; +} + +.site-nav { + position: sticky; + top: 1.5rem; + z-index: 40; + display: flex; + align-items: center; + justify-content: space-between; + width: min(1080px, 92vw); + margin: 0 auto; + padding: 0.65rem 1.25rem; + border-radius: var(--radius-pill); + border: 1px solid var(--light-gray); + background: var(--white); +} + +.brand { + text-decoration: none; + color: var(--black); + font-weight: 700; + font-size: 1.05rem; + letter-spacing: -0.02em; + font-family: var(--font-display); +} + +.site-nav nav { + display: flex; + align-items: center; + gap: 1.75rem; +} + +.site-nav nav a { + text-decoration: none; + color: var(--stone); + font-size: 0.9rem; + font-weight: 400; + transition: color 0.2s ease; +} + +.site-nav nav a:hover { + color: var(--black); +} + +.nav-actions { + display: flex; + align-items: center; + gap: 0.6rem; +} + +.language-switcher { + position: relative; + display: inline-flex; + align-items: center; +} + +.language-switcher select { + appearance: none; + min-width: 8.4rem; + border: 1px solid var(--light-gray); + border-radius: var(--radius-pill); + background: var(--white); + color: var(--near-black); + font: 500 0.86rem var(--font-body); + line-height: 1; + padding: 0.55rem 1.9rem 0.55rem 0.9rem; + cursor: pointer; +} + +.language-switcher::after { + content: ''; + position: absolute; + right: 0.85rem; + width: 0.42rem; + height: 0.42rem; + border-right: 1.5px solid var(--stone); + border-bottom: 1.5px solid var(--stone); + pointer-events: none; + transform: translateY(-0.12rem) rotate(45deg); +} + +.language-switcher select:focus-visible, +.nav-cta:focus-visible, +.site-nav a:focus-visible, +.btn:focus-visible { + outline: 2px solid var(--ring-blue); + outline-offset: 2px; +} + +.sr-only { + position: absolute; + width: 1px; + height: 1px; + padding: 0; + margin: -1px; + overflow: hidden; + clip-path: inset(50%); + white-space: nowrap; + border: 0; +} + +.nav-cta { + text-decoration: none; + border-radius: var(--radius-pill); + background: var(--black); + color: var(--white); + padding: 0.55rem 1.25rem; + font-weight: 500; + font-size: 0.9rem; + transition: opacity 0.2s ease; +} + +.nav-cta:hover { + opacity: 0.85; +} + +.page-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(5rem, 12vw, 9rem) 0; +} + +.hero { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 1.5rem; + padding: clamp(6rem, 16vw, 12rem) 0 clamp(5rem, 10vw, 7rem); +} + +.hero h1 { + font-family: var(--font-display); + font-size: clamp(2.5rem, 5.5vw, 3.5rem); + font-weight: 700; + line-height: 1.05; + letter-spacing: -0.03em; + color: var(--black); + max-width: 18ch; +} + +.hero-subtitle { + color: var(--stone); + font-size: clamp(1rem, 1.4vw, 1.15rem); + line-height: 1.6; + max-width: 52ch; +} + +.hero-actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.75rem; + margin-top: 0.5rem; +} + +.screenshot-stack { + position: relative; + width: min(860px, 92vw); + height: clamp(340px, 44vw, 520px); + margin-top: clamp(2rem, 5vw, 4rem); + perspective: 1200px; + outline: none; +} + +.screenshot-stack:focus-visible { + outline: 2px solid var(--ring-blue); + outline-offset: 8px; + border-radius: var(--radius-container); +} + +.screenshot-card { + position: absolute; + left: 50%; + top: 50%; + z-index: var(--z); + width: min(250px, 34vw); + aspect-ratio: 512 / 833; + border-radius: 22px; + border: 1px solid var(--light-gray); + background: var(--snow); + box-shadow: 0 18px 44px rgba(0, 0, 0, 0.12); + object-fit: cover; + transform: + translate3d(calc(-50% + var(--base-x) + var(--move-x)), calc(-50% + var(--base-y) + var(--move-y)), 0) + rotateZ(var(--base-r)) + rotateX(var(--tilt-x)) + rotateY(var(--tilt-y)); + transform-style: preserve-3d; + will-change: transform; + transition: + box-shadow 0.2s ease, + border-color 0.2s ease; +} + +.screenshot-stack:hover .screenshot-card { + border-color: var(--border-light); + box-shadow: 0 22px 54px rgba(0, 0, 0, 0.16); +} + +.btn { + display: inline-flex; + align-items: center; + justify-content: center; + text-decoration: none; + border-radius: var(--radius-pill); + padding: 0.625rem 1.5rem; + font-weight: 500; + font-size: 0.95rem; + font-family: var(--font-body); + border: none; + cursor: pointer; + transition: opacity 0.2s ease; +} + +.btn:hover { + opacity: 0.85; +} + +.btn-primary { + background: var(--black); + color: var(--white); +} + +.btn-secondary { + background: var(--light-gray); + color: var(--near-black); +} + +.section-head { + margin-bottom: 2.5rem; +} + +.section-head h2 { + font-family: var(--font-display); + font-size: clamp(1.75rem, 3.2vw, 2.25rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + color: var(--black); + max-width: 28ch; +} + +.section-head p { + margin-top: 0.75rem; + color: var(--stone); + font-size: 1rem; + line-height: 1.6; + max-width: 55ch; +} + +.feature-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.feature-card { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--white); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 0.6rem; + transition: border-color 0.2s ease; +} + +.feature-card:hover { + border-color: var(--border-light); +} + +.feature-card .icon { + width: 36px; + height: 36px; + border-radius: var(--radius-container); + background: var(--snow); + display: flex; + align-items: center; + justify-content: center; + font-size: 1rem; +} + +.feature-card h3 { + font-family: var(--font-display); + font-size: 1rem; + font-weight: 500; + color: var(--black); +} + +.feature-card p { + color: var(--stone); + font-size: 0.88rem; + line-height: 1.55; +} + +.feature-card.wide { + grid-column: span 2; +} + +.protocol-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(4rem, 8vw, 6rem) 0; +} + +.protocol-badges { + display: flex; + flex-wrap: wrap; + gap: 0.6rem; + margin-top: 1.5rem; +} + +.protocol-badge { + border-radius: var(--radius-pill); + border: 1px solid var(--light-gray); + background: var(--white); + padding: 0.45rem 1rem; + font-size: 0.85rem; + font-weight: 500; + color: var(--near-black); + font-family: var(--font-mono); + transition: background 0.2s ease; +} + +.protocol-badge:hover { + background: var(--snow); +} + +.code-block { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--snow); + display: grid; + gap: 0.3rem; + padding: 1.25rem 1.5rem; + margin-top: 2rem; + font-family: var(--font-mono); + font-size: 0.88rem; + line-height: 1.6; + color: var(--near-black); + overflow-x: auto; +} + +.code-block .prompt { + color: var(--stone); + font-size: 0.78rem; +} + +.code-block .command { + color: var(--black); + font-weight: 500; +} + +.install-note { + margin-top: 0.9rem; + color: var(--stone); + font-size: 0.92rem; + line-height: 1.6; + max-width: 66ch; +} + +.install-note code { + font-family: var(--font-mono); + color: var(--near-black); +} + +.testimonial-grid { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 1rem; +} + +.testimonial-card { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--white); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1rem; +} + +.testimonial-card .quote { + font-size: 0.95rem; + line-height: 1.6; + color: var(--near-black); + font-style: italic; +} + +.testimonial-card .author { + margin-top: auto; +} + +.testimonial-card .name { + font-weight: 500; + font-size: 0.88rem; + color: var(--black); +} + +.testimonial-card .role { + font-size: 0.82rem; + color: var(--stone); +} + +.cta-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(4rem, 10vw, 8rem) 0; +} + +.download-section { + width: min(1080px, 92vw); + margin: 0 auto; + padding: clamp(5rem, 12vw, 9rem) 0; +} + +.download-grid { + display: grid; + grid-template-columns: repeat(2, 1fr); + gap: 1rem; +} + +.download-card { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--white); + padding: 1.5rem; + display: flex; + flex-direction: column; + gap: 1.25rem; + transition: border-color 0.2s ease; +} + +.download-card:hover { + border-color: var(--border-light); +} + +.download-card-head h3 { + font-family: var(--font-display); + font-size: 1.05rem; + font-weight: 500; + color: var(--black); +} + +.download-card-head p { + margin-top: 0.45rem; + color: var(--stone); + font-size: 0.9rem; + line-height: 1.55; +} + +.download-links { + display: grid; + gap: 0.5rem; + margin-top: auto; +} + +.download-links a { + text-decoration: none; + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--snow); + color: var(--near-black); + padding: 0.75rem 0.9rem; + display: flex; + align-items: center; + justify-content: space-between; + gap: 1rem; + transition: + background 0.2s ease, + border-color 0.2s ease; +} + +.download-links a:hover { + background: var(--white); + border-color: var(--border-light); +} + +.download-links span { + font-weight: 500; + font-size: 0.92rem; +} + +.download-links small { + color: var(--stone); + font-family: var(--font-mono); + font-size: 0.75rem; + text-align: right; + white-space: nowrap; +} + +.download-note { + margin-top: 1rem; + color: var(--stone); + font-size: 0.92rem; + line-height: 1.6; + max-width: 66ch; +} + +.cta-block { + border-radius: var(--radius-container); + border: 1px solid var(--light-gray); + background: var(--white); + padding: clamp(2rem, 4vw, 3.5rem); + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + gap: 1rem; +} + +.cta-block h2 { + font-family: var(--font-display); + font-size: clamp(1.5rem, 3vw, 2rem); + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + max-width: 22ch; +} + +.cta-block p { + color: var(--stone); + max-width: 48ch; + line-height: 1.6; +} + +.cta-actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 0.75rem; +} + +.site-footer { + width: min(1080px, 92vw); + margin: 0 auto; + padding: 1.5rem 0 3rem; + border-top: 1px solid var(--light-gray); + display: flex; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + gap: 1rem; + color: var(--stone); + font-size: 0.85rem; +} + +.site-footer a { + text-decoration: none; + color: var(--stone); + transition: color 0.2s ease; +} + +.site-footer a:hover { + color: var(--black); +} + +.footer-links { + display: flex; + gap: 1.5rem; +} + +@media (max-width: 768px) { + .site-nav nav { + display: none; + } + + .language-switcher select { + min-width: 7.2rem; + } + + .feature-grid { + grid-template-columns: 1fr; + } + + .feature-card.wide { + grid-column: span 1; + } + + .testimonial-grid { + grid-template-columns: 1fr; + } + + .download-grid { + grid-template-columns: 1fr; + } + + .hero h1 { + max-width: none; + } +} + +@media (max-width: 480px) { + .site-nav { + top: 0.75rem; + padding: 0.55rem 1rem; + } + + .language-switcher select { + min-width: 4.5rem; + max-width: 4.5rem; + padding-right: 1.45rem; + overflow: hidden; + text-overflow: ellipsis; + } + + .nav-actions { + gap: 0.4rem; + } + + .nav-cta { + padding-inline: 1rem; + } + + .hero { + padding: clamp(4rem, 10vw, 6rem) 0 clamp(3rem, 6vw, 5rem); + } + + .screenshot-stack { + width: min(100%, 92vw); + height: 330px; + margin-top: 1.75rem; + } + + .screenshot-card { + width: min(176px, 42vw); + border-radius: 18px; + } + + .protocol-badges { + gap: 0.4rem; + } + + .download-links a { + align-items: flex-start; + flex-direction: column; + gap: 0.2rem; + } + + .download-links small { + text-align: left; + white-space: normal; + } + + .site-footer { + flex-direction: column; + align-items: flex-start; + } +} diff --git a/website/src/assets/serverbox/app_icon.png b/website/src/assets/serverbox/app_icon.png new file mode 100644 index 0000000000000000000000000000000000000000..2ab34c7a3b4d4400d647594a3a6786cec1331aa7 GIT binary patch literal 15362 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%0AKyOyf0R?n(Qe)MZRm z`#-Kr7Sc67%KEX0@6>|9zy_bpaZvji9jk#bQw_XPB|#F?fVqMRRczndKJ;J_d3H60IPC;T>o7X;IZPYN+UnFJLq4oo}vZ%#<8yX0_h3S7_q zyfaG{y0AF#d1cCK>**^`)NxE36@Gmjq*E@dXj(q-dIFgN@NSA1?fqD|6X?SDYJ2k} z9Uy1*S3$eR>#b*-cX%35<7GW=CE6bpd%>QUsX87jqK3Py?pK7R?m+9+V$hx7(DJj6 zeDaM$FY|6vngz~aVnKofI|q&$n5zhGypV|m+2a?SL6v^4Gq&%^O(RC`O)DuNN+PlH z)R|r}l8XVPLUiXTu%bjV;{zgZkirpmnwb;{Ou;C0*rmnGX!CJ`^HXQ(^Krw>IDp<| z*O@!?vcQ#tm~oixNASm$_y&ST3|Ke7Bs+f}%%o;)=BqT!SNE5h^b0$`itMiA>ar|t z_wmm0;x`R`1Rk-huYiMgF^rtH88<5>;g3nX0VOTHQ#{tIC2C7k7)JtWBFh~%G-(q{ iBxmFQ*L~hDksx7{=^qUc(_r=j0Geugs>OFKLjM;;nh$0G literal 0 HcmV?d00001 diff --git a/website/src/i18n/en/index.ts b/website/src/i18n/en/index.ts new file mode 100644 index 000000000..08faf613e --- /dev/null +++ b/website/src/i18n/en/index.ts @@ -0,0 +1,152 @@ +import type { BaseTranslation } from '../i18n-types.js' + +const en: BaseTranslation = { + meta: { + lang: 'en', + title: 'ServerBox — Server status, SSH, and operations in one Flutter app', + description: + 'ServerBox monitors Linux, Unix, and Windows servers with status charts, SSH terminal, SFTP, Docker, process, systemd, S.M.A.R.T, push, widgets, and watchOS support.', + }, + nav: { + features: 'Features', + capabilities: 'Capabilities', + testimonials: 'Testimonials', + download: 'Download', + languageLabel: 'Language', + }, + hero: { + titlePrefix: 'Server status,', + titleSuffix: 'right in your pocket.', + subtitle: + 'ServerBox brings charts, SSH terminal, SFTP, Docker, process control, systemd, S.M.A.R.T, push alerts, widgets, and watchOS support into one Flutter app.', + primaryAction: 'Download ServerBox', + secondaryAction: 'Explore features', + }, + screenshots: { + label: 'Interactive ServerBox screenshots', + one: 'ServerBox server overview screenshot', + two: 'ServerBox status chart screenshot', + three: 'ServerBox terminal screenshot', + four: 'ServerBox tools screenshot', + }, + features: { + title: 'One compact workspace for everyday server maintenance.', + subtitle: + 'A focused operational surface with no decorative filler — every block maps to a real maintenance workflow.', + charts: { + title: 'Status Charts', + description: + 'Track CPU, memory, sensors, GPU, network, disk, and host health from dense mobile charts.', + }, + workspace: { + title: 'Cross-Platform Workspace', + description: + 'Use ServerBox across iOS, Android, macOS, Linux, and Windows with one familiar Flutter interface.', + }, + terminal: { + title: 'SSH Terminal and SFTP', + description: + 'Open command-line and file sessions directly from a server card, backed by dartssh2 and xterm.dart.', + }, + native: { + title: 'Native Device Hooks', + description: + 'Biometric auth, message push, home widgets, and watchOS support keep server context nearby.', + }, + platforms: { + title: 'Docker, Process, Systemd', + description: + 'Inspect containers, processes, and services without switching away from your monitoring flow.', + }, + }, + capabilities: { + title: 'All the tools. One app.', + subtitle: + 'ServerBox keeps terminal access, file transfer, service checks, hardware health, and device-native alerts in the same workflow.', + installIosPrompt: '# iOS and macOS', + installReleasePrompt: '# Android, Linux, and Windows', + }, + download: { + title: 'Every platform, every official source.', + subtitle: + 'Choose the channel that matches your device and trust model. iOS and macOS use the App Store; Android, Linux, and Windows also have direct package downloads.', + platforms: { + iosMacos: { + title: 'iOS / macOS', + description: + 'Use the App Store build for Apple platforms, including automatic updates through your Apple account.', + }, + android: { + title: 'Android', + description: + 'Install from GitHub Releases, the project CDN, F-Droid, or OpenAPK depending on how you manage Android packages.', + }, + linux: { + title: 'Linux', + description: + 'Download desktop packages from GitHub Releases or the project CDN.', + }, + windows: { + title: 'Windows', + description: + 'Download Windows packages from GitHub Releases or the project CDN.', + }, + }, + sources: { + appStore: { + name: 'App Store', + note: 'Apple platforms', + }, + github: { + name: 'GitHub Releases', + note: 'release builds', + }, + cdn: { + name: 'Project CDN', + note: 'package mirror', + }, + fdroid: { + name: 'F-Droid', + note: 'Android repo', + }, + openapk: { + name: 'OpenAPK', + note: 'Android listing', + }, + }, + note: + 'Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers.', + }, + testimonials: { + title: 'Trusted by people who maintain real machines.', + admin: { + quote: + 'ServerBox keeps the quick server checks I do every day in one place, without forcing me back to a laptop.', + role: 'Server Administrator', + }, + infra: { + quote: + 'The jump from metrics to SSH and SFTP is direct. It removes a lot of small context switches during incidents.', + role: 'Infrastructure Maintainer', + }, + student: { + quote: + 'It is lightweight enough for my personal servers, but still covers Docker, systemd, and health checks.', + role: 'Homelab User', + }, + }, + cta: { + title: 'ServerBox is free and open source under AGPLv3.', + subtitle: + 'Install from the App Store, GitHub Releases, F-Droid, OpenAPK, or the project CDN. Only download packages from sources you trust.', + appStoreAction: 'Open App Store', + githubAction: 'Download from GitHub Releases', + }, + footer: { + features: 'Features', + capabilities: 'Capabilities', + releases: 'Releases', + }, +} + +export default en diff --git a/website/src/i18n/formatters.ts b/website/src/i18n/formatters.ts new file mode 100644 index 000000000..9e0741e47 --- /dev/null +++ b/website/src/i18n/formatters.ts @@ -0,0 +1,11 @@ +import type { FormattersInitializer } from 'typesafe-i18n' +import type { Locales, Formatters } from './i18n-types.js' + +export const initFormatters: FormattersInitializer = (locale: Locales) => { + + const formatters: Formatters = { + // add your formatter functions here + } + + return formatters +} diff --git a/website/src/i18n/i18n-svelte.ts b/website/src/i18n/i18n-svelte.ts new file mode 100644 index 000000000..23d478de0 --- /dev/null +++ b/website/src/i18n/i18n-svelte.ts @@ -0,0 +1,12 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initI18nSvelte } from 'typesafe-i18n/svelte' +import type { Formatters, Locales, TranslationFunctions, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales } from './i18n-util.js' + +const { locale, LL, setLocale } = initI18nSvelte(loadedLocales, loadedFormatters) + +export { locale, LL, setLocale } + +export default LL diff --git a/website/src/i18n/i18n-types.ts b/website/src/i18n/i18n-types.ts new file mode 100644 index 000000000..1ba4742e7 --- /dev/null +++ b/website/src/i18n/i18n-types.ts @@ -0,0 +1,690 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ +import type { BaseTranslation as BaseTranslationType, LocalizedString } from 'typesafe-i18n' + +export type BaseTranslation = BaseTranslationType +export type BaseLocale = 'en' + +export type Locales = + | 'en' + | 'zh-CN' + +export type Translation = RootTranslation + +export type Translations = RootTranslation + +type RootTranslation = { + meta: { + /** + * e​n + */ + lang: string + /** + * S​e​r​v​e​r​B​o​x​ ​—​ ​S​e​r​v​e​r​ ​s​t​a​t​u​s​,​ ​S​S​H​,​ ​a​n​d​ ​o​p​e​r​a​t​i​o​n​s​ ​i​n​ ​o​n​e​ ​F​l​u​t​t​e​r​ ​a​p​p + */ + title: string + /** + * S​e​r​v​e​r​B​o​x​ ​m​o​n​i​t​o​r​s​ ​L​i​n​u​x​,​ ​U​n​i​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s​ ​s​e​r​v​e​r​s​ ​w​i​t​h​ ​s​t​a​t​u​s​ ​c​h​a​r​t​s​,​ ​S​S​H​ ​t​e​r​m​i​n​a​l​,​ ​S​F​T​P​,​ ​D​o​c​k​e​r​,​ ​p​r​o​c​e​s​s​,​ ​s​y​s​t​e​m​d​,​ ​S​.​M​.​A​.​R​.​T​,​ ​p​u​s​h​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​w​a​t​c​h​O​S​ ​s​u​p​p​o​r​t​. + */ + description: string + } + nav: { + /** + * F​e​a​t​u​r​e​s + */ + features: string + /** + * C​a​p​a​b​i​l​i​t​i​e​s + */ + capabilities: string + /** + * T​e​s​t​i​m​o​n​i​a​l​s + */ + testimonials: string + /** + * D​o​w​n​l​o​a​d + */ + download: string + /** + * L​a​n​g​u​a​g​e + */ + languageLabel: string + } + hero: { + /** + * S​e​r​v​e​r​ ​s​t​a​t​u​s​, + */ + titlePrefix: string + /** + * r​i​g​h​t​ ​i​n​ ​y​o​u​r​ ​p​o​c​k​e​t​. + */ + titleSuffix: string + /** + * S​e​r​v​e​r​B​o​x​ ​b​r​i​n​g​s​ ​c​h​a​r​t​s​,​ ​S​S​H​ ​t​e​r​m​i​n​a​l​,​ ​S​F​T​P​,​ ​D​o​c​k​e​r​,​ ​p​r​o​c​e​s​s​ ​c​o​n​t​r​o​l​,​ ​s​y​s​t​e​m​d​,​ ​S​.​M​.​A​.​R​.​T​,​ ​p​u​s​h​ ​a​l​e​r​t​s​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​w​a​t​c​h​O​S​ ​s​u​p​p​o​r​t​ ​i​n​t​o​ ​o​n​e​ ​F​l​u​t​t​e​r​ ​a​p​p​. + */ + subtitle: string + /** + * D​o​w​n​l​o​a​d​ ​S​e​r​v​e​r​B​o​x + */ + primaryAction: string + /** + * E​x​p​l​o​r​e​ ​f​e​a​t​u​r​e​s + */ + secondaryAction: string + } + screenshots: { + /** + * I​n​t​e​r​a​c​t​i​v​e​ ​S​e​r​v​e​r​B​o​x​ ​s​c​r​e​e​n​s​h​o​t​s + */ + label: string + /** + * S​e​r​v​e​r​B​o​x​ ​s​e​r​v​e​r​ ​o​v​e​r​v​i​e​w​ ​s​c​r​e​e​n​s​h​o​t + */ + one: string + /** + * S​e​r​v​e​r​B​o​x​ ​s​t​a​t​u​s​ ​c​h​a​r​t​ ​s​c​r​e​e​n​s​h​o​t + */ + two: string + /** + * S​e​r​v​e​r​B​o​x​ ​t​e​r​m​i​n​a​l​ ​s​c​r​e​e​n​s​h​o​t + */ + three: string + /** + * S​e​r​v​e​r​B​o​x​ ​t​o​o​l​s​ ​s​c​r​e​e​n​s​h​o​t + */ + four: string + } + features: { + /** + * O​n​e​ ​c​o​m​p​a​c​t​ ​w​o​r​k​s​p​a​c​e​ ​f​o​r​ ​e​v​e​r​y​d​a​y​ ​s​e​r​v​e​r​ ​m​a​i​n​t​e​n​a​n​c​e​. + */ + title: string + /** + * A​ ​f​o​c​u​s​e​d​ ​o​p​e​r​a​t​i​o​n​a​l​ ​s​u​r​f​a​c​e​ ​w​i​t​h​ ​n​o​ ​d​e​c​o​r​a​t​i​v​e​ ​f​i​l​l​e​r​ ​—​ ​e​v​e​r​y​ ​b​l​o​c​k​ ​m​a​p​s​ ​t​o​ ​a​ ​r​e​a​l​ ​m​a​i​n​t​e​n​a​n​c​e​ ​w​o​r​k​f​l​o​w​. + */ + subtitle: string + charts: { + /** + * S​t​a​t​u​s​ ​C​h​a​r​t​s + */ + title: string + /** + * T​r​a​c​k​ ​C​P​U​,​ ​m​e​m​o​r​y​,​ ​s​e​n​s​o​r​s​,​ ​G​P​U​,​ ​n​e​t​w​o​r​k​,​ ​d​i​s​k​,​ ​a​n​d​ ​h​o​s​t​ ​h​e​a​l​t​h​ ​f​r​o​m​ ​d​e​n​s​e​ ​m​o​b​i​l​e​ ​c​h​a​r​t​s​. + */ + description: string + } + workspace: { + /** + * C​r​o​s​s​-​P​l​a​t​f​o​r​m​ ​W​o​r​k​s​p​a​c​e + */ + title: string + /** + * U​s​e​ ​S​e​r​v​e​r​B​o​x​ ​a​c​r​o​s​s​ ​i​O​S​,​ ​A​n​d​r​o​i​d​,​ ​m​a​c​O​S​,​ ​L​i​n​u​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s​ ​w​i​t​h​ ​o​n​e​ ​f​a​m​i​l​i​a​r​ ​F​l​u​t​t​e​r​ ​i​n​t​e​r​f​a​c​e​. + */ + description: string + } + terminal: { + /** + * S​S​H​ ​T​e​r​m​i​n​a​l​ ​a​n​d​ ​S​F​T​P + */ + title: string + /** + * O​p​e​n​ ​c​o​m​m​a​n​d​-​l​i​n​e​ ​a​n​d​ ​f​i​l​e​ ​s​e​s​s​i​o​n​s​ ​d​i​r​e​c​t​l​y​ ​f​r​o​m​ ​a​ ​s​e​r​v​e​r​ ​c​a​r​d​,​ ​b​a​c​k​e​d​ ​b​y​ ​d​a​r​t​s​s​h​2​ ​a​n​d​ ​x​t​e​r​m​.​d​a​r​t​. + */ + description: string + } + native: { + /** + * N​a​t​i​v​e​ ​D​e​v​i​c​e​ ​H​o​o​k​s + */ + title: string + /** + * B​i​o​m​e​t​r​i​c​ ​a​u​t​h​,​ ​m​e​s​s​a​g​e​ ​p​u​s​h​,​ ​h​o​m​e​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​w​a​t​c​h​O​S​ ​s​u​p​p​o​r​t​ ​k​e​e​p​ ​s​e​r​v​e​r​ ​c​o​n​t​e​x​t​ ​n​e​a​r​b​y​. + */ + description: string + } + platforms: { + /** + * D​o​c​k​e​r​,​ ​P​r​o​c​e​s​s​,​ ​S​y​s​t​e​m​d + */ + title: string + /** + * I​n​s​p​e​c​t​ ​c​o​n​t​a​i​n​e​r​s​,​ ​p​r​o​c​e​s​s​e​s​,​ ​a​n​d​ ​s​e​r​v​i​c​e​s​ ​w​i​t​h​o​u​t​ ​s​w​i​t​c​h​i​n​g​ ​a​w​a​y​ ​f​r​o​m​ ​y​o​u​r​ ​m​o​n​i​t​o​r​i​n​g​ ​f​l​o​w​. + */ + description: string + } + } + capabilities: { + /** + * A​l​l​ ​t​h​e​ ​t​o​o​l​s​.​ ​O​n​e​ ​a​p​p​. + */ + title: string + /** + * S​e​r​v​e​r​B​o​x​ ​k​e​e​p​s​ ​t​e​r​m​i​n​a​l​ ​a​c​c​e​s​s​,​ ​f​i​l​e​ ​t​r​a​n​s​f​e​r​,​ ​s​e​r​v​i​c​e​ ​c​h​e​c​k​s​,​ ​h​a​r​d​w​a​r​e​ ​h​e​a​l​t​h​,​ ​a​n​d​ ​d​e​v​i​c​e​-​n​a​t​i​v​e​ ​a​l​e​r​t​s​ ​i​n​ ​t​h​e​ ​s​a​m​e​ ​w​o​r​k​f​l​o​w​. + */ + subtitle: string + /** + * #​ ​i​O​S​ ​a​n​d​ ​m​a​c​O​S + */ + installIosPrompt: string + /** + * #​ ​A​n​d​r​o​i​d​,​ ​L​i​n​u​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s + */ + installReleasePrompt: string + } + download: { + /** + * E​v​e​r​y​ ​p​l​a​t​f​o​r​m​,​ ​e​v​e​r​y​ ​o​f​f​i​c​i​a​l​ ​s​o​u​r​c​e​. + */ + title: string + /** + * C​h​o​o​s​e​ ​t​h​e​ ​c​h​a​n​n​e​l​ ​t​h​a​t​ ​m​a​t​c​h​e​s​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​a​n​d​ ​t​r​u​s​t​ ​m​o​d​e​l​.​ ​i​O​S​ ​a​n​d​ ​m​a​c​O​S​ ​u​s​e​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​;​ ​A​n​d​r​o​i​d​,​ ​L​i​n​u​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s​ ​a​l​s​o​ ​h​a​v​e​ ​d​i​r​e​c​t​ ​p​a​c​k​a​g​e​ ​d​o​w​n​l​o​a​d​s​. + */ + subtitle: string + platforms: { + iosMacos: { + /** + * i​O​S​ ​/​ ​m​a​c​O​S + */ + title: string + /** + * U​s​e​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​ ​b​u​i​l​d​ ​f​o​r​ ​A​p​p​l​e​ ​p​l​a​t​f​o​r​m​s​,​ ​i​n​c​l​u​d​i​n​g​ ​a​u​t​o​m​a​t​i​c​ ​u​p​d​a​t​e​s​ ​t​h​r​o​u​g​h​ ​y​o​u​r​ ​A​p​p​l​e​ ​a​c​c​o​u​n​t​. + */ + description: string + } + android: { + /** + * A​n​d​r​o​i​d + */ + title: string + /** + * I​n​s​t​a​l​l​ ​f​r​o​m​ ​G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s​,​ ​t​h​e​ ​p​r​o​j​e​c​t​ ​C​D​N​,​ ​F​-​D​r​o​i​d​,​ ​o​r​ ​O​p​e​n​A​P​K​ ​d​e​p​e​n​d​i​n​g​ ​o​n​ ​h​o​w​ ​y​o​u​ ​m​a​n​a​g​e​ ​A​n​d​r​o​i​d​ ​p​a​c​k​a​g​e​s​. + */ + description: string + } + linux: { + /** + * L​i​n​u​x + */ + title: string + /** + * D​o​w​n​l​o​a​d​ ​d​e​s​k​t​o​p​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s​ ​o​r​ ​t​h​e​ ​p​r​o​j​e​c​t​ ​C​D​N​. + */ + description: string + } + windows: { + /** + * W​i​n​d​o​w​s + */ + title: string + /** + * D​o​w​n​l​o​a​d​ ​W​i​n​d​o​w​s​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s​ ​o​r​ ​t​h​e​ ​p​r​o​j​e​c​t​ ​C​D​N​. + */ + description: string + } + } + sources: { + appStore: { + /** + * A​p​p​ ​S​t​o​r​e + */ + name: string + /** + * A​p​p​l​e​ ​p​l​a​t​f​o​r​m​s + */ + note: string + } + github: { + /** + * G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s + */ + name: string + /** + * r​e​l​e​a​s​e​ ​b​u​i​l​d​s + */ + note: string + } + cdn: { + /** + * P​r​o​j​e​c​t​ ​C​D​N + */ + name: string + /** + * p​a​c​k​a​g​e​ ​m​i​r​r​o​r + */ + note: string + } + fdroid: { + /** + * F​-​D​r​o​i​d + */ + name: string + /** + * A​n​d​r​o​i​d​ ​r​e​p​o + */ + note: string + } + openapk: { + /** + * O​p​e​n​A​P​K + */ + name: string + /** + * A​n​d​r​o​i​d​ ​l​i​s​t​i​n​g + */ + note: string + } + } + /** + * O​n​l​y​ ​d​o​w​n​l​o​a​d​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​a​ ​s​o​u​r​c​e​ ​y​o​u​ ​t​r​u​s​t​.​ ​F​o​r​ ​s​e​r​v​e​r​-​s​i​d​e​ ​p​u​s​h​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​c​o​m​p​a​n​i​o​n​ ​m​o​n​i​t​o​r​i​n​g​,​ ​i​n​s​t​a​l​l​ ​S​e​r​v​e​r​B​o​x​M​o​n​i​t​o​r​ ​s​e​p​a​r​a​t​e​l​y​ ​o​n​ ​y​o​u​r​ ​s​e​r​v​e​r​s​. + */ + note: string + } + testimonials: { + /** + * T​r​u​s​t​e​d​ ​b​y​ ​p​e​o​p​l​e​ ​w​h​o​ ​m​a​i​n​t​a​i​n​ ​r​e​a​l​ ​m​a​c​h​i​n​e​s​. + */ + title: string + admin: { + /** + * S​e​r​v​e​r​B​o​x​ ​k​e​e​p​s​ ​t​h​e​ ​q​u​i​c​k​ ​s​e​r​v​e​r​ ​c​h​e​c​k​s​ ​I​ ​d​o​ ​e​v​e​r​y​ ​d​a​y​ ​i​n​ ​o​n​e​ ​p​l​a​c​e​,​ ​w​i​t​h​o​u​t​ ​f​o​r​c​i​n​g​ ​m​e​ ​b​a​c​k​ ​t​o​ ​a​ ​l​a​p​t​o​p​. + */ + quote: string + /** + * S​e​r​v​e​r​ ​A​d​m​i​n​i​s​t​r​a​t​o​r + */ + role: string + } + infra: { + /** + * T​h​e​ ​j​u​m​p​ ​f​r​o​m​ ​m​e​t​r​i​c​s​ ​t​o​ ​S​S​H​ ​a​n​d​ ​S​F​T​P​ ​i​s​ ​d​i​r​e​c​t​.​ ​I​t​ ​r​e​m​o​v​e​s​ ​a​ ​l​o​t​ ​o​f​ ​s​m​a​l​l​ ​c​o​n​t​e​x​t​ ​s​w​i​t​c​h​e​s​ ​d​u​r​i​n​g​ ​i​n​c​i​d​e​n​t​s​. + */ + quote: string + /** + * I​n​f​r​a​s​t​r​u​c​t​u​r​e​ ​M​a​i​n​t​a​i​n​e​r + */ + role: string + } + student: { + /** + * I​t​ ​i​s​ ​l​i​g​h​t​w​e​i​g​h​t​ ​e​n​o​u​g​h​ ​f​o​r​ ​m​y​ ​p​e​r​s​o​n​a​l​ ​s​e​r​v​e​r​s​,​ ​b​u​t​ ​s​t​i​l​l​ ​c​o​v​e​r​s​ ​D​o​c​k​e​r​,​ ​s​y​s​t​e​m​d​,​ ​a​n​d​ ​h​e​a​l​t​h​ ​c​h​e​c​k​s​. + */ + quote: string + /** + * H​o​m​e​l​a​b​ ​U​s​e​r + */ + role: string + } + } + cta: { + /** + * S​e​r​v​e​r​B​o​x​ ​i​s​ ​f​r​e​e​ ​a​n​d​ ​o​p​e​n​ ​s​o​u​r​c​e​ ​u​n​d​e​r​ ​A​G​P​L​v​3​. + */ + title: string + /** + * I​n​s​t​a​l​l​ ​f​r​o​m​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​,​ ​G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s​,​ ​F​-​D​r​o​i​d​,​ ​O​p​e​n​A​P​K​,​ ​o​r​ ​t​h​e​ ​p​r​o​j​e​c​t​ ​C​D​N​.​ ​O​n​l​y​ ​d​o​w​n​l​o​a​d​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​s​o​u​r​c​e​s​ ​y​o​u​ ​t​r​u​s​t​. + */ + subtitle: string + /** + * O​p​e​n​ ​A​p​p​ ​S​t​o​r​e + */ + appStoreAction: string + /** + * D​o​w​n​l​o​a​d​ ​f​r​o​m​ ​G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s + */ + githubAction: string + } + footer: { + /** + * F​e​a​t​u​r​e​s + */ + features: string + /** + * C​a​p​a​b​i​l​i​t​i​e​s + */ + capabilities: string + /** + * R​e​l​e​a​s​e​s + */ + releases: string + } +} + +export type TranslationFunctions = { + meta: { + /** + * en + */ + lang: () => LocalizedString + /** + * ServerBox — Server status, SSH, and operations in one Flutter app + */ + title: () => LocalizedString + /** + * ServerBox monitors Linux, Unix, and Windows servers with status charts, SSH terminal, SFTP, Docker, process, systemd, S.M.A.R.T, push, widgets, and watchOS support. + */ + description: () => LocalizedString + } + nav: { + /** + * Features + */ + features: () => LocalizedString + /** + * Capabilities + */ + capabilities: () => LocalizedString + /** + * Testimonials + */ + testimonials: () => LocalizedString + /** + * Download + */ + download: () => LocalizedString + /** + * Language + */ + languageLabel: () => LocalizedString + } + hero: { + /** + * Server status, + */ + titlePrefix: () => LocalizedString + /** + * right in your pocket. + */ + titleSuffix: () => LocalizedString + /** + * ServerBox brings charts, SSH terminal, SFTP, Docker, process control, systemd, S.M.A.R.T, push alerts, widgets, and watchOS support into one Flutter app. + */ + subtitle: () => LocalizedString + /** + * Download ServerBox + */ + primaryAction: () => LocalizedString + /** + * Explore features + */ + secondaryAction: () => LocalizedString + } + screenshots: { + /** + * Interactive ServerBox screenshots + */ + label: () => LocalizedString + /** + * ServerBox server overview screenshot + */ + one: () => LocalizedString + /** + * ServerBox status chart screenshot + */ + two: () => LocalizedString + /** + * ServerBox terminal screenshot + */ + three: () => LocalizedString + /** + * ServerBox tools screenshot + */ + four: () => LocalizedString + } + features: { + /** + * One compact workspace for everyday server maintenance. + */ + title: () => LocalizedString + /** + * A focused operational surface with no decorative filler — every block maps to a real maintenance workflow. + */ + subtitle: () => LocalizedString + charts: { + /** + * Status Charts + */ + title: () => LocalizedString + /** + * Track CPU, memory, sensors, GPU, network, disk, and host health from dense mobile charts. + */ + description: () => LocalizedString + } + workspace: { + /** + * Cross-Platform Workspace + */ + title: () => LocalizedString + /** + * Use ServerBox across iOS, Android, macOS, Linux, and Windows with one familiar Flutter interface. + */ + description: () => LocalizedString + } + terminal: { + /** + * SSH Terminal and SFTP + */ + title: () => LocalizedString + /** + * Open command-line and file sessions directly from a server card, backed by dartssh2 and xterm.dart. + */ + description: () => LocalizedString + } + native: { + /** + * Native Device Hooks + */ + title: () => LocalizedString + /** + * Biometric auth, message push, home widgets, and watchOS support keep server context nearby. + */ + description: () => LocalizedString + } + platforms: { + /** + * Docker, Process, Systemd + */ + title: () => LocalizedString + /** + * Inspect containers, processes, and services without switching away from your monitoring flow. + */ + description: () => LocalizedString + } + } + capabilities: { + /** + * All the tools. One app. + */ + title: () => LocalizedString + /** + * ServerBox keeps terminal access, file transfer, service checks, hardware health, and device-native alerts in the same workflow. + */ + subtitle: () => LocalizedString + /** + * # iOS and macOS + */ + installIosPrompt: () => LocalizedString + /** + * # Android, Linux, and Windows + */ + installReleasePrompt: () => LocalizedString + } + download: { + /** + * Every platform, every official source. + */ + title: () => LocalizedString + /** + * Choose the channel that matches your device and trust model. iOS and macOS use the App Store; Android, Linux, and Windows also have direct package downloads. + */ + subtitle: () => LocalizedString + platforms: { + iosMacos: { + /** + * iOS / macOS + */ + title: () => LocalizedString + /** + * Use the App Store build for Apple platforms, including automatic updates through your Apple account. + */ + description: () => LocalizedString + } + android: { + /** + * Android + */ + title: () => LocalizedString + /** + * Install from GitHub Releases, the project CDN, F-Droid, or OpenAPK depending on how you manage Android packages. + */ + description: () => LocalizedString + } + linux: { + /** + * Linux + */ + title: () => LocalizedString + /** + * Download desktop packages from GitHub Releases or the project CDN. + */ + description: () => LocalizedString + } + windows: { + /** + * Windows + */ + title: () => LocalizedString + /** + * Download Windows packages from GitHub Releases or the project CDN. + */ + description: () => LocalizedString + } + } + sources: { + appStore: { + /** + * App Store + */ + name: () => LocalizedString + /** + * Apple platforms + */ + note: () => LocalizedString + } + github: { + /** + * GitHub Releases + */ + name: () => LocalizedString + /** + * release builds + */ + note: () => LocalizedString + } + cdn: { + /** + * Project CDN + */ + name: () => LocalizedString + /** + * package mirror + */ + note: () => LocalizedString + } + fdroid: { + /** + * F-Droid + */ + name: () => LocalizedString + /** + * Android repo + */ + note: () => LocalizedString + } + openapk: { + /** + * OpenAPK + */ + name: () => LocalizedString + /** + * Android listing + */ + note: () => LocalizedString + } + } + /** + * Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers. + */ + note: () => LocalizedString + } + testimonials: { + /** + * Trusted by people who maintain real machines. + */ + title: () => LocalizedString + admin: { + /** + * ServerBox keeps the quick server checks I do every day in one place, without forcing me back to a laptop. + */ + quote: () => LocalizedString + /** + * Server Administrator + */ + role: () => LocalizedString + } + infra: { + /** + * The jump from metrics to SSH and SFTP is direct. It removes a lot of small context switches during incidents. + */ + quote: () => LocalizedString + /** + * Infrastructure Maintainer + */ + role: () => LocalizedString + } + student: { + /** + * It is lightweight enough for my personal servers, but still covers Docker, systemd, and health checks. + */ + quote: () => LocalizedString + /** + * Homelab User + */ + role: () => LocalizedString + } + } + cta: { + /** + * ServerBox is free and open source under AGPLv3. + */ + title: () => LocalizedString + /** + * Install from the App Store, GitHub Releases, F-Droid, OpenAPK, or the project CDN. Only download packages from sources you trust. + */ + subtitle: () => LocalizedString + /** + * Open App Store + */ + appStoreAction: () => LocalizedString + /** + * Download from GitHub Releases + */ + githubAction: () => LocalizedString + } + footer: { + /** + * Features + */ + features: () => LocalizedString + /** + * Capabilities + */ + capabilities: () => LocalizedString + /** + * Releases + */ + releases: () => LocalizedString + } +} + +export type Formatters = {} diff --git a/website/src/i18n/i18n-util.async.ts b/website/src/i18n/i18n-util.async.ts new file mode 100644 index 000000000..7621099d9 --- /dev/null +++ b/website/src/i18n/i18n-util.async.ts @@ -0,0 +1,27 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters.js' +import type { Locales, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales, locales } from './i18n-util.js' + +const localeTranslationLoaders = { + en: () => import('./en/index.js'), + 'zh-CN': () => import('./zh-CN/index.js'), +} + +const updateDictionary = (locale: Locales, dictionary: Partial): Translations => + loadedLocales[locale] = { ...loadedLocales[locale], ...dictionary } + +export const importLocaleAsync = async (locale: Locales): Promise => + (await localeTranslationLoaders[locale]()).default as unknown as Translations + +export const loadLocaleAsync = async (locale: Locales): Promise => { + updateDictionary(locale, await importLocaleAsync(locale)) + loadFormatters(locale) +} + +export const loadAllLocalesAsync = (): Promise => Promise.all(locales.map(loadLocaleAsync)) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/website/src/i18n/i18n-util.sync.ts b/website/src/i18n/i18n-util.sync.ts new file mode 100644 index 000000000..b6f8fa5c6 --- /dev/null +++ b/website/src/i18n/i18n-util.sync.ts @@ -0,0 +1,26 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { initFormatters } from './formatters.js' +import type { Locales, Translations } from './i18n-types.js' +import { loadedFormatters, loadedLocales, locales } from './i18n-util.js' + +import en from './en/index.js' +import zh_CN from './zh-CN/index.js' + +const localeTranslations = { + en, + 'zh-CN': zh_CN, +} + +export const loadLocale = (locale: Locales): void => { + if (loadedLocales[locale]) return + + loadedLocales[locale] = localeTranslations[locale] as unknown as Translations + loadFormatters(locale) +} + +export const loadAllLocales = (): void => locales.forEach(loadLocale) + +export const loadFormatters = (locale: Locales): void => + void (loadedFormatters[locale] = initFormatters(locale)) diff --git a/website/src/i18n/i18n-util.ts b/website/src/i18n/i18n-util.ts new file mode 100644 index 000000000..d69518834 --- /dev/null +++ b/website/src/i18n/i18n-util.ts @@ -0,0 +1,38 @@ +// This file was auto-generated by 'typesafe-i18n'. Any manual changes will be overwritten. +/* eslint-disable */ + +import { i18n as initI18n, i18nObject as initI18nObject, i18nString as initI18nString } from 'typesafe-i18n' +import type { LocaleDetector } from 'typesafe-i18n/detectors' +import type { LocaleTranslationFunctions, TranslateByString } from 'typesafe-i18n' +import { detectLocale as detectLocaleFn } from 'typesafe-i18n/detectors' +import { initExtendDictionary } from 'typesafe-i18n/utils' +import type { Formatters, Locales, Translations, TranslationFunctions } from './i18n-types.js' + +export const baseLocale: Locales = 'en' + +export const locales: Locales[] = [ + 'en', + 'zh-CN' +] + +export const isLocale = (locale: string): locale is Locales => locales.includes(locale as Locales) + +export const loadedLocales: Record = {} as Record + +export const loadedFormatters: Record = {} as Record + +export const extendDictionary = initExtendDictionary() + +export const i18nString = (locale: Locales): TranslateByString => initI18nString(locale, loadedFormatters[locale]) + +export const i18nObject = (locale: Locales): TranslationFunctions => + initI18nObject( + locale, + loadedLocales[locale], + loadedFormatters[locale] + ) + +export const i18n = (): LocaleTranslationFunctions => + initI18n(loadedLocales, loadedFormatters) + +export const detectLocale = (...detectors: LocaleDetector[]): Locales => detectLocaleFn(baseLocale, locales, ...detectors) diff --git a/website/src/i18n/zh-CN/index.ts b/website/src/i18n/zh-CN/index.ts new file mode 100644 index 000000000..daa88eed4 --- /dev/null +++ b/website/src/i18n/zh-CN/index.ts @@ -0,0 +1,151 @@ +import type { Translation } from '../i18n-types.js' + +const zhCN: Translation = { + meta: { + lang: 'zh-CN', + title: 'ServerBox — 服务器状态、SSH 与运维工具', + description: + 'ServerBox 用状态图表、SSH 终端、SFTP、Docker、进程、systemd、S.M.A.R.T、推送、小组件和 watchOS 支持监控 Linux、Unix 与 Windows 服务器。', + }, + nav: { + features: '特性', + capabilities: '能力', + testimonials: '用户评价', + download: '下载', + languageLabel: '语言', + }, + hero: { + titlePrefix: '服务器状态,', + titleSuffix: '就在口袋里。', + subtitle: + 'ServerBox 将图表、SSH 终端、SFTP、Docker、进程控制、systemd、S.M.A.R.T、推送提醒、小组件和 watchOS 支持整合到一个 Flutter 应用里。', + primaryAction: '下载 ServerBox', + secondaryAction: '查看特性', + }, + screenshots: { + label: '可交互的 ServerBox 截图', + one: 'ServerBox 服务器概览截图', + two: 'ServerBox 状态图表截图', + three: 'ServerBox 终端截图', + four: 'ServerBox 工具截图', + }, + features: { + title: '一个紧凑工作区,覆盖日常服务器维护。', + subtitle: '能力密度高,没有装饰性填充;每个模块都对应真实维护工作流。', + charts: { + title: '状态图表', + description: + '通过高密度移动端图表跟踪 CPU、内存、传感器、GPU、网络、磁盘和主机健康状态。', + }, + workspace: { + title: '跨平台工作区', + description: + '在 iOS、Android、macOS、Linux 和 Windows 上使用同一个熟悉的 Flutter 界面。', + }, + terminal: { + title: 'SSH 终端与 SFTP', + description: + '从服务器卡片直接打开命令行和文件会话,底层基于 dartssh2 与 xterm.dart。', + }, + native: { + title: '设备原生能力', + description: + '生物认证、消息推送、桌面小组件和 watchOS 支持,让服务器上下文保持贴近。', + }, + platforms: { + title: 'Docker、进程、systemd', + description: + '无需离开监控流程,就能检查容器、进程和服务状态。', + }, + }, + capabilities: { + title: '所有工具,一个应用。', + subtitle: + 'ServerBox 将终端访问、文件传输、服务检查、硬件健康和设备原生提醒放在同一个工作流中。', + installIosPrompt: '# iOS 与 macOS', + installReleasePrompt: '# Android、Linux 与 Windows', + }, + download: { + title: '所有平台,所有官方来源。', + subtitle: + '根据设备和信任模型选择下载渠道。iOS 与 macOS 使用 App Store;Android、Linux 和 Windows 也提供直接安装包。', + platforms: { + iosMacos: { + title: 'iOS / macOS', + description: + 'Apple 平台使用 App Store 构建,并可通过 Apple 账号获得自动更新。', + }, + android: { + title: 'Android', + description: + '可从 GitHub Releases、项目 CDN、F-Droid 或 OpenAPK 安装,取决于你的 Android 包管理方式。', + }, + linux: { + title: 'Linux', + description: + '可从 GitHub Releases 或项目 CDN 下载桌面端安装包。', + }, + windows: { + title: 'Windows', + description: + '可从 GitHub Releases 或项目 CDN 下载 Windows 安装包。', + }, + }, + sources: { + appStore: { + name: 'App Store', + note: 'Apple 平台', + }, + github: { + name: 'GitHub Releases', + note: '发布构建', + }, + cdn: { + name: '项目 CDN', + note: '安装包镜像', + }, + fdroid: { + name: 'F-Droid', + note: 'Android 仓库', + }, + openapk: { + name: 'OpenAPK', + note: 'Android 页面', + }, + }, + note: + '请只从你信任的来源下载安装包。若需要服务器端推送、小组件和 companion 监控,请在服务器上单独安装 ServerBoxMonitor。', + }, + testimonials: { + title: '受到真实机器维护者信任。', + admin: { + quote: + 'ServerBox 把我每天要做的快速服务器检查放在一个地方,不必总是回到电脑前。', + role: '服务器管理员', + }, + infra: { + quote: + '从指标跳到 SSH 和 SFTP 很直接,事故处理中少了很多细碎的上下文切换。', + role: '基础设施维护者', + }, + student: { + quote: + '它对个人服务器足够轻量,但依然覆盖 Docker、systemd 和健康检查。', + role: 'Homelab 用户', + }, + }, + cta: { + title: 'ServerBox 是基于 AGPLv3 的免费开源软件。', + subtitle: + '可从 App Store、GitHub Releases、F-Droid、OpenAPK 或项目 CDN 安装。请只从你信任的来源下载安装包。', + appStoreAction: '打开 App Store', + githubAction: '从 GitHub Releases 下载', + }, + footer: { + features: '特性', + capabilities: '能力', + releases: '版本发布', + }, +} + +export default zhCN diff --git a/website/src/lib/Counter.svelte b/website/src/lib/Counter.svelte new file mode 100644 index 000000000..20bf4c97a --- /dev/null +++ b/website/src/lib/Counter.svelte @@ -0,0 +1,5 @@ + + + diff --git a/website/src/lib/i18n.js b/website/src/lib/i18n.js new file mode 100644 index 000000000..797e1cab1 --- /dev/null +++ b/website/src/lib/i18n.js @@ -0,0 +1,38 @@ +import { baseLocale, locales as generatedLocales } from '../i18n/i18n-util' + +export const defaultLocale = baseLocale + +export const locales = [ + { code: 'en', label: 'English' }, + { code: 'zh-CN', label: '简体中文' }, +].filter((locale) => generatedLocales.includes(locale.code)) + +export const localeStorageKey = 'mfuse.website.locale' + +export function normalizeLocale(locale) { + if (!locale) return defaultLocale + if (generatedLocales.includes(locale)) return locale + + const lowerLocale = locale.toLowerCase() + if (lowerLocale.startsWith('zh')) return 'zh-CN' + if (lowerLocale.startsWith('en')) return 'en' + + return defaultLocale +} + +export function getInitialLocale() { + const params = new URLSearchParams(window.location.search) + const queryLocale = normalizeLocale(params.get('lang')) + if (params.has('lang')) return queryLocale + + const storedLocale = localStorage.getItem(localeStorageKey) + if (storedLocale) return normalizeLocale(storedLocale) + + return normalizeLocale(navigator.languages?.[0] || navigator.language) +} + +export function syncLocaleToUrl(locale) { + const url = new URL(window.location.href) + url.searchParams.set('lang', normalizeLocale(locale)) + window.history.replaceState({}, '', url) +} diff --git a/website/src/lib/utils.js b/website/src/lib/utils.js new file mode 100644 index 000000000..f79e2db4c --- /dev/null +++ b/website/src/lib/utils.js @@ -0,0 +1,8 @@ +import { clsx, } from "clsx"; +import { twMerge } from "tailwind-merge"; + +export function cn(...inputs) { + return twMerge(clsx(inputs)); +} + +// eslint-disable-next-line @typescript-eslint/no-explicit-any \ No newline at end of file diff --git a/website/src/main.js b/website/src/main.js new file mode 100644 index 000000000..458c7a8a8 --- /dev/null +++ b/website/src/main.js @@ -0,0 +1,9 @@ +import { mount } from 'svelte' +import './app.css' +import App from './App.svelte' + +const app = mount(App, { + target: document.getElementById('app'), +}) + +export default app diff --git a/website/svelte.config.js b/website/svelte.config.js new file mode 100644 index 000000000..0cf7db32a --- /dev/null +++ b/website/svelte.config.js @@ -0,0 +1,2 @@ +/** @type {import("@sveltejs/vite-plugin-svelte").SvelteConfig} */ +export default {} diff --git a/website/vite.config.js b/website/vite.config.js new file mode 100644 index 000000000..f66a825c7 --- /dev/null +++ b/website/vite.config.js @@ -0,0 +1,17 @@ +import { defineConfig } from 'vite' +import { svelte } from '@sveltejs/vite-plugin-svelte' +import tailwindcss from '@tailwindcss/vite' +import path from 'node:path' +import { fileURLToPath } from 'node:url' + +const __dirname = path.dirname(fileURLToPath(import.meta.url)) + +// https://vite.dev/config/ +export default defineConfig({ + plugins: [tailwindcss(), svelte()], + resolve: { + alias: { + $lib: path.resolve(__dirname, 'src/lib'), + }, + }, +}) From 3c22a463b9fd927aa8b665b8fef537ea00f192f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sat, 25 Apr 2026 03:53:56 +0800 Subject: [PATCH 02/11] opt. --- website/src/App.svelte | 112 +++++++++++++------------ website/src/app.css | 123 +++++++++++++-------------- website/src/i18n/en/index.ts | 34 ++------ website/src/i18n/i18n-types.ts | 144 +++++--------------------------- website/src/i18n/zh-CN/index.ts | 34 ++------ 5 files changed, 152 insertions(+), 295 deletions(-) diff --git a/website/src/App.svelte b/website/src/App.svelte index e4c5a92d0..2fd0bd9b1 100644 --- a/website/src/App.svelte +++ b/website/src/App.svelte @@ -1,4 +1,12 @@ {#if locale && isMounted} @@ -140,7 +156,6 @@ @@ -185,7 +199,7 @@ alt={$LL.screenshots[shot.key]()} loading={index === 0 ? 'eager' : 'lazy'} referrerpolicy="no-referrer" - style={`--base-x:${shot.x}%; --base-y:${shot.y}%; --base-r:${shot.rotate}deg; --move-x:${$stackMotion.x * shot.motion}px; --move-y:${$stackMotion.y * shot.motion}px; --tilt-x:${-$stackMotion.y * 6}deg; --tilt-y:${$stackMotion.x * 8}deg; --z:${index + 1};`} + style={`--base-x:${shot.x}%; --base-y:${shot.y}%; --hover-slot:${shot.hoverSlot}; --base-r:${shot.rotate}deg; --hover-r:${shot.hoverRotate}deg; --move-x:${$stackMotion.x * shot.motion}px; --move-y:${$stackMotion.y * shot.motion}px; --tilt-x:${-$stackMotion.y * 6}deg; --tilt-y:${$stackMotion.x * 8}deg; --z:${index + 1};`} /> {/each} @@ -223,13 +237,6 @@ {item} {/each} - -
- {$LL.capabilities.installIosPrompt()} - App Store: apps.apple.com/app/id1586449703 - {$LL.capabilities.installReleasePrompt()} - github.com/lollipopkit/flutter_server_box/releases -
@@ -240,19 +247,32 @@

-
+
{#each downloadGroups as group} -
-
+ @@ -262,24 +282,6 @@

{$LL.download.note()}

-
-
-

{$LL.testimonials.title()}

-
- -
- {#each testimonials as t} -
-

“{$LL.testimonials[t.key].quote()}”

-
-

{t.name}

-

{$LL.testimonials[t.key].role()}

-
-
- {/each} -
-
-

{$LL.cta.title()}

diff --git a/website/src/app.css b/website/src/app.css index d708d58fa..b29985e80 100644 --- a/website/src/app.css +++ b/website/src/app.css @@ -225,11 +225,12 @@ body { .screenshot-stack { position: relative; - width: min(860px, 92vw); - height: clamp(340px, 44vw, 520px); + width: min(1040px, 92vw); + height: clamp(430px, 58vw, 620px); margin-top: clamp(2rem, 5vw, 4rem); perspective: 1200px; outline: none; + --spread: clamp(148px, 24vw, 250px); } .screenshot-stack:focus-visible { @@ -243,12 +244,12 @@ body { left: 50%; top: 50%; z-index: var(--z); - width: min(250px, 34vw); - aspect-ratio: 512 / 833; - border-radius: 22px; - border: 1px solid var(--light-gray); - background: var(--snow); - box-shadow: 0 18px 44px rgba(0, 0, 0, 0.12); + width: min(220px, 21vw); + aspect-ratio: 9 / 20; + border-radius: 0; + border: 0; + background: transparent; + box-shadow: 0 18px 44px rgba(0, 0, 0, 0.14); object-fit: cover; transform: translate3d(calc(-50% + var(--base-x) + var(--move-x)), calc(-50% + var(--base-y) + var(--move-y)), 0) @@ -258,13 +259,18 @@ body { transform-style: preserve-3d; will-change: transform; transition: + transform 520ms cubic-bezier(0.16, 1, 0.3, 1), box-shadow 0.2s ease, - border-color 0.2s ease; + opacity 0.2s ease; } .screenshot-stack:hover .screenshot-card { - border-color: var(--border-light); box-shadow: 0 22px 54px rgba(0, 0, 0, 0.16); + transform: + translate3d(calc(-50% + (var(--hover-slot) * var(--spread)) + var(--move-x)), calc(-50% + var(--move-y)), 0) + rotateZ(var(--hover-r)) + rotateX(var(--tilt-x)) + rotateY(var(--tilt-y)); } .btn { @@ -484,81 +490,71 @@ body { padding: clamp(5rem, 12vw, 9rem) 0; } -.download-grid { - display: grid; - grid-template-columns: repeat(2, 1fr); - gap: 1rem; -} - -.download-card { - border-radius: var(--radius-container); - border: 1px solid var(--light-gray); - background: var(--white); - padding: 1.5rem; - display: flex; - flex-direction: column; - gap: 1.25rem; - transition: border-color 0.2s ease; +.download-list { + border-top: 1px solid var(--light-gray); } -.download-card:hover { - border-color: var(--border-light); +.download-platform { + display: grid; + grid-template-columns: minmax(190px, 0.8fr) minmax(0, 1.4fr); + gap: 1.5rem; + padding: 1.35rem 0; + border-bottom: 1px solid var(--light-gray); } -.download-card-head h3 { +.download-platform-copy h3 { font-family: var(--font-display); font-size: 1.05rem; font-weight: 500; color: var(--black); } -.download-card-head p { +.download-platform-copy p { margin-top: 0.45rem; color: var(--stone); font-size: 0.9rem; line-height: 1.55; } -.download-links { - display: grid; - gap: 0.5rem; - margin-top: auto; +.download-actions { + display: flex; + align-items: center; + justify-content: flex-end; + flex-wrap: wrap; + gap: 0.55rem; } -.download-links a { +.download-icon-btn { text-decoration: none; - border-radius: var(--radius-container); + font: inherit; + border-radius: var(--radius-pill); border: 1px solid var(--light-gray); - background: var(--snow); + background: var(--white); color: var(--near-black); - padding: 0.75rem 0.9rem; - display: flex; + min-height: 42px; + padding: 0.55rem 0.72rem; + display: inline-flex; align-items: center; - justify-content: space-between; - gap: 1rem; + justify-content: center; + gap: 0.42rem; + cursor: pointer; transition: + opacity 0.2s ease, background 0.2s ease, border-color 0.2s ease; } -.download-links a:hover { - background: var(--white); +.download-icon-btn:hover { + background: var(--snow); border-color: var(--border-light); + opacity: 0.9; } -.download-links span { +.download-icon-btn span { font-weight: 500; font-size: 0.92rem; } -.download-links small { - color: var(--stone); - font-family: var(--font-mono); - font-size: 0.75rem; - text-align: right; - white-space: nowrap; -} - .download-note { margin-top: 1rem; color: var(--stone); @@ -651,8 +647,13 @@ body { grid-template-columns: 1fr; } - .download-grid { + .download-platform { grid-template-columns: 1fr; + gap: 0.9rem; + } + + .download-actions { + justify-content: flex-start; } .hero h1 { @@ -688,28 +689,22 @@ body { .screenshot-stack { width: min(100%, 92vw); - height: 330px; + height: 390px; margin-top: 1.75rem; + --spread: clamp(88px, 28vw, 124px); } .screenshot-card { - width: min(176px, 42vw); - border-radius: 18px; + width: min(104px, 24vw); } .protocol-badges { gap: 0.4rem; } - .download-links a { - align-items: flex-start; - flex-direction: column; - gap: 0.2rem; - } - - .download-links small { - text-align: left; - white-space: normal; + .download-icon-btn { + min-height: 40px; + padding-inline: 0.68rem; } .site-footer { diff --git a/website/src/i18n/en/index.ts b/website/src/i18n/en/index.ts index 08faf613e..38e5c798f 100644 --- a/website/src/i18n/en/index.ts +++ b/website/src/i18n/en/index.ts @@ -10,7 +10,6 @@ const en: BaseTranslation = { nav: { features: 'Features', capabilities: 'Capabilities', - testimonials: 'Testimonials', download: 'Download', languageLabel: 'Language', }, @@ -67,14 +66,15 @@ const en: BaseTranslation = { installReleasePrompt: '# Android, Linux, and Windows', }, download: { - title: 'Every platform, every official source.', + title: 'Every platform, every source.', subtitle: 'Choose the channel that matches your device and trust model. iOS and macOS use the App Store; Android, Linux, and Windows also have direct package downloads.', + copied: 'Install command copied', platforms: { iosMacos: { title: 'iOS / macOS', description: - 'Use the App Store build for Apple platforms, including automatic updates through your Apple account.', + 'Use the App Store build for Apple platforms, or install the macOS-only Homebrew cask.', }, android: { title: 'Android', @@ -95,45 +95,25 @@ const en: BaseTranslation = { sources: { appStore: { name: 'App Store', - note: 'Apple platforms', + }, + homebrew: { + name: 'Homebrew Cask', }, github: { name: 'GitHub Releases', - note: 'release builds', }, cdn: { name: 'Project CDN', - note: 'package mirror', }, fdroid: { name: 'F-Droid', - note: 'Android repo', }, openapk: { name: 'OpenAPK', - note: 'Android listing', }, }, note: - 'Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers.', - }, - testimonials: { - title: 'Trusted by people who maintain real machines.', - admin: { - quote: - 'ServerBox keeps the quick server checks I do every day in one place, without forcing me back to a laptop.', - role: 'Server Administrator', - }, - infra: { - quote: - 'The jump from metrics to SSH and SFTP is direct. It removes a lot of small context switches during incidents.', - role: 'Infrastructure Maintainer', - }, - student: { - quote: - 'It is lightweight enough for my personal servers, but still covers Docker, systemd, and health checks.', - role: 'Homelab User', - }, + 'Homebrew supports macOS only: brew install --cask server-box. Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers.', }, cta: { title: 'ServerBox is free and open source under AGPLv3.', diff --git a/website/src/i18n/i18n-types.ts b/website/src/i18n/i18n-types.ts index 1ba4742e7..f1f54f519 100644 --- a/website/src/i18n/i18n-types.ts +++ b/website/src/i18n/i18n-types.ts @@ -37,10 +37,6 @@ type RootTranslation = { * C​a​p​a​b​i​l​i​t​i​e​s */ capabilities: string - /** - * T​e​s​t​i​m​o​n​i​a​l​s - */ - testimonials: string /** * D​o​w​n​l​o​a​d */ @@ -174,13 +170,17 @@ type RootTranslation = { } download: { /** - * E​v​e​r​y​ ​p​l​a​t​f​o​r​m​,​ ​e​v​e​r​y​ ​o​f​f​i​c​i​a​l​ ​s​o​u​r​c​e​. + * E​v​e​r​y​ ​p​l​a​t​f​o​r​m​,​ ​e​v​e​r​y​ ​s​o​u​r​c​e​. */ title: string /** * C​h​o​o​s​e​ ​t​h​e​ ​c​h​a​n​n​e​l​ ​t​h​a​t​ ​m​a​t​c​h​e​s​ ​y​o​u​r​ ​d​e​v​i​c​e​ ​a​n​d​ ​t​r​u​s​t​ ​m​o​d​e​l​.​ ​i​O​S​ ​a​n​d​ ​m​a​c​O​S​ ​u​s​e​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​;​ ​A​n​d​r​o​i​d​,​ ​L​i​n​u​x​,​ ​a​n​d​ ​W​i​n​d​o​w​s​ ​a​l​s​o​ ​h​a​v​e​ ​d​i​r​e​c​t​ ​p​a​c​k​a​g​e​ ​d​o​w​n​l​o​a​d​s​. */ subtitle: string + /** + * I​n​s​t​a​l​l​ ​c​o​m​m​a​n​d​ ​c​o​p​i​e​d + */ + copied: string platforms: { iosMacos: { /** @@ -188,7 +188,7 @@ type RootTranslation = { */ title: string /** - * U​s​e​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​ ​b​u​i​l​d​ ​f​o​r​ ​A​p​p​l​e​ ​p​l​a​t​f​o​r​m​s​,​ ​i​n​c​l​u​d​i​n​g​ ​a​u​t​o​m​a​t​i​c​ ​u​p​d​a​t​e​s​ ​t​h​r​o​u​g​h​ ​y​o​u​r​ ​A​p​p​l​e​ ​a​c​c​o​u​n​t​. + * U​s​e​ ​t​h​e​ ​A​p​p​ ​S​t​o​r​e​ ​b​u​i​l​d​ ​f​o​r​ ​A​p​p​l​e​ ​p​l​a​t​f​o​r​m​s​,​ ​o​r​ ​i​n​s​t​a​l​l​ ​t​h​e​ ​m​a​c​O​S​-​o​n​l​y​ ​H​o​m​e​b​r​e​w​ ​c​a​s​k​. */ description: string } @@ -229,93 +229,43 @@ type RootTranslation = { * A​p​p​ ​S​t​o​r​e */ name: string + } + homebrew: { /** - * A​p​p​l​e​ ​p​l​a​t​f​o​r​m​s + * H​o​m​e​b​r​e​w​ ​C​a​s​k */ - note: string + name: string } github: { /** * G​i​t​H​u​b​ ​R​e​l​e​a​s​e​s */ name: string - /** - * r​e​l​e​a​s​e​ ​b​u​i​l​d​s - */ - note: string } cdn: { /** * P​r​o​j​e​c​t​ ​C​D​N */ name: string - /** - * p​a​c​k​a​g​e​ ​m​i​r​r​o​r - */ - note: string } fdroid: { /** * F​-​D​r​o​i​d */ name: string - /** - * A​n​d​r​o​i​d​ ​r​e​p​o - */ - note: string } openapk: { /** * O​p​e​n​A​P​K */ name: string - /** - * A​n​d​r​o​i​d​ ​l​i​s​t​i​n​g - */ - note: string } } /** - * O​n​l​y​ ​d​o​w​n​l​o​a​d​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​a​ ​s​o​u​r​c​e​ ​y​o​u​ ​t​r​u​s​t​.​ ​F​o​r​ ​s​e​r​v​e​r​-​s​i​d​e​ ​p​u​s​h​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​c​o​m​p​a​n​i​o​n​ ​m​o​n​i​t​o​r​i​n​g​,​ ​i​n​s​t​a​l​l​ ​S​e​r​v​e​r​B​o​x​M​o​n​i​t​o​r​ ​s​e​p​a​r​a​t​e​l​y​ ​o​n​ ​y​o​u​r​ ​s​e​r​v​e​r​s​. + * H​o​m​e​b​r​e​w​ ​s​u​p​p​o​r​t​s​ ​m​a​c​O​S​ ​o​n​l​y​:​ ​b​r​e​w​ ​i​n​s​t​a​l​l​ ​-​-​c​a​s​k​ ​s​e​r​v​e​r​-​b​o​x​.​ ​O​n​l​y​ ​d​o​w​n​l​o​a​d​ ​p​a​c​k​a​g​e​s​ ​f​r​o​m​ ​a​ ​s​o​u​r​c​e​ ​y​o​u​ ​t​r​u​s​t​.​ ​F​o​r​ ​s​e​r​v​e​r​-​s​i​d​e​ ​p​u​s​h​,​ ​w​i​d​g​e​t​s​,​ ​a​n​d​ ​c​o​m​p​a​n​i​o​n​ ​m​o​n​i​t​o​r​i​n​g​,​ ​i​n​s​t​a​l​l​ ​S​e​r​v​e​r​B​o​x​M​o​n​i​t​o​r​ ​s​e​p​a​r​a​t​e​l​y​ ​o​n​ ​y​o​u​r​ ​s​e​r​v​e​r​s​. */ note: string } - testimonials: { - /** - * T​r​u​s​t​e​d​ ​b​y​ ​p​e​o​p​l​e​ ​w​h​o​ ​m​a​i​n​t​a​i​n​ ​r​e​a​l​ ​m​a​c​h​i​n​e​s​. - */ - title: string - admin: { - /** - * S​e​r​v​e​r​B​o​x​ ​k​e​e​p​s​ ​t​h​e​ ​q​u​i​c​k​ ​s​e​r​v​e​r​ ​c​h​e​c​k​s​ ​I​ ​d​o​ ​e​v​e​r​y​ ​d​a​y​ ​i​n​ ​o​n​e​ ​p​l​a​c​e​,​ ​w​i​t​h​o​u​t​ ​f​o​r​c​i​n​g​ ​m​e​ ​b​a​c​k​ ​t​o​ ​a​ ​l​a​p​t​o​p​. - */ - quote: string - /** - * S​e​r​v​e​r​ ​A​d​m​i​n​i​s​t​r​a​t​o​r - */ - role: string - } - infra: { - /** - * T​h​e​ ​j​u​m​p​ ​f​r​o​m​ ​m​e​t​r​i​c​s​ ​t​o​ ​S​S​H​ ​a​n​d​ ​S​F​T​P​ ​i​s​ ​d​i​r​e​c​t​.​ ​I​t​ ​r​e​m​o​v​e​s​ ​a​ ​l​o​t​ ​o​f​ ​s​m​a​l​l​ ​c​o​n​t​e​x​t​ ​s​w​i​t​c​h​e​s​ ​d​u​r​i​n​g​ ​i​n​c​i​d​e​n​t​s​. - */ - quote: string - /** - * I​n​f​r​a​s​t​r​u​c​t​u​r​e​ ​M​a​i​n​t​a​i​n​e​r - */ - role: string - } - student: { - /** - * I​t​ ​i​s​ ​l​i​g​h​t​w​e​i​g​h​t​ ​e​n​o​u​g​h​ ​f​o​r​ ​m​y​ ​p​e​r​s​o​n​a​l​ ​s​e​r​v​e​r​s​,​ ​b​u​t​ ​s​t​i​l​l​ ​c​o​v​e​r​s​ ​D​o​c​k​e​r​,​ ​s​y​s​t​e​m​d​,​ ​a​n​d​ ​h​e​a​l​t​h​ ​c​h​e​c​k​s​. - */ - quote: string - /** - * H​o​m​e​l​a​b​ ​U​s​e​r - */ - role: string - } - } cta: { /** * S​e​r​v​e​r​B​o​x​ ​i​s​ ​f​r​e​e​ ​a​n​d​ ​o​p​e​n​ ​s​o​u​r​c​e​ ​u​n​d​e​r​ ​A​G​P​L​v​3​. @@ -374,10 +324,6 @@ export type TranslationFunctions = { * Capabilities */ capabilities: () => LocalizedString - /** - * Testimonials - */ - testimonials: () => LocalizedString /** * Download */ @@ -511,13 +457,17 @@ export type TranslationFunctions = { } download: { /** - * Every platform, every official source. + * Every platform, every source. */ title: () => LocalizedString /** * Choose the channel that matches your device and trust model. iOS and macOS use the App Store; Android, Linux, and Windows also have direct package downloads. */ subtitle: () => LocalizedString + /** + * Install command copied + */ + copied: () => LocalizedString platforms: { iosMacos: { /** @@ -525,7 +475,7 @@ export type TranslationFunctions = { */ title: () => LocalizedString /** - * Use the App Store build for Apple platforms, including automatic updates through your Apple account. + * Use the App Store build for Apple platforms, or install the macOS-only Homebrew cask. */ description: () => LocalizedString } @@ -566,93 +516,43 @@ export type TranslationFunctions = { * App Store */ name: () => LocalizedString + } + homebrew: { /** - * Apple platforms + * Homebrew Cask */ - note: () => LocalizedString + name: () => LocalizedString } github: { /** * GitHub Releases */ name: () => LocalizedString - /** - * release builds - */ - note: () => LocalizedString } cdn: { /** * Project CDN */ name: () => LocalizedString - /** - * package mirror - */ - note: () => LocalizedString } fdroid: { /** * F-Droid */ name: () => LocalizedString - /** - * Android repo - */ - note: () => LocalizedString } openapk: { /** * OpenAPK */ name: () => LocalizedString - /** - * Android listing - */ - note: () => LocalizedString } } /** - * Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers. + * Homebrew supports macOS only: brew install --cask server-box. Only download packages from a source you trust. For server-side push, widgets, and companion monitoring, install ServerBoxMonitor separately on your servers. */ note: () => LocalizedString } - testimonials: { - /** - * Trusted by people who maintain real machines. - */ - title: () => LocalizedString - admin: { - /** - * ServerBox keeps the quick server checks I do every day in one place, without forcing me back to a laptop. - */ - quote: () => LocalizedString - /** - * Server Administrator - */ - role: () => LocalizedString - } - infra: { - /** - * The jump from metrics to SSH and SFTP is direct. It removes a lot of small context switches during incidents. - */ - quote: () => LocalizedString - /** - * Infrastructure Maintainer - */ - role: () => LocalizedString - } - student: { - /** - * It is lightweight enough for my personal servers, but still covers Docker, systemd, and health checks. - */ - quote: () => LocalizedString - /** - * Homelab User - */ - role: () => LocalizedString - } - } cta: { /** * ServerBox is free and open source under AGPLv3. diff --git a/website/src/i18n/zh-CN/index.ts b/website/src/i18n/zh-CN/index.ts index daa88eed4..4a8ed6e51 100644 --- a/website/src/i18n/zh-CN/index.ts +++ b/website/src/i18n/zh-CN/index.ts @@ -10,7 +10,6 @@ const zhCN: Translation = { nav: { features: '特性', capabilities: '能力', - testimonials: '用户评价', download: '下载', languageLabel: '语言', }, @@ -66,14 +65,15 @@ const zhCN: Translation = { installReleasePrompt: '# Android、Linux 与 Windows', }, download: { - title: '所有平台,所有官方来源。', + title: '所有平台,所有来源。', subtitle: '根据设备和信任模型选择下载渠道。iOS 与 macOS 使用 App Store;Android、Linux 和 Windows 也提供直接安装包。', + copied: '已复制安装命令', platforms: { iosMacos: { title: 'iOS / macOS', description: - 'Apple 平台使用 App Store 构建,并可通过 Apple 账号获得自动更新。', + 'Apple 平台可使用 App Store 构建,也可在 macOS 上使用 Homebrew cask 安装。', }, android: { title: 'Android', @@ -94,45 +94,25 @@ const zhCN: Translation = { sources: { appStore: { name: 'App Store', - note: 'Apple 平台', + }, + homebrew: { + name: 'Homebrew Cask', }, github: { name: 'GitHub Releases', - note: '发布构建', }, cdn: { name: '项目 CDN', - note: '安装包镜像', }, fdroid: { name: 'F-Droid', - note: 'Android 仓库', }, openapk: { name: 'OpenAPK', - note: 'Android 页面', }, }, note: - '请只从你信任的来源下载安装包。若需要服务器端推送、小组件和 companion 监控,请在服务器上单独安装 ServerBoxMonitor。', - }, - testimonials: { - title: '受到真实机器维护者信任。', - admin: { - quote: - 'ServerBox 把我每天要做的快速服务器检查放在一个地方,不必总是回到电脑前。', - role: '服务器管理员', - }, - infra: { - quote: - '从指标跳到 SSH 和 SFTP 很直接,事故处理中少了很多细碎的上下文切换。', - role: '基础设施维护者', - }, - student: { - quote: - '它对个人服务器足够轻量,但依然覆盖 Docker、systemd 和健康检查。', - role: 'Homelab 用户', - }, + 'Homebrew 仅支持 macOS:brew install --cask server-box。请只从你信任的来源下载安装包。若需要服务器端推送、小组件和独立监控,请在服务器上单独安装 ServerBoxMonitor。', }, cta: { title: 'ServerBox 是基于 AGPLv3 的免费开源软件。', From 7bf3894c1edd0cbde734af3813e18246b5ea3218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?lollipopkit=F0=9F=8F=B3=EF=B8=8F=E2=80=8D=E2=9A=A7?= =?UTF-8?q?=EF=B8=8F?= <10864310+lollipopkit@users.noreply.github.com> Date: Sat, 25 Apr 2026 04:10:08 +0800 Subject: [PATCH 03/11] opt. Co-authored-by: Copilot --- website/index.html | 2 +- website/public/app_icon.png | Bin 0 -> 15362 bytes website/public/favicon.svg | 13 --- website/src/App.svelte | 65 +++++++------ website/src/app.css | 18 ++-- website/src/i18n/en/index.ts | 44 +-------- website/src/i18n/i18n-types.ts | 164 +------------------------------- website/src/i18n/zh-CN/index.ts | 46 +-------- 8 files changed, 49 insertions(+), 303 deletions(-) create mode 100644 website/public/app_icon.png delete mode 100644 website/public/favicon.svg diff --git a/website/index.html b/website/index.html index e3ea29aba..d4e3de0f0 100644 --- a/website/index.html +++ b/website/index.html @@ -2,7 +2,7 @@ - + >#};y-Eo^6qN{4q)0DHQG`&W*MKyY z5(&MAj-dqz5NdK(*!y|Te#iIgJO9oYCu2A;YpuJiyIl9Y=A3zZUr(L!^tsaj05EE5 zs6GS$O7Opw038kZXWM7s0Q_^xP2-sd0MN6M|4{&`>0AK74``~YJoZUn%Q%(HKA2J} zbuVXBvP);T`?*xt^ZoK?J{TU ziFQ9@|DvaRslvS?1XI|j?`wWm9 z>m+^X%{_0eU_LoxzhlA>$1KpHlf=FHYT8>~lgm0V&2x$}_?r71@}^mPT#4 zT7Iif6E7|^bSYrCCk6&Qb3GLy!8h$(T!Ii8YcUUvl+K4;42AzBxTihJw?o8y=ye53 z`F1&oSEUWJ-l=?7Zrir^h)lix{73At#*#cRqOG^WrbX{J&qgJXZ;IgC4S;>|oPvCD ze8YJf=S9G_u~7;PWf@Jb`cC=h;`x;)Ep3lKkMJMm`23~Z=C^wlMWEGMx*J^AjQFt= zpde2Z5(76x_aD5h?u2^=QE7;*kD72hap#5cu$Gm0TfKyOc;Jom@E4Vr-3W(?Lj+A) zSph4UpOM0}?-Xqw{*to8#_6fg&K3$nJQHyEi@h&H2anLcZ2X&hO{7z(VOWI9eL;Z_ zD|{ZWCpz=+mzAwX@yS_17in7F!42Q9f6dEi4p5ehp}ZJ9(;7~SXZ&QYO)D;7RXKL5 z`}jHo)fWJ%_U2KHK9Z_fk|0aRd#S6?aRgg@@7gm z>*_%`+-p4^fk2eR^>za9XZNRmiqk2osymY2arT3VqQb;qWVnr4{;Iyts1i z#;TIQCvSNOQA>^wqS{e}f^=r%E*VtVw%bz{;fCUO`n(45Avk zHFzBxd{^~dWaiM4*3ye$jg5f7c+chw0v&271MOV=P34OtG}A%YVEX=0FF%!(!Zd~c z$=Q@1glzfb!S5eVKi3e?%3rAV7FEcNs0YB z6`NmiA^Cy(!Euk)aFH?$l5DV>ZSS%q2Hst_iNXkwYFS?&Rf2t_%V^{V8o9!P=^ta{ zx;Pr2h{)pq{7Byq6WLDO`?BLb&XPV{q@|?ocYAR8^!~FXCUKr>#v2)vQS75-pZau; zuoe<^fi=$j^PQnwiK3Ss5X?!vDBz=&{uWf5);PA#N&(e73mY_g)}j==S{80`%gd<_C>Q=4xz6v|B z$)z$%*;a9i+O6%Zti=9UO9*&P@0sL}3ccQl7&dVyaOH>R;U$#6`1s=R=#Zy{V}7qQ zJ}U7(oWTrVF48udg-R(eRi4E+TIO}_TC$S@6|9N&CXCIG)9}Z0x#WUin?6|O!gkiG z4Fse65GG5hnzZm8;bEiuJM&8M75OmA?D{TcJ=RI}&TN@AU75DgtWVU`{&G}KqM z=X!d>3?@}grHAU`g&?1#{2Zo&mLRu`DIvNad)SmXS;9UfQK~F!E*k@B2m)RA5n-50 z!}OG!1W7ZrowOiiv8h~4WwGp8`#??!@=EJ3ZUBmc=`uvXN>8^aOR`-Lin?9|zk^RO?ho=oc_;jkRv$tzQ4GaHy#wmJp6*+7k9>8va zmg-Z&nQuM`PNVt8&%!3g2;^qwN&?^^dra_i(MW2Zlt!Z48%-wZU*)3Cwi)G81P{a~ z&{+6tA3Y@!Y@yns3D(n3N3}V3UwXN+CMepnIKHO=9_s&f4yfL9wPi2aPTXR3a5!l_1$)QzdXI8 zpDdgIY6ITiL|Uk`R4t{an5)3SYB@@{FaEm*f>0^N$j~__$PMiBo)QT&T%b)GktGiw z)Pev7mrpS^iy6lc0~361koyhNP2;3p+rur2*(D~cN;IInta%4gig?nLZLl0D7-mYL zB>SEbFaU1U{^y=}hyhSV6G+eBAqFg?3bgJ1_dPcQ%3+|0*^hNv|F0Ul4I>T;tCxA? z5q?Eu@qm&KV#2X)9lUfIC*E8xx*)EsQ+zUkzkzO7y(zumBvqI7w4} zpZ`yHpY6{}0qp^$#uQ8HmtBRYhu+RKoH-?a`kyg%BMPbLQ`T^DVp*bF64ZYBp1psF zh`Pr)kK%4~IB7nChF^09_(0tNln%Rlq_u$)m$_rTPO|>?&+Y8w>~SE2!kmb!^ewWL ztDBGXm)#Ol)~P$`AR4#@%M9+Mkg2q}TYH3AX%n6$-I`9|_B~>bKUrZHr2q-~P2}kt zg)F_`N@Qj8n58CKM6aJ03M%Fwx^^<4KXg3z}^h6uVejoYANCEaI~0 z2#BGECA+N6|B*oNpXGlXfFZ#>v#A%78*U2UX}C1)BBKNED}uom)rCD4^X}dmzv+V> z4AaHm;@xT8PU~5UN$f?jT(Hp{e_#_9RJpwb{|kkmy2@jxva&mC&TSz$&2ga1>>G~E z=5c@8dxX=cr5&Qvsk6CpcxY{!(qn@W&B)4m-VxE=Ct;AaCx=C`UbV4Yyixu%>ZZa$ zl1+y6CI9W?eD$c3a2x*kpXV3tchWseU(cwH)0tpaQ`QiPGLUiO^2Lr1=_zOW78NbA zX8e*tz4s+v^kqjx5(n}iJ+J7)ztu7iWr$v`o4AsZ)tX;3KVDK*7Ie?npfwB@dHiNA zV-hm9==?ie8`gBcjGE|Xyx|&HyFtof9$TD#j76Q5)>?5!Q*c(zAvi~Drsa$WT&pDx zysECciLJZWVd_bXsjuk~?#OO)#B1pn_w>14I@l`;0{>sdhdGg`SHF%H^*lNl=bI&-xC`?T1ZRtSkoU$EJy)) z#sy+=Mr|;L$m5-IZ8Yf_9JAhOVHhske@$GfMeRXJS>k|=5v(Y^- ztTiZgNtb=fZ3V(BqY%`aJS&{r`9$QG!O}@-wVi-t1#Ezc zNW0Z0JI`c_lP$E>T4j~g*Ed+B=BM+~lAZutH=Qu>XS?q}H6T}R7WmZ9WH3A7;Em_> z1p%$J0Vv>EbZ#Rhy7)~d|7lX@EN0`*(ywlV+)nmuHjRoc9JtF6CrYz#y6x?YnKPt| z3RPNe89563d~r-5z`Uw}2uiA842~$IX{s$Rw9L%dL7uVLB*FThN(LGGIlZ?SnxC?Z z++A%XY(pHVw)wQ>8H%qpJ@gA$_!Qt3?Fk47{3@`~Sekh`>QRfdv{N^jn`>bwc1%|7 znNN*MO^cxCBG32F1&;Mj4^s_MB|IjKWgX(s*21(Xyy7*!U^x7y142nCBfQIUEaDAn zEVNPd%Xjl@#7Ra%1ym;JfuGZ-1P4V>09FQ{Y)kvQ1G%7(||_9*uENBU1SrThu=*X}Y9P zMcfG3@xWTBs=m`g4MWEVIJ+iI#|z_nJz zFGd*8s=k&uzOPko%XVQ$xjCx&bh$La5?dylP-3G=dqu#?r(>>`v}=40^Ta5Y^G_i0 zP57iDg}tAL^Xd)9vFK~2-_1W~F_xb4`M6E>eR*}|V&{h*8*|&R`RXkdFefX@EWz>J zeE!4nqQ`2HR7#FhStfdPaKp)su+MGgmUL)atoWcJXn-9I&F1zG%uDs@Z}T6!P~;8R zI+xxS<0*o}Rj@9uejv1ttF5en!l13Llo>lvsi}ZnXNVHNY;j$OGzG~zk`PE?#<&;% zEKBL>OPyZ^U^I+`b0uUBA>w3rR@b}BR22Jh=T6Dvl~i8plyX-0#bh+ zRUeJ;vFQDK*lab`q9?{jCGx61gD^1o>Qb^l$jMY=t48sEb3E8ms=jjtdM5fKFqngDW z94Q<0wPJGe@)mT-0b2#kkqa`PB7ZErbWP~Z={K-ZG_{@~y|5h)p3Cc`V7HL|)9QSI z&zd_#$zTvD`q^mi#}IA#`g?uCQ~A8Gv#ez%Yu&K$E>rY=|I9kcptc!T5WU{v-u^m8 zFt-zUV#OU&JsKKRi|wly_Bl5Vb@%%qk=D~RNi$%B&Az94`-_olzY)?{&UJUTYG!TK z!6!HLJ951Dpv2P{q7Cz=qr-cUw5v{y7}~JiczA%{Z8y~_*KIbPZHa=9#7L_n_!I+t zR;Rp|st!avyh!=G>u*T0#0D11lhe^mYMhcZPZ2>{a8EtPMH=hO?K`~nj1?3hh8c;( znQaX7iGCiZgsCCh!BrfSG0BKnmug05vES)%`t2&M;P3(4r@x9nB0W9Cd~+8e9=d(m z8obhOqXOrGOcIIGmv!GBa^p-~aDN;&wDIV(_#`-|7TKQRlnATFO7Wstv@QL|tenA12OMj(Sng#myGNo$KWvFjc zCc}$OVwCjca1_mE6UQq{!SmdT+``p6?=n49P7~dlY{t?%pYLqX@D2A><#+$hn~`KK2CiBc=AQkAN#=C zhs)gIbjTmHfJ1g=<<65#Tt-oVSHapM-#e9;Ds9PF`<-Bs10}Rk)^poKCm4O~Ju(ec zsj@!$#v&8#khrv>o*U0BaPM=Z?~0c3hS*ks($Wbr^cO(aH#T^o{9tTohKUo(ozgn&ArYQv@u@V|<6-Via5Wt-g7d31=t) z{4Ku$exXO_0N~51b3og35Qzg)Dv;o09eV)C7P13a;;%9SzyK8^V0AN(0timcrcl<& zmjrAN9=pVi5u}< zp3M-lQFKDaUU+~0mE1oqQ=y=oYv%)ged0kzsx+>wK#I@Od2L)48y$73kD3(caw3V2 z%1RG}-M9U_gTWT=m1+%5JQ~Jn`p+8fhTq(acNy7idc=VXFbjyK?c)tLO;KYGx|k4l zKm4B()3>}fqhspE1WYkZQ&g(~>j`N=hhiao!H+oBR?qO6?9ae1-l3D{H*^4st_~1|p17lV79?0+!GGJ!dTEKVP46Z}Kq} zegv&o1lvP1A`i1gio~>-*R#XS8|L!5qvVx1{@wC(m!rwcrq1t+U1>{T^5#FTPwf-$ zGld?{Sm)9jzKOqcHsHUn(k@aQsS+q(8tGl}+K;8F-mdfaBhIye#{t=a@>y!~nS1L| z@{RXt&Hp=8Wz^EJU$Z>=TEq1)&ra*l*L^oCG??z#%nS+YHEw<^8H0#-JKyHVk|!-_ zYnB$d+w)@Foc-tY3d29|!=zitduwaHT|T;+ZN|xDIjis|x|8}L-T&b#{QteAe+ht4 zf98;gw88I_aw>`6k_f~uV(L$u^8GhD9VfeI#mdL^I&8GPD$@P%ur&|Q2++OnTA2E@ zS~5Y~9MtzHwjyNTN5&khf#68kqZ`nM4TXU|FP_04j9@s9ghz zKpi))1BNnQc4-*8r2q~|e>;{JN|NX(qtFp(9>FhD*9#&t1*yxnj(yTfJCG%8 zDA4qfp1Kb*DZD!mopqJ5IMR=aM3U{#vgV*G#RtEH!Nf`3%TtC}Q1zYk5>l$;2SPCe zZCWt_E5w-4v=%nNVW?o-!2uw8OHIe@4nHmbx~GK|56KHun=@E@2h+as8lylnvNtd~ z2~n-C9Wdg|4dY-HzoLM76el}a)W=ueepnJBN#Q^7{FwQt_#n7&QlKMeNuk+b@-s+c zWQGP&g#&XYK6ZH2W-NRy52F_O-62s7w&I4XRTFP=B86~E)T$*-o$pqVz;`=Pg- zsayqirPPZ;tgtF=XW1BSt<59xlrhLw-Jo?r#eBV*DXQy@(t2W3!yq&3qjmgf0MQT| zFdq4=M88yGk0(T$Qo*kPyE!GOP}L^ksJnPY{%+Rq(ZRi>X`3TPv6`-9ke}cx95%d$ zsJCWGhr?&S4mzXvz0D_#DYTO|{0BThgzS^QEn(1(pQJ16Z=Q(Pa^GZY&0yS0Aup$X zvligoQ|KR^ypdCL4R3_q+zAI!&}<7F&Q)f#LITBj!hYvX&QdG+8-iPM#S1m%Snmo5U^;i5mb8#6J|NYIrSovu+X9?lFjE` zO+EV!Fnw2EFVQ(R3v(<@0zqTi&?HO!q4r@b;)s*^j;iW~$|1hE-qr<+$lYPYIAxI0 zw0HI0kJ}>xOB2T74IQTI)rx!;o3OFt?$NHMl;j?++aO0l%P;UL{i~@Qb5i7JHnWW5 z;1%PBtkWCzA)MOP)Y5i+x_<<60@Ye&R<9ouLW)R8eDL7W5uxYGLSxeX5N=0JqPrpzx| zUV%@gMy|2Fot0KYQpiDRiloFT7DGq2QgU{bufz{Z0mMzTHC;Lu%H((V1wFuR_6=!v%eh_CHIBHd z(mDr&!(si4ZA{pP^O9t&-z_JF+WwOC3wBOf!4{;N1dm7Wws7p!zUVy|_Vknr&^o}s zdA$X)Q)H@%yRz=7oLKm4?53h~+Zz!IItIAE zki7s7sm}_-#|21#D9#0EyU)Z~@rTeAkhy!nS>;UQmjJJO>gLT4Fz_t*2A;iGKC zXP|1=x;aE$HSKEOlXCfGX8h{Y*p;9L z>8YvJ&G&WE3pfvFJuK>qLhs8uO)Lb;ce%=}3|Pa2kCvfBZ`a@7!J;^?O1N@wcpGnH zGIr0Bnn2+?fqsBqH=XHgoS5YpZk`ick38jB)Q(pT&)mdLkpn&f_41~xu1t7MR75+s*x&zv_;VyZ|D9jN=#PT#me*s(cuJrL)DVY%ij z&%%hmn?(spD=A8HJ%2)v_E=kgH}a{GC9#NKg=~2cgb_U`(4BuD*|0}g&g8wa_uT-p z_4P44=lap%0n&g`y6#e@fhvpR&8+V{8^CV+n$IO(yC;8ah=oS;pq2(56;fW-3QpUv z@7&J?zsDC>=_;2W$%b&{+K(}^SRgM}QW%~Ko5ie%2tMbH=O(p&oNY%Amj~U<9f;M) z;`}eA-#iiem)JpI2Ir=As7s6a0O5#v5nFD^1;_rQApiufsaDpa-I4)}an6|J6(y~d z-33S+eUQ2QQ_0j1n@;CmVPnZ!7AQB!Q*j>ibW5>Lp1y^DxPM4ImfcTnnK<42Sc9w| zXfifeaG4$8R%$O`PII~2h( zB}eWasbo>6uIKH)Rk>**SMd=J#UtpE7ZnkzNVC4vfPUR*GVXe|S-K_J?jQN*mY_-! z$v@-#hQh{?-6gXDVXrn--cxP=RgW5|iio(Cn6$7)#v66kH9jgO17%8femg=T$*-Wm zcBK!55fCys(TeN<7rklg;Oa`Mao`=2zo`URN4-sAWc}C61Jwo5o?w54DW_<&ojY!9 zf-g6R;`<+xO6eGU8>mS}r+??B5dV)TIBm=qq4Gd%Sly^RpJk&S zNPpydj^AvctI}F6r%z<2f#dA|R_mrxvtI`bL(G7Yl~=w$pV8jfJpC^v&f;F^6x}UQ zN9%^RJ(J!1ZdSa$poU&zuo5|0d0LQcgfUwwU$gh&*<+TN)_E!?I?r8Q$UB!0O8?Pn z!PU3K%(h`Py-$IpsVT|njV3vUbOyL4@BK?Nt|z6B+_O!2HJ*bRk~9$P`CIYtpNebyl^7*eU&F;1WGCBw51=d}fHnC+N?xRW)= z4J^@_r-+E#Q3g$>O63HmFuHn{-;C${RYIFw_dNS&j7>kEP7--FRt#MRrL)S+%%nic z4TGw^7<$W2q*iy#!+f8NiJXw)J(-wOgrBskr%XYE3TTap1N{bI6ys1glA)8N;8vgS zwwswHz5L|xzy|%K&-|2Il)B{^37AfSR^Z)}sJHeK$)HH`Lw`P# z6yg~}8=cnwT(0iZRidq#T-%<=*4#Y??KEzs%O?>!4Mj>L3v}C|H^+@y8!%ZCORC#n z%qy$(x>IKvA$J?+m0n&OKe9U52_y-gNP6FTMq%%nD)_x5@Y>)33k!~Fc6FX1X_iM! z79q{?ztR;wl2g82thqP6nns%pN}P!tyb&?X;*=+b7tjrnI5ae5Fd842(H^OlTjhwf3}SF^M*2R;PYfvTxzUFKbz)EaQQg>g#u?KQRoUhB>v#0Ini)h*NXPI#^?qER2PK z79wZ8l}9HQB1tJJ9(#SFk`^tWz^8=FrzDhfv`|J2g+TZ?05?Rv+~-^7K{KoA9Zz_fb7A(GTzwFgs?gHc}01f7OUb@nF|d$KJ6Asok zsUOeeqS{8RU`tegd19Emq`X{ex>N5AJ*LOya; z{gtk?v@~;x_FAwNIg`ZmIL3<%VcQ{lI|7WfNHRpn=H z8OdQNnuF||odX-4sf0!>0&misg0Qj`XKa$5?K|8PP4Wjde$d_M78T5BWNu$+wCQK5 zTGT!1T?PA`q4Vd23kskCr3dT1z$*6SZv@7$daGCy7V)6*dkuewnWo3tw;dRL2+cx8Ae8pS8DR%0OJwwaED{cLaWoB|2y zbE6Hu_>`w?d1=zla@u|qpx@Pt5u~gEWu7}AnGKK`T<|k+0pzT~TOft_|89Y!LF))O zl}SSo>bTh)F~yT^Aq5xHZ-~w1md36V{Qnq$T3J!}4|9$pE$`2XY5jIu3t0 zebBIv@9FliNtHm!b)7LvjoR{3t3%Ea*SkGXSC5NUijh;>+uMH^3IX%Rj{PK)mka5& z9zCxldcV~CHduZofx6*=%5q#Zze)Oa=i>$qb`zYc0Y)%F!$F0`r^(DNatm+Cc?{WW zK>HO8$<2|40tx`oxRkjR_;`OgK)+Clj^p@vSuy_ejtMA&2n42s^ZGDEbBd2Sp-&F_D~;d;7Tx(&h=oAn-EfWzcRjeups3b!OZU3?7{0Q~`Gn{6U{O9YeL0 zOF?sc_V12os9}ANOW@NFLvV+DrR^kf>}=|p$$J_n2^)qR+#Q1>vm~bKAG&p4gnoxt z4B2qe#K2xW;0J)VRI9Q*E#p$@j%>SpvfhrrW(kg{NaoOI zZO*QWua&%4kB`BAA3=J0dR%qUj)Cg$oJ6i3%l;bdUq&_Dq5}Y{@U6i?wA#0l{l(Cf z+j(}2<}b@?XWnpk|9T?aGYQ2wOS?`TLF-71#H>RJyjZ6Bfqc*fG2%W+1yK@9%mgRI zOlaxQoLwy1f6y0G0rgCFEUEWKwl|qfVoZJ3D)Sr5bG9Y&I(N4ADu~Qj95^u9ua;KE z^XPNZ%1{Ty>mHq`pr>AaIMiUFK^h*mclVK$-HV>2ZLnb11XJ5HYludJ-fdWc57%&+(P`Pb&GS#czXNW}KF=80Ml zvGL9C~<`GcLbf6@OHY`-qYCKso*N6 zFs0>{H5#~kCAx3K-MYOq^0?le9};Xc;^``e-3(SaQ2_q`*hu{muP*faS4j1=Kz z)*$pX5(zV`gR-gaaI`pbmDL(;Opuw-z2d}SGC}$>X};3bq%mU z33BE@BRbJhGPg5g!O>$XaCcZnW62zN;KXkt!1K;9#k%7)5-9am)h?1bzG@)z<_ZTS znp?>%pvW*qnqYbn1b%I$6mF|Fyqnqy#o21^=Jk!`cLR!4WGjzXf5#*p>Y;z%7)+_J za+y3}#olJ%4`mE4v|@Q>7*dT1^1ql=A$Gi(H*3wPCG+7*^ynINLR9f-k&gVSg8~qD z!qeBrtC7D%-n?g0F6w^Km8AZ#`-7T-LfFDE}VJUCM>Z*ZBgiEO%8>H0m6$~oyB3_st4gA zoPHNP2<3Bozc3D7)^DvQU-{1}`xO}`g1CJr_X;wsoGZ zSTA@}8Z#bR$O(^OE8LR91~9N(-OWW_q6rCS_ZxkY^xEvly{suA#olZ$>(hrdrr!Gt zvvX}; z^(K)-Y>COmDv5}k7B0#13NBX=-c9{C-eMoMP}cjsY7;Mm!iI)aI$K9Rlm@j5D3Hih0_cjln6EY?| zPzqq3TVA!OgRA7#@rLmgHOT5G`W}pkf8>;`c2H!br!Z|j(=MdE&fbHzZvR!WL|8!Y zb1I)n~QnZClPbyO{a7-MBLiELXm&#oPM@zRcLGcOI{5)RiMq!mYz*`qQ4V zBy%lg!h$ZUt)+XQ*!dh0y|-;`E_N;-@e%KXt2xvAJH9Am@BHNqD1}vPBe;)4{iX6f z-DX=F9a^4%JM4~jITnj$<#VkpHmx6;PosZ-UU&);%U~Kk{_RCqbsk_vaQn?!D-@Y`MmRI<4Z%o1|JLOpz)udl!76pU>vY^D(x;+ z6yzx#K|WY@#(73k3?qy$gHg)MK=hO0(UQ(wJ=?=YiK-3#aWE|Qk$?We>NNUo!BpB! zuE^M$rFo>&!!}Ut+vpkrC9faQkSMouSS}#1xQ>bA68qOOOw%rL_)4$5PgT zC15H8&`~3H>J<=X5nq2)M#)YVnIp{_YWQ>rXLCj_e>5QQfCMoU77w1E%XFyWYV{|7 z(lh!$xBGeU6GkWSdk=Wo4ta`V6~XYH-Fx3RG3wihtSu@<(Cp8l#Mfh1ViUzixK;CklDVaBn5OzN8~~-&MG6;*^ef&Bp5;M=*^Db$ zcubbr)M4s2fe8TI;7f&4anya-Dn{>i*>{|DwclZzmy(iM1&!4@4X+WK*58Bg1^`-i z2$Fi;joszlX{Z5}OVj>hD#z(Mq<{SJ+43gRF*@xO2r0g41!^B0f*8{0Y~Iep&DoJo z^7;QRMWTn@z|7vIcIN|RO300bIzCIVoy54q3=Z&wK{8N)p}_MO7Cv+kSf!yLYQ#ihk=h>*9!~w9CzL=`QqN6U>qWa{>mrMk2)m|pSB6~08nb4nwlD2 zAu+hN)qeAZ!%iWYSkZ(b!D^2`kwK*qDw4A&!H)au(%-0pos8dnjAA~BrehyO#S#_4 zcl@<*-?*kYNi6e}L7`(FQUjc)h%0AKyOyf0R?n(Qe)MZRm z`#-Kr7Sc67%KEX0@6>|9zy_bpaZvji9jk#bQw_XPB|#F?fVqMRRczndKJ;J_d3H60IPC;T>o7X;IZPYN+UnFJLq4oo}vZ%#<8yX0_h3S7_q zyfaG{y0AF#d1cCK>**^`)NxE36@Gmjq*E@dXj(q-dIFgN@NSA1?fqD|6X?SDYJ2k} z9Uy1*S3$eR>#b*-cX%35<7GW=CE6bpd%>QUsX87jqK3Py?pK7R?m+9+V$hx7(DJj6 zeDaM$FY|6vngz~aVnKofI|q&$n5zhGypV|m+2a?SL6v^4Gq&%^O(RC`O)DuNN+PlH z)R|r}l8XVPLUiXTu%bjV;{zgZkirpmnwb;{Ou;C0*rmnGX!CJ`^HXQ(^Krw>IDp<| z*O@!?vcQ#tm~oixNASm$_y&ST3|Ke7Bs+f}%%o;)=BqT!SNE5h^b0$`itMiA>ar|t z_wmm0;x`R`1Rk-huYiMgF^rtH88<5>;g3nX0VOTHQ#{tIC2C7k7)JtWBFh~%G-(q{ iBxmFQ*L~hDksx7{=^qUc(_r=j0Geugs>OFKLjM;;nh$0G literal 0 HcmV?d00001 diff --git a/website/public/favicon.svg b/website/public/favicon.svg deleted file mode 100644 index 69faf4108..000000000 --- a/website/public/favicon.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/website/src/App.svelte b/website/src/App.svelte index 2fd0bd9b1..e961fc6d4 100644 --- a/website/src/App.svelte +++ b/website/src/App.svelte @@ -1,11 +1,6 @@ -{#if locale && isMounted} +{#if locale}