From 0333b3b389948ec2f5e4c9360cedbb01e5a156bb Mon Sep 17 00:00:00 2001 From: zipg Date: Wed, 2 Sep 2026 17:34:23 +0800 Subject: [PATCH] docs: improve legacy windows and linux install guidance --- .github/workflows/ci.yml | 2 +- .../windowsInstallerTemplate.spec.ts | 19 ++ crates/dbx-core/src/agent_offline_export.rs | 2 +- crates/dbx-core/src/schema.rs | 7 +- .../dbx-core/src/sql_dialect/table_select.rs | 6 +- crates/dbx-mcp/src/runtime.rs | 2 +- crates/dbx-mcp/tests/protocol.rs | 2 +- docs/app/global.css | 18 ++ docs/components/landing/InstallTabs.tsx | 225 +++++++++++++++++- docs/lib/downloadLinks.test.ts | 60 ++++- docs/lib/downloadLinks.ts | 50 +++- docs/public/icons/platform/windows-legacy.png | Bin 0 -> 24727 bytes src-tauri/windows/nsis/installer.nsi | 27 +++ 13 files changed, 382 insertions(+), 38 deletions(-) create mode 100644 docs/public/icons/platform/windows-legacy.png diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index cc841fb638..9a6c8434e4 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -200,7 +200,7 @@ jobs: needs: changes if: needs.changes.outputs.windows_win7_bundle == 'true' runs-on: windows-2022 - timeout-minutes: 90 + timeout-minutes: 120 env: CARGO_INCREMENTAL: "0" RUSTFLAGS: -C debuginfo=line-tables-only -C target-feature=+crt-static diff --git a/apps/desktop/src/lib/__tests__/windowsInstallerTemplate.spec.ts b/apps/desktop/src/lib/__tests__/windowsInstallerTemplate.spec.ts index 4ee5752f00..4fc834d2c3 100644 --- a/apps/desktop/src/lib/__tests__/windowsInstallerTemplate.spec.ts +++ b/apps/desktop/src/lib/__tests__/windowsInstallerTemplate.spec.ts @@ -17,6 +17,25 @@ const ciWorkflow = readFileSync(resolve(process.cwd(), ".github/workflows/ci.yml const releaseWorkflow = readFileSync(resolve(process.cwd(), ".github/workflows/release.yml"), "utf8"); describe("Windows offline installer template", () => { + it("routes supported legacy Windows users from generic installers to the fixed-runtime package", () => { + expect(template).toContain("ManifestSupportedOS all"); + expect(template).toContain("!include WinVer.nsh"); + expect(template).toContain('!if "${INSTALLWEBVIEW2MODE}" != "fixedRuntime"'); + expect(template).toContain("${If} ${IsWin7}"); + expect(template).toContain("${OrIf} ${IsWin2012R2}"); + expect(template).toContain('MessageBox MB_ICONSTOP|MB_YESNO|MB_DEFBUTTON1 "$(dbxWin7InstallerRequired)" IDYES dbx_open_win7_installer'); + expect(template).toContain("https://dl.dbxio.com/releases/v${VERSION}/DBX_${VERSION}_x64-win7-server2012r2-webview2-109-offline-setup.exe?v=${VERSION}"); + expect(template).toContain("SetErrorLevel 1633"); + expect(template).toContain("${OrIf} $PassiveMode = 1"); + }); + + it("localizes the Windows 7 package guidance in every installer language", () => { + expect(template.match(/LangString dbxWin7InstallerRequired/g)).toHaveLength(3); + expect(template).toContain("This installer does not support Windows 7 or Windows Server 2012 R2."); + expect(template).toContain("此安装包不支持 Windows 7 或 Windows Server 2012 R2。"); + expect(template).toContain("此安裝套件不支援 Windows 7 或 Windows Server 2012 R2。"); + }); + it.each([ { name: "continues after installer failure when a compatible Runtime remains", diff --git a/crates/dbx-core/src/agent_offline_export.rs b/crates/dbx-core/src/agent_offline_export.rs index 001fb7fd71..5c234a5de5 100644 --- a/crates/dbx-core/src/agent_offline_export.rs +++ b/crates/dbx-core/src/agent_offline_export.rs @@ -417,7 +417,7 @@ fn export_agents_offline_unlocked( let plan = inspect_offline_package(package)?; let expected = drivers.iter().map(|driver| driver.db_type.clone()).collect::>(); let actual = plan.driver_keys.into_iter().collect::>(); - if expected != actual || plan.includes_jre != !jres.is_empty() { + if expected != actual || plan.includes_jre == jres.is_empty() { return Err("Generated offline Agent package failed compatibility validation".to_string()); } Ok(()) diff --git a/crates/dbx-core/src/schema.rs b/crates/dbx-core/src/schema.rs index bc28ee1a77..9a9c2d143f 100644 --- a/crates/dbx-core/src/schema.rs +++ b/crates/dbx-core/src/schema.rs @@ -5840,7 +5840,7 @@ async fn list_objects_once( let db_config = connection_config(state, connection_id).await; let force_local_table_name_filter = table_name_filter.is_some_and(|filter| !filter.is_empty()); let (mysql_limit, mysql_offset) = if filter.is_none_or(|value| value.trim().is_empty()) - && !table_name_filter.is_some_and(|filter| !filter.is_empty()) + && table_name_filter.is_none_or(|filter| filter.is_empty()) { (limit, offset) } else { @@ -9429,10 +9429,9 @@ fn postgres_missing_prokind_error(err: &str) -> bool { // PostgreSQL localizes the undefined-column message (for example, Chinese // servers report "字段 p.prokind 不存在"). Keep the column context so an // unrelated relation named `prokind` cannot trigger this compatibility path. - let undefined_column = lower.contains("sqlstate 42703") + lower.contains("sqlstate 42703") || (lower.contains("does not exist") && lower.contains("column")) - || (err.contains("不存在") && (err.contains("字段") || err.contains("列 p.prokind"))); - undefined_column + || (err.contains("不存在") && (err.contains("字段") || err.contains("列 p.prokind"))) } fn opengauss_sequence_cache_metadata_error(err: &str) -> bool { diff --git a/crates/dbx-core/src/sql_dialect/table_select.rs b/crates/dbx-core/src/sql_dialect/table_select.rs index d22fe83192..1248aa033f 100644 --- a/crates/dbx-core/src/sql_dialect/table_select.rs +++ b/crates/dbx-core/src/sql_dialect/table_select.rs @@ -209,9 +209,9 @@ pub fn build_table_data_select_sql_with_database( let table = if let Some(database) = jdbc_tdengine_database { qualified_table_name(Some(DatabaseType::Tdengine), Some(database), &options.table_name) // Doris / StarRocks multi-catalog: prefix the catalog for external-catalog tables. - } else if database_type == Some(DatabaseType::Iris) { - table_data_qualified_table_name(database_type, schema, &options.table_name, options.identifier_quote.as_deref()) - } else if uses_connection_identifier_quote(database_type, options.identifier_quote.as_deref()) { + } else if database_type == Some(DatabaseType::Iris) + || uses_connection_identifier_quote(database_type, options.identifier_quote.as_deref()) + { table_data_qualified_table_name(database_type, schema, &options.table_name, options.identifier_quote.as_deref()) } else if include_database_name { database_qualified_table_name( diff --git a/crates/dbx-mcp/src/runtime.rs b/crates/dbx-mcp/src/runtime.rs index 387ebf44ee..27edd94d53 100644 --- a/crates/dbx-mcp/src/runtime.rs +++ b/crates/dbx-mcp/src/runtime.rs @@ -93,7 +93,7 @@ impl RuntimeConfig { validate_path(&path)?; let is_loopback = ip.is_loopback(); - if !is_loopback && !(allow_remote && explicit_allow_remote) { + if !(is_loopback || (allow_remote && explicit_allow_remote)) { return Err( "non-loopback HTTP binding requires both DBX_MCP_HTTP_ALLOW_REMOTE=1 and --http-allow-remote".into() ); diff --git a/crates/dbx-mcp/tests/protocol.rs b/crates/dbx-mcp/tests/protocol.rs index 362cd809f0..39b789a32d 100644 --- a/crates/dbx-mcp/tests/protocol.rs +++ b/crates/dbx-mcp/tests/protocol.rs @@ -224,7 +224,7 @@ async fn execute_query_injects_and_omits_timeout_secs_from_policy() { let captured = captured_query_arguments(backend, json!({ "connection_id": "scoped", "sql": "SELECT 1" })).await; assert_eq!(captured.len(), 1, "expected one captured execute_query call"); assert!( - !captured[0].get("timeout_secs").is_some(), + captured[0].get("timeout_secs").is_none(), "no timeout_secs must be injected when the policy inherits the connection: {}", captured[0] ); diff --git a/docs/app/global.css b/docs/app/global.css index b765007d1e..f241550321 100644 --- a/docs/app/global.css +++ b/docs/app/global.css @@ -755,6 +755,24 @@ a { color: var(--color-landing-ink); } +.landing-install-download-link:focus-visible { + border-radius: 8px; + outline: 2px solid rgba(255, 255, 255, 0.72); + outline-offset: -3px; +} + +.landing-install-driver-link { + color: #69c9ff; + text-decoration-color: rgba(105, 201, 255, 0.78); + transition: color 0.16s ease; +} + +.landing-install-driver-link:hover, +.landing-install-driver-link:focus-visible { + color: #a7e3ff; + text-decoration-color: currentColor; +} + .landing-install-option svg { fill: currentColor; stroke-width: 1.8; diff --git a/docs/components/landing/InstallTabs.tsx b/docs/components/landing/InstallTabs.tsx index 7ab6e27a47..6b12a94b42 100644 --- a/docs/components/landing/InstallTabs.tsx +++ b/docs/components/landing/InstallTabs.tsx @@ -1,8 +1,9 @@ "use client"; -import { ChevronDown, Download, Server } from "lucide-react"; -import { useEffect, useMemo, useState } from "react"; -import { createInstallOptions } from "@/lib/downloadLinks"; +import { Check, ChevronDown, ChevronRight, Copy, Download, Server, X } from "lucide-react"; +import { useCallback, useEffect, useMemo, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { createInstallOptions, type InstallOption } from "@/lib/downloadLinks"; import { detectPlatformId, type DownloadPlatformId } from "@/lib/platformDetection"; type InstallTabsProps = { @@ -14,8 +15,45 @@ const downloadLabel = { en: "Download DBX", cn: "下载 DBX" }; const selectDownloadLabel = { en: "Choose a version", cn: "选择下载版本" }; const selectMacLabel = { en: "Choose a macOS version", cn: "选择 macOS 版本" }; const offlineHint = { - en: "Offline network or Windows 7? Choose an offline installer", - cn: "无法联网或使用 Windows 7?选择专用离线安装包", + en: "Installing offline or using legacy Windows? View the matching installer", + cn: "需要离线安装或使用旧版 Windows?查看对应安装包", +}; + +const browserStaticText = { + en: { + eyebrow: "Linux browser package", + intro: "For Kylin, UnionTech UOS, and other Linux distributions. DBX runs locally and opens in your browser.", + extract: "Extract and enter the package directory", + start: "Start DBX", + open: "Open in your local browser", + port: "Need a different port?", + portHint: "Replace 8080 with any available local port.", + browserStyle: "Page layout looks incorrect?", + browserStyleHint: [ + "Some browsers in intranet or enterprise Linux environments use older browser engines that may not fully support the modern web standards used by DBX, resulting in missing or incorrect styles.", + "For example, older QiAnXin browser versions based on Chromium 90 can show this behavior.", + "Update the browser to its latest version, or use a current version of Chrome, Edge, or Firefox.", + ], + download: "Download", + close: "Close installation guide", + copy: "Copy command", + copied: "Copied", + }, + cn: { + eyebrow: "Linux 浏览器版", + intro: "适用于麒麟、统信 UOS 等 Linux 发行版。DBX 在本机运行,通过浏览器访问。", + extract: "解压并进入安装目录", + start: "启动 DBX", + open: "在本机浏览器打开", + port: "需要修改默认端口?", + portHint: "将 8080 替换为其他可用端口即可。", + browserStyle: "网页样式显示异常?", + browserStyleHint: ["部分信创或内网环境使用的浏览器内核版本较旧,可能无法完整支持 DBX 使用的现代 Web 标准,从而出现布局错位、样式缺失等问题。", "例如,基于 Chromium 90 内核的旧版奇安信浏览器可能出现此类情况。", "请优先升级当前浏览器;环境允许时,也可以改用新版 Chrome、Edge 或 Firefox。"], + download: "下载", + close: "关闭安装说明", + copy: "复制命令", + copied: "已复制", + }, }; const platformIconPaths = { @@ -26,6 +64,7 @@ const platformIconPaths = { "macos-intel": "/icons/platform/macos.png", "macos-unknown": "/icons/platform/macos.png", windows: "/icons/platform/windows.png", + "windows-legacy": "/icons/platform/windows-legacy.png", }, light: { "linux-arm": "/icons/platform/linux.svg", @@ -34,6 +73,7 @@ const platformIconPaths = { "macos-intel": "/icons/platform/macos-white.png", "macos-unknown": "/icons/platform/macos-white.png", windows: "/icons/platform/windows.png", + "windows-legacy": "/icons/platform/windows-legacy.png", }, }; @@ -43,10 +83,141 @@ function PlatformIcon({ id, size, variant }: { id: string; size: number; variant return ; } +function BrowserStaticDialog({ lang, option, version, onClose }: { lang: "en" | "cn"; option: InstallOption; version: string; onClose: () => void }) { + const closeButtonRef = useRef(null); + const [copiedCommand, setCopiedCommand] = useState(null); + const text = browserStaticText[lang]; + const archiveName = `DBX_${version}_*-browser-static.tar.gz`; + const packageDirectory = "dbx-linux-*-browser-static"; + const extractCommand = `tar -xzf ${archiveName}\ncd ${packageDirectory}`; + const downloads = option.browserStaticDownloads ?? [{ arch: "x64" as const, href: option.href }]; + + useEffect(() => { + const previousFocus = document.activeElement instanceof HTMLElement ? document.activeElement : null; + const previousOverflow = document.body.style.overflow; + document.body.style.overflow = "hidden"; + closeButtonRef.current?.focus(); + + const closeOnEscape = (event: KeyboardEvent) => { + if (event.key === "Escape") onClose(); + }; + window.addEventListener("keydown", closeOnEscape); + + return () => { + window.removeEventListener("keydown", closeOnEscape); + document.body.style.overflow = previousOverflow; + previousFocus?.focus(); + }; + }, [onClose]); + + const copyCommand = (command: string) => { + void navigator.clipboard + .writeText(command) + .then(() => { + setCopiedCommand(command); + window.setTimeout(() => setCopiedCommand((current) => (current === command ? null : current)), 1600); + }) + .catch(() => undefined); + }; + + const commandBlock = (command: string) => ( +
+ {command} + +
+ ); + + if (typeof document === "undefined") return null; + + return createPortal( +
{ + if (event.target === event.currentTarget) onClose(); + }} + > +
+
+
+ {text.eyebrow} +

+ {option.label} +

+

{text.intro}

+
+ +
+ +
+
+

1. {text.extract}

+ {commandBlock(extractCommand)} +
+
+

2. {text.start}

+ {commandBlock("./dbx")} +
+
+

3. {text.open}

+ + http://127.0.0.1:4224 + +
+
+ {text.port} +
+ {commandBlock("DBX_PORT=8080 ./dbx")} +

{text.portHint}

+
+
+
+ {text.browserStyle} +
+ {text.browserStyleHint.map((paragraph) => ( +

{paragraph}

+ ))} +
+
+
+ + +
+
, + document.body, + ); +} + export function InstallTabs({ lang, version }: InstallTabsProps) { const options = useMemo(() => createInstallOptions(lang, version), [lang, version]); const [open, setOpen] = useState(false); const [platformId, setPlatformId] = useState("unknown"); + const [browserStaticOption, setBrowserStaticOption] = useState(null); + const closeBrowserStaticDialog = useCallback(() => setBrowserStaticOption(null), []); useEffect(() => { let cancelled = false; @@ -129,23 +300,50 @@ export function InstallTabs({ lang, version }: InstallTabsProps) { @@ -159,6 +357,7 @@ export function InstallTabs({ lang, version }: InstallTabsProps) { {offlineHint[lang]} ) : null} + {browserStaticOption ? : null} ); } diff --git a/docs/lib/downloadLinks.test.ts b/docs/lib/downloadLinks.test.ts index 1ce177ca3a..b2745c2c56 100644 --- a/docs/lib/downloadLinks.test.ts +++ b/docs/lib/downloadLinks.test.ts @@ -4,29 +4,47 @@ import { test } from "vitest"; import { createInstallOptions } from "./downloadLinks"; test("Windows downloads include standard, offline, and Windows 7 installers", () => { - const windowsOptions = createInstallOptions("cn", "0.5.82").filter((option) => option.iconId === "windows"); + const windowsOptions = createInstallOptions("cn", "0.5.82").filter((option) => option.id.startsWith("windows")); assert.deepEqual( - windowsOptions.map(({ id, label, description, badge, href }) => ({ id, label, description, badge, href })), + windowsOptions.map(({ id, iconId, label, description, driverLinkLabel, descriptionSuffix, badge, href }) => ({ + id, + iconId, + label, + description, + driverLinkLabel, + descriptionSuffix, + badge, + href, + })), [ { id: "windows", + iconId: "windows", label: "Windows 10/11 (x64)", description: "标准在线安装包", + driverLinkLabel: undefined, + descriptionSuffix: undefined, badge: "推荐", href: "https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_x64-setup.exe?v=0.5.82", }, { id: "windows-offline", - label: "Windows 完整离线安装包", - description: "内置 WebView2 · 适用于内网部署或运行库缺失", - badge: "离线", + iconId: "windows", + label: "Windows10内网环境安装包", + description: "内置 WebView2 离线运行库 · 不含", + driverLinkLabel: "数据库离线驱动", + descriptionSuffix: undefined, + badge: "内网", href: "https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_x64-webview2-offline-setup.exe?v=0.5.82", }, { id: "windows-7-offline", - label: "Windows 7 / Server 2012 R2 离线安装包", - description: "内置 WebView2 109 · 仅支持 x64", + iconId: "windows-legacy", + label: "Windows 7 / Server\u00a02012\u00a0R2 专用包", + description: "内置 WebView2 离线运行库 · 不含", + driverLinkLabel: "数据库离线驱动", + descriptionSuffix: undefined, badge: "旧系统", href: "https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_x64-win7-server2012r2-webview2-109-offline-setup.exe?v=0.5.82", }, @@ -37,7 +55,33 @@ test("Windows downloads include standard, offline, and Windows 7 installers", () test("all downloads use immutable versioned release paths", () => { const options = createInstallOptions("en", "0.5.82"); - assert.equal(options.length, 7); + assert.equal(options.length, 8); assert.ok(options.every((option) => option.href.startsWith("https://dl.dbxio.com/releases/v0.5.82/DBX_0.5.82_"))); assert.ok(options.every((option) => !option.href.includes("/releases/latest/"))); }); + +test("browser static package opens one guide with both architecture downloads", () => { + const options = createInstallOptions("cn", "0.6.0").filter((option) => option.action === "instructions"); + + assert.deepEqual( + options.map(({ id, iconId, label, href, browserStaticDownloads }) => ({ id, iconId, label, href, browserStaticDownloads })), + [ + { + id: "linux-browser", + iconId: "linux", + label: "Linux 浏览器版", + href: "https://dl.dbxio.com/releases/v0.6.0/DBX_0.6.0_x64-browser-static.tar.gz?v=0.6.0", + browserStaticDownloads: [ + { + arch: "x64", + href: "https://dl.dbxio.com/releases/v0.6.0/DBX_0.6.0_x64-browser-static.tar.gz?v=0.6.0", + }, + { + arch: "arm64", + href: "https://dl.dbxio.com/releases/v0.6.0/DBX_0.6.0_arm64-browser-static.tar.gz?v=0.6.0", + }, + ], + }, + ], + ); +}); diff --git a/docs/lib/downloadLinks.ts b/docs/lib/downloadLinks.ts index 9259adec2c..8f1c4db410 100644 --- a/docs/lib/downloadLinks.ts +++ b/docs/lib/downloadLinks.ts @@ -1,12 +1,22 @@ export type InstallLang = "en" | "cn"; +export type BrowserStaticArch = "x64" | "arm64"; + +export type BrowserStaticDownload = { + arch: BrowserStaticArch; + href: string; +}; export type InstallOption = { id: string; iconId: string; label: string; description?: string; + driverLinkLabel?: string; + descriptionSuffix?: string; badge?: string; href: string; + action?: "download" | "instructions"; + browserStaticDownloads?: BrowserStaticDownload[]; }; type DownloadArtifact = { @@ -14,8 +24,12 @@ type DownloadArtifact = { iconId: string; labels: Record; descriptions?: Record; + driverLinkLabels?: Record; + descriptionSuffixes?: Record; badges?: Record; suffix: string; + action?: "download" | "instructions"; + browserStaticSuffixes?: Record; }; const DOWNLOAD_BASE_URL = "https://dl.dbxio.com/releases"; @@ -44,16 +58,18 @@ const downloadArtifacts: DownloadArtifact[] = [ { id: "windows-offline", iconId: "windows", - labels: { en: "Windows complete offline installer", cn: "Windows 完整离线安装包" }, - descriptions: { en: "Includes WebView2 · For offline deployment or missing runtime", cn: "内置 WebView2 · 适用于内网部署或运行库缺失" }, - badges: { en: "Offline", cn: "离线" }, + labels: { en: "Windows 10 intranet environment installer", cn: "Windows10内网环境安装包" }, + descriptions: { en: "Includes WebView2 offline runtime · Does not include", cn: "内置 WebView2 离线运行库 · 不含" }, + driverLinkLabels: { en: "offline database drivers", cn: "数据库离线驱动" }, + badges: { en: "Intranet", cn: "内网" }, suffix: "x64-webview2-offline-setup.exe", }, { id: "windows-7-offline", - iconId: "windows", - labels: { en: "Windows 7 / Server 2012 R2 offline installer", cn: "Windows 7 / Server 2012 R2 离线安装包" }, - descriptions: { en: "Includes WebView2 109 · x64 only", cn: "内置 WebView2 109 · 仅支持 x64" }, + iconId: "windows-legacy", + labels: { en: "Windows 7 / Server\u00a02012\u00a0R2 package", cn: "Windows 7 / Server\u00a02012\u00a0R2 专用包" }, + descriptions: { en: "Includes WebView2 offline runtime · Does not include", cn: "内置 WebView2 离线运行库 · 不含" }, + driverLinkLabels: { en: "offline database drivers", cn: "数据库离线驱动" }, badges: { en: "Legacy", cn: "旧系统" }, suffix: "x64-win7-server2012r2-webview2-109-offline-setup.exe", }, @@ -69,6 +85,19 @@ const downloadArtifacts: DownloadArtifact[] = [ labels: { en: "For Linux ARM64", cn: "适用于 Linux ARM64" }, suffix: "aarch64.AppImage", }, + { + id: "linux-browser", + iconId: "linux", + labels: { en: "Linux browser package", cn: "Linux 浏览器版" }, + descriptions: { en: "For Kylin, UnionTech UOS, and other Linux distributions", cn: "适用于麒麟、统信 UOS 等 Linux 发行版" }, + badges: { en: "Guide", cn: "安装说明" }, + suffix: "x64-browser-static.tar.gz", + action: "instructions", + browserStaticSuffixes: { + x64: "x64-browser-static.tar.gz", + arm64: "arm64-browser-static.tar.gz", + }, + }, ]; export function createInstallOptions(lang: InstallLang, version: string): InstallOption[] { @@ -77,7 +106,16 @@ export function createInstallOptions(lang: InstallLang, version: string): Instal iconId: artifact.iconId, label: artifact.labels[lang], description: artifact.descriptions?.[lang], + driverLinkLabel: artifact.driverLinkLabels?.[lang], + descriptionSuffix: artifact.descriptionSuffixes?.[lang], badge: artifact.badges?.[lang], href: `${DOWNLOAD_BASE_URL}/v${version}/DBX_${version}_${artifact.suffix}?v=${version}`, + action: artifact.action ?? "download", + browserStaticDownloads: artifact.browserStaticSuffixes + ? Object.entries(artifact.browserStaticSuffixes).map(([arch, suffix]) => ({ + arch: arch as BrowserStaticArch, + href: `${DOWNLOAD_BASE_URL}/v${version}/DBX_${version}_${suffix}?v=${version}`, + })) + : undefined, })); } diff --git a/docs/public/icons/platform/windows-legacy.png b/docs/public/icons/platform/windows-legacy.png new file mode 100644 index 0000000000000000000000000000000000000000..5487903a347d0ff125bf7069e98bddba9f9e3e25 GIT binary patch literal 24727 zcmYg$bx<5W)a}CJ?(R_B-4`pRr9h#$yA&_3i@UqKyHniV-KDs@+rIwZd*95LnVWlV z&L2r;GLf4T`a}K;DiRSA002P!DkG)z&-?sm2yp-EK2d|Qe-6=3M#JHs|F1v+0^IQb z3$#ttznXsi$_POC7XSek!KnW@0D$wK0ia<1FZTHl{xAF=1q49l{ImakvCn_Z z|H9h;;#{-jvwsfCLFtPG)XE4m_+LO_B%>q;0Jzis8({zd@ba(8=Li6BVg~?D^Z@|D z6aWCyDpXLXLQcG(qVjMydCsqsrq=@AM%X4Ir})Fn9oQ5k(+ACnKVIQ zw0Ujy->Q|d9&1j?6&dCex>&el_2yC}-w@y!37|0)^@2VrQjEI+gO}@yS%yCL4=>;M z<9|AV4PXDpN{ZtQfNTw|utq!2yBIJ&`T*UL#zb&vy&;6LK?XD^VyjwpcB}0VZe1Sl z&%}8akThadmQ+3DH;0qH_?D7IAGf}I4Am1cPjs1WAgaUz0~I}){Vp+_{v6Diy1p@| zKJLx-UPU@z`#sXIi_>?JKOPXH0YSH&&O>MLWo`%C1xsRH%w=?sPd7as5+oh!?-nUw zeD_Z_5hW!hm+0S4q2dmG@Ksu5IVYUgSmD@V#*!HUKJQ@IAMMhf1M)YVw__x(1_ewb z>+k#`HPM)VFX9$s~pQn;ECJ>fQr-QT)W< z-R$nk;Cc_O?ZvUBs;&yJzv+C;XRRCr zf69ygf;w3+7=< zB6v!_Vq>%O3gAVaulMx$XdR4qoiwXu{a(WC_?VPBd3(-88kUX$ozE5iZx?Xk7P^r0!z~GiLY0Q|4^7^N-rPP26sJ?6mjHrveydw{APk^xkf#-mXH0 z;0Fh(1_fzftLR^N3jCMI0nCX;W-Kpfg@^A3kBf)4Yq}&pHDuEC4R+faAy4`tHIaq2 zwW+Q`-`qOcS(9|5(!?lG!*L(O9~Fs!pH?vM)BY$v2YlzFfKJCJLLndv@Xbd5yyAw8 z*9GotanW`;2wVGjih|t6k_*>52tg30l)X2^u3Djeh76oLhby9-k5GEY(8&oXQ(`@F z2;dM)*-2za3Zd+&_UwoNXHFM?1^*aHy0=Wn`Q2cPg>3Cz77{TZd&O?C)XU=1N|}WC z`-B|=dpY5oWH-B^TEWIK2g%i)H?QEkMdYrR@CoqqbsPE1{7kIHX#Ij7o$u4Xp50JP zk4ak`kMmUO^UdABGPNr)NSP*<4DQKrdqMKTx+2kLqVTo$_h#btPz4{qcn~I{1?Vlb zDe|Bt6U5$9Y~l4OnP^IC9k^D!94Q>u0uQ}!PlV)tweO>S-TN&^@O}fUL%A zkbJ(qd?A0P36v-n6$;%qkq2i~1naw0efIg=_1)M9yLlJ{vCt*zd%-S--sqm00F)R2 zMotb^$QuW;67zLUt&oT`oc9A@Tx55a%haWz*$zPSZMW;={2mrCR@_qFp<(JdWqb zGtrcKx$oD*iEJZbF5%~TGjvq6D+g&zBqxnkV-09JU=ar58-9C-4Pctv?Qd!L?`O^T zc0+<%lqf?3E8MZUEY@T4ue3k(w;A-_L<_{JG8Ims6O4g^xDik}+!ALW7IM>P-O518 zI3H#v|F`g1-;E~cWoMe@H7*xl#dM;`B3YAM@hA1q^kEtj@hK?qkbR`kYGQ!v8y#k( zs&j+R>W-Vpt~f#zmesL(v>YK^PAH-hAXJ=69McTS!ru}g7DtF+pRx!bm{{)Ht0f^< z{wraO5vP*p{0hR3FkJKt8$cCo_6&LMDI-yHwwc_nv9azRf1{;WW@ zHU*j@=+U>rcvpk!J%#h}5eo7($n~9MkxTA2T)~Tm%=Z0Eu$ACCCvI4I;&-bKd4BP# z1G_l-1;r>KMb*{ZaJ8D%$p!wO7HMApv$17`x2NJktm8)PK@D(&)6(I0&W7 z-u!3-d*8#`cwU}N!Juk)K9ur6PQmY!qxrBjx0q|)FJ6=% z61b3OFwF=OJ@O?>W#!Se1tgFv@LGa*E;7|WpRugb`CuKnO82>T)OE&nXFA_n=hj^m zk_f*A-j#&B?0o6GPRzC`brU6^5|i)< zNr@7u1`B5GOzUg!dF2T~-jxg}1^uLWg47%hxo0dk@N8T1D3bxg8;Z&;-Alb z24?{r(%aC}9Gv_$w>4W_5u^W5EZMJ?vz3+T8dQXY{04%; zAwtJfyvHd?K|g8v!`clMGa-SxeiIef#dWys`G-K8Pm*?Mi0L+&+0)3@j1NWbG5&ZE z#%9WeDte$JI2+u3e4cR+g4|7(Z#h4e%0h0^A+Pw&pk@m*9R#)UHS`qjFh{o0Q~QU7 zTG?-s>dI^_kXWC(^}wZr1Ll*Fj820UX_ut;izO03*$e*UFH2u0k5yLwR!amcNw_0g z_rp8eB67>AtydweDFtpv)*eDFrD>deM8Uv>Ee*@oDnN;D6z&wlRm6hj}%2lwK3KaPhfqrGnuX# z#j0*9&&4@>;d-)aZjxJV{b1awT9li+{da}vb1S=#T}{SmL-NUjEXQTD7&D_e8y^pb z5}_|59RgGjo%Cz(xOMxqP-g&mww2K__8efU)}<2w@x z(a;)o-yJ91WNK5NJR4c%(?H93GTX4Jb_xk;PXtN-BNmq5)P)8fHfNJ0{1>!2pg(-l zgkHlHGWo|=^1B5j-ptFxZtY6V`>l`8SuR4=k0NA>kQxroOPA#DI&~|iJPP8)g3Li~ z9)Y|uEfLm;_KuMDkZ;t5=iiL-#VQ_$Mm{Iz^A6)2Ma?CUD9YSgS;Pp*iLup78nD~g zj9&kEYfWCyUR<_gQPXrvgE`j*%a!1h*tA>M3JQRPm}KB`S9g5qL7b4ZP+bBYn>-)B zF9A;f5?$lPthEHo#LuhWjV8TE!~d%Q-)WB5J3L)g=l!}%LzAZI z2yvk}0R^&9w-!F4ki7Q?^$9Yg)kr-x9qSxC@NRm>zy4J|^F4P?A^SDga_cc_=mE3@ z-f~cAUVp5=1pTSsMDY7nI4J7*JAX0&T_%>M@5mfjGLK}wFdF3bp>nEby+W$ve)6(X zu1MGEUZ;COMlW6CHAUuJfv_GSr`d)Sa?bWkisvp3+9z8b56)inRO)nXee9cmhM`Qq z_dp10rMehUjF_m{$eW0{$P~fEkqh^9Ztd6}T4$Lf{;wVsS%C#;1h$`Lc51}>!E2u5 z)|`s|jGWq>-$;XY%m}l-7Ty_nka41$S&z)FL`*eKh&oo2`U-o7igHAc1s%s_D?@{E zBM{!P*S$ASKuAQ~s}r*0kvJODOe$YbFJv;upXlZx!FAW zc1XtVs<%s1s+8kg4&Oqn*V|OJ+(qomeoK5Qi~0Vr`Sh@|@k)oM!>PE|)gJ30#Fc5% z-sp;P;^En29%cQx#c%9Pp#^IovKxL1U4Nwgx!M{bW`!TMRZDYyu5NN5*b=t~0nv+T zmpHW6KJ8n6KNkrm@p znJmv9*7YdHvUn|)|C(}{zb29H!oXHCEwKns-IlYfkoQ!jhQH##ApaPL?E(PJU}mid%qDSgSzA|P?}rI#OI@+07p+_hBRa8R4KU4#G5;crvA6aQHyU&f zB6kbBR~MH4jpOL-a%fov)&H!sxzhB9o5L;b%Jf^z!<`ZRX2I8X1&)9}^$ChfqFalb z*+{TEY&3otV~@~jxv&ij_hNBj58OKMbiXJdbmBGxj9)?j4ZzcV3J~@7{Gq3u}BZ>z3OrhCvn(&Q)#%T_6SUfQohX5S$k@A#|8Zu|2?i z_Pf!#ek!y-i320RzC43HN@>V*nJRsxd%fyn2@imxkrEB*vOn%U%{=+Hr9{kl`7>oGpQ^RHN>V)1wAryk$13nYAq9)C)(<5E%aj1DvJOKTi# zT+9QEdH1c;K0)Q#45DUvyZ^L14W4Q*jd96YCOD`X0BqMp2lT#TCK}<$fVfu7UN8bkX6VuX1NS@i3Poa*7cq#Q zzwUJ;fiT9(+Ru{}c#Z;7O$cxm|D=Dim(Xd$PisD77S?!w>QE^00I9rm__j&Znp0Ol5A5lySkAj{(kyTnbRO&&~7^6OEV0XHff&IJ; z7HZDFi9?W^cR_#+=#Z4S7{~QLxqy9EyBMH}bZJn}KqNy18Ha6u``M7BPpd3%$UFd- zugMrm$N77tbO_kKzMitLR1`))voup9T4NZ0A%(Dw`l~UUKh(#ukBMox>6F3HH{G9b z+pk0dK^)ELLB#N>)7w$iMz`Z6T^i{080z%bp2&vLw$Of2@6Z@T2pB`2I$R--2u$uv zaPpZH1#@DOQLuKc(z#<2VrUOvp6O}>iA~LowUAhjTFjbBSn%kQCbG9&;MfXO+8NG> zQu))xa0ZygLJ$Bqf1Y?_C1d>%4dvzGasWG=fyU_fbp(m3(x0H05FBS_rw5DZy*IYK zP}qQgkGhdNaXl?!?)FtncmPXHwSd#d>dT!narkw6G&#?JQ-*7674(T6MTBkHa&@5I zPlD-OfYNuL9gG-rsC&V@A$A`dY6UaE4jZNwE0r(q>K~#8wO^@C_?*MzL`5Uv3^kUA z7o$N;NtuW=cFaSR1yUNu^~!IDwftB?c)D3gz~5(DB6vJRXnyp(U>sQ`Ys%-8a4?XD zy|Jee)~U-6CK#qsypURotp_^er>nmMWwL+gqI1dj$PQ;f%)}bt@V;q7iVl9@XZr&Q zu~ukzkzw>ld1N~8pF&3fUy-+{cl9bSmKY7T_+IKlsev{g@BE7Fs%U@H4;W0PRJ6n627>NM@5SbLMQ_OXl5d3p3a zz9}DhaA}5_RDS-<-v~9+r;WGh<+8#%GJ_F6KPD6%X9LB&2Y+yzeyI7yEpZ>_4Hqnr;wO|C z_t>xLgwxSU5MStaSbI?wTY}tm>SByj?l5>l#Bzl-mRRSJDl5n&3pJ!0O4j6a9ib)1pL*QKpBcJ-*qZ{HJy02h-dB>xU90@;OoRCn%16L_1FmNJ?vj<(i3XN#sLwXM;c#%# zXsBr9ofRGTb#AoCh*@ezsc9_oMMWOD4=SG&?pTIHf7E`4rLXz%I7-BA3hmEmsw9@9 z{E1bDEc{6O0MJ{PsyZ#2)WGJG6EQ`Oonao7%G~|Pmpmcnk_&rs4@a?e4wjqicBvG( zH>~C!oYPKv3$6nMM4Lc$pjyP5m15|Nb1e)R`0}1Y4M4|0i)A1YO_&(`m6c`&h-v8} zSvX@!){387z~kmu^?7R@E@t@F&)b<31l^luxxsB-&qO9G?Qr}(vFl(v${OS89myUx zuM*J-`HBg>S1TO`TOz0}KBMkT@l)d?*$uGZE8{B`NRPQ?0UfdY6lYv;h@8)*kMXJD zD;Khz7#wrg*@=0u-+4@^Chj9hd}HfxD!%Ug$%2up5$TThZMtR5;rAS` z1r9S^_~SW9U7`rR9)iQTCA3i>Cl8)tRna7`)Tc1{=07L_D zm9TVbj6Zq{c?HgM4t)a|7QYE+EyS-%Gfqm9tXVirY<-84>@t<);wnIvXqC;Y7`1bkl1w*pb zviH$AWBMkZP)Jm#oCFio&}Y{flkz3=mOru(hR`mBZ{GgRp^17Y28l*nk4?@6VWkhs zN0IrPDH4cfeb!|APl^Kyc6Y?T+~yM&bH*q7q7jD|4@Xi!>3NZ=_iBR)`^)3|jDa7jXS(1}yAk=J1OjxB5;E63A7yTGTrnR*2uKBnLU31xPB z_K5^RdCLvIW0g8h=~XHQ#u_>Z&JWKk=*CqFN1dBrc_uWF^T@ab>_Mv#hHbJ=K&P-U zc7IY`1yypE^IAEFueU`YLhph_p%RxUWP_z-nkM_cM@ZU z6Bl}s-6T*CRjIq?KlkD|IaM~caX`!TQ%$FZER z?n6!<8h1hc`FZxwiLyZ%lOV&?;j`!*pkIG26pvywoK=X>WCDEYn^UZX!ez~q2L4v*kZyKH4 z+xRA7p?+P0PPAHNYvjv)O(?2BM8R(e|0EM;$sx}0IlbcRx_>C1(M zOYt~Sdg8M<18_h?maNeVcpFGx7YM!4?l$)1S7>c}mOeLQQO8V4LCINLnE)z@{Y-P5 zRZ^jQ_$;UgpXNw->KA?Ed2tCB3y3Kt#oX@x-T$Ky94As(lV7_%e*&VqampeqvH+1{ zJw5VAq3WEajAA>@r_jE_zebbQS)Hl@ebR7%$+&0r;fWRpNAwb&X^llxDu|JEzYEI1 zauXz6H_v-)ayPH3&s}KPs=Zj%!%sO|Q#9)hk)y_wqH(rD<54{$jSbxw8y=_c9?d<) zAop{($T0MlNfL&IQ#d_ii65fehaXafJ~`o>_LZrFl;U>-h`?|TcSYu1x&S6qG=%Xb z!JUnt>XmKk3d1AkAO%jG|Tn=i@uWZ zaZa6x2fWQO$>dX_{P^TAHMJqvT5W$r{SJFHS$jlOsh8qgc*5IwMsg+HDUnU%%kYF5 z2J-Sli0e_vo{>L@E{hLlV~l^YEwrXA`%CW{sieIs17kPF8_dK;HMdoQs@ ztlIO=NoRx;&+6}->!9|`PzhtfgYs&aw6aG?5RdHPeNdyyF6JXrSX`<=4x{1CI~L6B z^l=>fHz;aCc0by|kVqTaAcf-S>Id5PD#IPSR@k>Hlj!BBzZkz}t1fJwPSifU4c`N0 zlbjxiiW?4BTVi8-W>Gnmv4=dJW??ajYL{hag{T>xLhSk&vOdM5prk1Au=#qlksR(? z6qU%_OocXRAd2_TNq&8WMxbE;Q0hSANl_1wD}I*Wfauqtvm71g50na)MZUrp!xcAa zL?xOqV|Sd)(Y47_2)D(w;k-6N8*s*xvzqtu73vEDr^(RWqs@eqTl4#$TF=`w_-6_C zH`HA7Z#*K)kR#n?`%X0)`izmHSD8ss{On$v@ZWIpowe15@l*1bzn^f|Sg0Ga2fDl* z-BkT4UDw5TeriC#M1^4?_?lMS^w}wmn5>4A7xS<0%_vp%9;GC}|7tTY;c2d+`X{J` zGi(wtPeok13NVYI2+8p;g|wf~mP!-Ga;|8SWLGT{mIwOYEkOB@(>Zu64I6J4!HVF^ zqh1~~(E6c$tK9jtRlx;7YQF2KRIe+L=DHY(}nrA1kGOJjOaez*%9Y2 z)=y!+-hF>os*uWJ?T?ALp`D9+etgOHa0$i~y?VUdMDXrUXS7%-M-Cg@84^!)Q)$8f zbniyF{_iebjpHPZ0-zf0kGn8|B7cELd{niZ`FYJeyGomq!Rj79@sdR+>i+^zDL{&{;~)0EYKwYeaCe) zKIWLbG;;^(MSb%T9{`&ePe79pq$KCNrBF(tu_3P}cT<&Fs068Yun>w3dF81CPL&d$hP=6v6ZVg| z&9K&WVa<)&moKR2M??>?j^RY*)!Y<3cVGL@Oq7{%=2s~-onQRX@>07S%q=Zt@1EGQ z%d{QO)A*d7g<2}C!?U`IN)MnB)DeU-FG8zvuQibb!W<)#2a`6dg!SA+$XP$d@^a(Z z>;B*$9tV_98tTYkH;^AbaX&a$esP+kqvt@NdguC=c#uFtS+H}fX>z!mKJs2`)FK>4 z;H(&Af&)NwJH7KwQup3A*FPnmlpMKcrbnTabyMyQSs?ut?jXNhF>Se^B)JNvQe2`Hn-2 z1;gzh6Rp_adi(++D5rHWX^?5v7m%!34NcL zXRiV!hAb>Vo71l4E};5PNAUl20fEph`1GO`caZkCwYklnqpdFyR>wV+!MxXw{+D!f zZHoDtEFDB}7}(}&502xda`IUNkz_$ILg%c;g3M_aJSDgbwHp-lM0b?ztNOiGr=V3H zOSKsmD*^LnE7}$9m7zfypNSxAqM$#-aPBoii^(?81yu^k1=u> z-9}6D9Mr#0|ESG=RS09?#z>1O-xV!TI&&qCL!wmFH+YS;w?48w&FzC139ziG7u667 zLG8oMC8Q+RP~FD``ZR;DSGblkTCdV1)EnC;t>za^BnBT)hi)-4xqtY>9*Wp(E&5hH z(CKm`DJWZ;w;qlBP;K{})vK~?`Ikik~k68h#C$!Gk zs(b3WQ3;DeUC-`%u85q+3~VO`y*=Iox9rLb15-b&@dbpkS|`_=&$%6JLn zYRV$IuL&`Xy%}K4N8s3wDu`qkMItSZZeZG`mCPd|<-3C{leU&OV~x7PHJnfXCQJFi ziCghAi>L~#;!292N+}8|8qs^|-JPoQ#`JFm?<)To=fnbAFfNuipic}VP+*1$dkmX6 zbWhTcR>+dg?)82*lHrvij*LPE9$NLglBEMHjzX2aRj1u}l3 z=4i4>c|=rMvmLC0Tah!&21{z`Q0%sq3|HyxeiyA6)N=deXJwX&HKrh)#gemSVI|nH10fkCas(a$?NgNhV7UX4Fr}6@3Mt9OeZ|c8v zWUaV76SKZId6+*6EdB&_u&jUV1upu+ajH4{*i(-A<0TJ_BjKb4%wQE+GiBATu0Fdr zs=BD*yPwU_eM>aQQq5|6f##~~=C#(mCH9#a1Bfv>Gw-(NxCiXqMFYj_VC01}Jvxd$ z5*{tLxxvlrMcj&i7zM2GHWWGWnk@}|zEKTB&@b#gGtVAF_ z3zIW#%agDxl*`M>&p%$QsMOXdehjdYm%3%aqX24*Tp%N7NSm%bu-lqw_=pc}-lsa> zyBZsq^_nVTY|m>>&WXvQ7c<^nYs}VGX8a%9$Ce+Sq%GN`A6i6k^tdBy`mkc`i`0`d zYeyJy7>(WY)+YYcn2aHR2@ZxooJ?J__eQ?W3ZXsT8rujsR78aSh?Y`!V+|A0lLyJp zevSPm?H6yl`~xg)>7y(t6VF*gk60Y!XTn5)8;U42Grcg2HjoRqyqqQIY3^A)!jP!o zB^G-LE2@X`iVH1v!LR24=r~P}_I~t5Jhs?vmUB*K}JomazyoHc9Zd zoD$g^VZ9WLc9v>oX-P_+RnpJ_MCO%niKSP{^z;lwS0)e~ngoHVMK9YO zZf!g67ts!8tXU4V-@ZFd1ev{?k$L;OTz^DR}aU0sKzR&E% zgP!tr*o5Evr^ufsGt=pYz5rVg;Btn@!4X@s;MZobmf&eceMB^5CA%@Z*4t|-qul1j z;yr^;SmkA;*{1Y;JL)lqJR1KhDDZXtr`ox=fqrvSW6z?sR{8t8$9C(Z&MPrX3pBZI zp*KO^ZXuY>DPDT;D-Zez6nR0fAFt>%p(WH0xuawGLghjS>6xD{P2|xZZuiyY9i&f4 zXPD%WQW&lF)-O*He?v@`Yz9*oZNVym6tK{S*NxK%1})s2XE~etS$YjD@U8fj_QlYi zBZ_Sv7Oz2bRiqgeEGKDg?7{v3GzFDZ@zid^_6352v2uL`7Jml2?<_z?8%G;Y)={cX zKjHWW(aUZplD44p7VO;*>&)bJ1U?<7T{`e?U$*c%WWI09<8SOgiApzm*X!`na0q{S z1kbo!I&a|8hFzblmdEQ;+tzc`BD(hxl?ASSIJ}?G z^P>fSP|JhG;W^dIY;_(FnJDwS9D~;!3j3~=FYm%Non?-4iVSYRyz_6T{A6yK6IAU3 zsp%yF==kI&Lnk*pCW?9dY)MV zLH0MavYr?HA2;Y&A-$UhxWz@i;HR_0qOP~bwPMg!+okRMG`W}K&&%|!S3d7eAv`d>0z0fGYHU zf62VtFU4-|#_TE|`#|Y4^6=j2B|uMSC4Yb-hA9p6MR9^bXr5`U^Emm3zBh(z`N0W}+`MKW6c}rk#2|<2&%F zbqxM!KdrLup6CKUd}=4AJ8Z2c0^B6gn!(Uv#kmX1a{fB0=gx(sW@om@%6A7DZfdc((wGBFW9v?lh~OVD*4??a%g3GX(Eg z!);;D(MUcqK%c|iOhT~khI!7cYRsJE`oK=!jD%D9k*^qCmP4(+Vg_tp;S(#!FAx)O zw-4~C?oHXO>E{qPR0nUnw*NXhg8Nb{Dw(auG*NN#*B-j zL_&X2(fAQw%SPFRCIKRLB!VyOlL=y~{TVM5Y0UOo&lWO1N&hhaQ5m0!tA3XSdd_a% zdZ~h3U)XvcbCEx%D}Z;7V|?eGbow6Ok8*>_^3J@KFK0e2WzI#J<|cw%p9mfIpBKz6 zE``UxL_CRMCd&m$qQzNq&{sGNCH~PnX2E;{e%t&HierlrCAzuEB{~jhMR#bGmR>r3jS>su zLUh&rS~&T3k%yRF&oA)`h-`5c6h*om#sYbp#?vFQL?bwwd>=iwUeM8Nf1@qZa%xjlyIwc+3utOc|}uOHe|FTeU(jv4~AH+DyPyWy~HzYXZ$NL zS0j5a0Yms>EqVz0#eoZ@D)_~aQ-y&_4Wft;6(5}OlVV?qF#UdgH-cs?B9e=iXU4I% ze-2c|bpN?s9u43=(yD^rWs;ANA=p+P=^nr)EVZj5D(|SP`{6kACvMR*hwg>lnipE| zVLmL@l~$>~y>G}e#vc9ZbF%JJtOvQ<*(&($j&Ae01^hN-=H~rnAp?#Kt$a?!aSF8I z@aR{FOm-~fvbnV>Qg>LH?u?d0%v(iUe!$ut6`RQ9I_&enDV74IEaE=r_=C&;IkfZe zsXLZb{KknDriJ7%uyDZ3i=A&v&Ltu-S5RqdXUP+tkUtm|``7(1#R1525gj}r#%PLi zvTG(>eRW7Uh_P((xkMA0TQqJDhsN7(-Fm;}jbfNXwv{g#q-RqDdPn-)>hs+#nhr%l z*>5o_2{-xU;PQh3*d6%|W}vHwypAs|_<@9|LE^Tl`fa;B8nPjh{oegg!d1I}Shazk zoZXVae3CZM@2HC6G?o;Z+&SQ}kpP+5ZKqD8%Db!OCDzN*S#g109)zx&$9@!J5WZVy;{XZuIcfle0>pgn<^q|U14FuC6J*-}jb zMeZw>lF+wCimyxwg!CZH=iCH?du`W7yRr-h4?pxQdhV1J^N%cM$t@l~DRdJGZoxmn{d;VGmPP^! zi{^pr3frnhlbWdLZdYTK80qBRRiBPMH5r!=B5IYhbq5p^0tVVb+kh>qIo~hRep5t=I44UI7iqhf&xX(`(_`rOhZSQOlGm zT*Q>Wo;H7Em#s9Gc;%s`LPFV|8(A?X#87Rw75;u1_}gi4ICjbE%n5L?w8xUG zpaTP&J3|XByOh9{71%bs79E8H|Fd^Ro|EbXrbvpby5&5#v#~11s+ZBk(S|y+xm3@u z0Qy=nO(DUa;Bn%olR<-mq}C7?%5>eH@4XqjcOB|^4c-A$HR$t>E`c4QkTW9`w3grb^F6i>*mehvQJq|Tlf1e zde@U4WbS`s?;k~8>rcTqNA+fJSNLqslc$!xD##uN`+hC#ZRP%s4GmYMV&2>S$MPl`89VZTs&o{R~j=Ao(1o6#Wi6j@cR2fjwAz+e|<+2PT;ZbMg7_uh@A`lk1ZxOI!azRP@IUHVNh-fr$p_+>UN{yOK zafTQcCkilBDsYSORF1$xxN!PVqOVUF)ONpA<%T6xTQGZD1zLi|btVHLnQ0Z@AtP(} z?}MxN&&>&X>+ouNDX$&n;Oo@WE{Dsg==NJ+_EYEKf?EjtbK1p!FS4Ct(fg5|FQAv1 z$>y%}^SjwbTi1{ca)L~v%q}-bQexNg!`N8!*?Py`p@K*ULVDJU;&5+or_Isvu%wnK zFt7p@&(;q2)g%O0qQi%iTZw+blUwInzihAX$q_d!k?6(~MHlEBo|%8Ec^H+UYJQr% z#rHZxGDJ7eZXI-fTslVotWi(?cNM8=x?pNi4F!DHmVKgcfXK&{Y@`e4pIAL{!djk19z~ zpuqLTkTV3uQDFC#wacoMap@iw?z{J^8G=?5>(OeX*8*IqjY!_Evf4&)kq6mwp<>p1 z=VZL<$a7zA!)?58cRUs=o_ZnAr)(cj(Ouq;UFSPpqeHVU7t3EauOD2#zb)u}EcAbY z?B$=^ww+sxh@6Xjsn!G!U^+uAFrFSRs6AB4Nd(rL_qP`Zf`{N1Mfvy_f0jk7?@J5x z9f8JD26ocoLva5-^UNsrNvk$~nj>mB5G>DJY>)k{6UcIlTgpnP}{NTS}KcRw1yw^0%ex?bL$`w|=#6lxvC^DzbuFg_G< zWK(z8Rrgm^!=SRJZ;=R6HB`DdK6I4{ah*!XvX;6ZZ~%TbJo&lvmqs4y6SAFZcN#82 ze*|T*(pS^agy5gJnjvWk41EB|iPUl^#%hAYXPV1w;fsO$Hjo`?%oq6NRp;E*@~2Ns89 zA5BrH-$4Ui7Thv+G{xI!?Wx=wS^ zy)eie9$Ta}f%m@Vc$PKn6T5ZTn&vgO3e)sujwXCaARaa>X|XRwvcfOuFp2sU`dl`G z!aArtJWo87f)n_RkK$7(=1V7Xpy*Zzl8V)Z2tTi?=*_lwwTbXND}4e_R4i} z=OrxV>Zq8bBU8(jw>NlO$WG+3XOOyp#Id9Cdz z9qx2AZ4MrJ)hsafs({P08=u$NPX)4V{NtUGi821CP^j+d5qP~mH>Szf-cgrkK52BM zN|!v1<%v>xB%<0M@f|0EAF{xoJU3q!5)PEtqX_{!RhpIPHR^#st7r1f^(v-{y>&ea zv5v(y4$G~+{LPCRH??nPd|YqZADQ7nE(YV@6jR!*F6XMEP;yeYGzd-J>z_4=b?|=A z>oL2LJv0MDEcMzS-oaN{k)XGS=%)4i_~7QZy%v1uKtX8^)$PYokk^F+?g9T``qrDN z!*dvJYNO>+3%<6UuaL0t8T+>2cw?rOXfRTxfIf=v_%zHW>PD}W5e6(fH-GG|r_o84~`w<6>}wzz@4qm8yL zH*x8phf=zaw3v9}QDISlu}-RB6)=3V^CrX4J}gs8R4+6XnM6w^LM~i|wLethh&*O; zH=ZP`EP*4l*wTpZF`>B_fQeZ~D1sMuvMTk9s(?svk)h;;mCPxu5zx#(ScJDkWXL9H zC=p?xOy+h}d~ul(%m$wi>`$v`x7XzeA-O>t*h5Q5_h)GamPO`f*GGPs|&( zzJm@nEJa^#?;{-2-)Ow-o#MlJi!yg?5Wy+#M-5-&@9#F|5ea%~Q^@Vy%ABf?QziErP}58UvQNgu=8Nlb=<{Z zt~xMKV+$8sZcg)cK2O~YJq=^D&ztwk0(@Y}qzHf&ZAUK)^m825q8yXEpv#_65p8&@ z4vM_-krH#6gjFp?eium_F+U~U3`2PH2UkqA23MRGP8Bbk5p8~{_@TVGqFlo2916vw z`j}M&X!U8z*{+ITay7+co701H=UXRP3)ZgNH9A%0UwLDDyL9J1<2J$M_4~28%8sc<}7 zd#BI=euOaw^*ZK!B*op%W#XspW2ifDbvA%Kahr^)Gs`PXtX?4< zZv17@y%Rkox_c`dINba@41Gaw(p8Wf({1vzVrZ>mW?@KWCaL4=K`4uGzP{A?tpGQw z_j7D=OC~+C{;{2`n!kFoE7=r;T9n~Sno>ARqG>4P{trN`{SHydCq@K3`}8$K;SjFxROM;DX#`z3drdz3=n_OV_cdQLIj*jQJJUL|(@&)f0(HM)q= zIVRJ`tVLqM2!?#Pf<RqsMq*f4nPp6PJJBQ@<2Ew49x!IryRo= z!cfbDK_lAITn;RnQp@%zlvq%)6qor>yszz}QdGL;d~3OSnxL@;9Cj4ksHx1srquq&zOrXQ=nEt2E@! zmhiJeijc$^o9U2nVyh00=K_^#byuX>81rm%4ap=~(7T-|$@L8+idt*99$`Ef^LIe& z4pWcCtlDa>^((yOL~mGM6iLH5oD1Na<_YtqNaJr@DG@@o5xIFeV+W(FVd9W5e@D5$ zWjwjrkLY$*icU2vBK-FK`rg49uS8ZIJfxy1B}^%Xaa}Wmq_Xp4t#;aVV8!0i)o`{p zGD?~{uhG4OgF`?udFZzx;W7$9zR#drDhktyP2J6+anYw0fAoeeaj=+S^-GM^kpA>| z4pX4AZnu~kpcKndT}O&R<6cQe9V%hzrv|Q6fxs=sn!6mClbySh(ui0$UDA)Ht&)X- zbmS_Py&;PbSv4_}Uq`T>*E~lg60Zcr#GbXlmGUz2%C68+W4g%+o!eB(>ra%WoWhD6qdukm3n}d_Bl_p{ipp}t;>=;^a2hLjQfSg=8IrRvD4)(S ze3bHX?b<@E8awK%`kig$>Oh@_Y2>sKSVl|MhRL6O_Eq_eTn*){Zr955%0E?oOq(G!iMUyLN$i)~G{l~GBPa6pQ?ri2+ z_Dn`4sz$XIPTYKX`*lt>i+!s=AexG68#E>OK*`tU_^&*bM5^l`@md)gLaFjo#*S${ z1%`wTnnQ&AZfNrM=3SymL=s+OLj$+-F#$YUcAqJgi$g5_=Xg1}QLi;dMn{in|9V*f z@Bwh%?{nJ5Yu`kW!*bFFzhIJB%ZVzlK7J<9O!@JkkP>r33lKHKRTXJ5{8T zKpVo7Wb99J|EAcVob{C9QvgT-0i`@&ibHAS#N)Mi)Q=-2C`laqV-jj&1I+|l3$V{+ zJE2DS1+Fu2xMFQD7R!NSiG^jLJUv|*9UYBc&ik7HAoA)kq}{XUK=&zY&YNQCU%e5^ zOW9Bw7k{Ms#3`?r&xv1Ege2h}* zUlOlVAW#5EX9b`$f;3Zgat?vha&ZUq0#}G+uL;y0VF*J5(r{NI#D!=v4rF6K*+RHPa=^-fx;k!?hM|_ z4k)U!G&Y;fqF?;-(cwK?A3dk7^O`w<+sDrPh>Q$+MQN@!qg1?5IMHx z?>}%rO#WnqP6;6|W&acdD7m)k?8{H9dr?cZ=2PDOWgKnmo1%mlCR~Z9fdH6J?JE*!2aGaZK^*J*gjEyV5v`~fyUw%##g|6@k&8aBz7zW^rYq~7`%TUKL(7_RSYN}YBU?%C{ow`${Qhj`4 zLJUH^9Q`)|fU1dm3s#@=7LT1D4giRo^^|E9{U?PU^<11sEAuE%PeMSV4fy-T#g&m< z%_5HZ$GOLQhBcGPzrxTu3V~W3LxusqKnu#^XlK?$lYSUx`TB14dQ|aByJ5w==n`<2 zFf6n5Se$%SU8D(UQ9KTiGX4xJG^KB;cpi(tl$T3T1PN2Qb>g8q?}Ghf zkB<{{2_PN{RKZ8Qu6|CYe!N?UMPENabGz^ZyqxU^A!=`HF&_TqlSe!A-kP;5W-a$= zj-A9YSveqnl4FYB)YOv}f|>zzOrOC9aT=En0n*R{Ov>OA06xT;LBxG!d}a9LYtyu^ zYX54R-vhHvw2K=?p|o}P__G#^|Gc%Gsdchz=gzXW$I09j1IW352wQ6@a!)H)$5{GD zFxCZ@IG?9#xl>SAEKJcqQag+^7}QR1dKIf=g9Hnev6Wv~IXzy9X6~5|%T#p?VI_`K zwNG4H6{%&^fvO3m7-t}?XAQEOg)f@{faSyv-SWbgq8sdbGs?ZiVX7&j-jQVC$2jsR zB`!-*u>h3CEut!ex?Z4-O-^8Zoe>0(rhI99{+utrGotDGP#gwyZ=42!Fn+{=5CXOe zP_sG+gcw(Y)=kywFcW;#=A$cK5Y_9AM$_calLUagopaS&H`cun$~B)TVGS{mr~(3( z>76vToVy>3K{YW0seu}Vv>?<2L^O`KL<>HU(jtlb>gSZ9g<@2(Q9mb}J>-N?k>IOy zXcCI@$lKrUT00v-nC0I+IW<+g>#l!+uY73b&0ush;7uV!YV0^%pz%J?*kdFiBw2vW zMu-Xxnw!%i@HpvU+UT+HOBg`Xddk3&VgW@u^cWDxb1#l<5bW6KRE!1{<;VU=0U9%K$Gzy@#?0657F+MbOnI| zLYxZf6D+<(=JdLF#LsQa0uX1KDX}Lay!3vWYBfh{JORttFFS+zrAi{yW<^hOCO@A0&l?^nxxON@61M#xYP(@ z#&4SYe^LP8w8`iGEnTx=Ugz9pt9HD280tPjn*LJ=fH=L=IXX%_UStO8OaNre7NRk( z9cWMxdAhNz#D*ljL-NGP?i|e(C>|`*F<-nxg#kWx;P6H0!T{QH(Ua_aP@ipQv)$<4 z`|dxIHTPfNHTSw64{Sn%5iA;3;`pyH93L)8Azy84@rRDh1|+S^qL0oDkkSB6dO&!7 zm=H*m^864|aUPmL5zCH{v#$f5-Nt^3pa2z)?AbrI*TUHLL!mXi{{H@xs{S2%JHqB!vXC?zasZy{~`?6+^1W!|{AIl}u0ViQWU-k+q(Iw(L+X}SP zX;}|6{(UvL2>K#SdL&Ww7sxAaQj2j}3-S-=4ASrWeukgdGCP=D&V?(lY`qAg7Qlu_ ziN`V|ELTfU;y)w>trT17(12!8f4UYB7lS~hgfZP~!}G_1WEP^to=Wi&DM6ElvG{dG z`{HLfX$8#fJD`s8)G#p`nzA18{h&ekB^x6q-wagci)})zs!kFB${pd{t8cryTKAw_ z4UVJ8nn3`{v-v^se*akQD{X*uN+>4E;do$3oNSR55VfGa_(r+MNJ=J}(q@{B4FvHe zR1J#a3p2W)A7W)~OHePfMI_&#VyX}}TGCEeg=UZQg-~Ri#H$Y}C5bph=Q3Hck zAVLy^I?tzN<3T*N0 z1$BtY*no)NBrO0*(_j#48X*j2c~@xwc@(-(DCR$zQwip zL<@)%NiX$9RJ}l4{7H?r@{vDzp&Ui;$>c)WO(s{@ul2CN4&~4K@=F5vorbz|_AgM1%H!Jq1Ot zD4vW2%DWVCX#uDKaJ*tr7!UC1B2HrP)V7NKu^`mZBc^_uivJOdT6t)gt&}fpUinb7V#` zGbR|WDWwoX@k9=!R$1n#WSZQEXc^P6Qe`A5F~W~B~fC$*x(QseqzjMqsJgn%0Hw@R>0LSU;ic70ySz;%QDfn z!`_2Iz_?Ca$P&SxJ^kf+tq!jw{fhy>EXRf!5x^jO17&3SxE*d>IaLqh8Hcq}O;7`X_{ z_d$atXL+a19~=wz_?~B4@-lnOmjW^R_bTiU=7x)}?aUN9*Bm@B33ZPz^rjjxlxWMe zIACN=>U21iFr`%&Yn)riYfz{beUxWAVTDoH-Y5^y>7%2L0Ri*!x@BN7(!tka;vK3H z;|OLPw-yEI&iF7y6BWE~ z_kqbBwgDNw;6xBajlBmC>XkoVk?{op?0ox`*e$e86bFdc2$|;?kY(PTZFi~L?$8Ev z$q-qnSPBrWv3MK!^=M?zfdXnAZ7m*nymVyO;cU*Wx!@1@rIB$COWSnCTzHEa*Af?e zu)wrzenk#yXL1;v-USWM=xZ3+mPs`Cvay=ItcY8djZ+_I_)@a`=olC*ldn)aXh#8~JgFSO;0=b6$=jgO*hJBr9^D7Ob1l7qqF(m?3KPavBe`_UiPD@@ z5lCk{;>0E+G>Pa-c|45^hKb?QhSuOnhXW0kxay~3e@IjBXUFOLfb-of{^$8M#Zal9 zd#+MG>SIj!G&ejsF4BxD+8P z3?UpjIM5zVK3nR4;t3p<4~0X!UmU4bLMj^hOz<7supHOUWLjE6Yy$5hB!vBqOvE?^ ze0FD$U@@M(*Ym+yxFgH}01sSAL_t*a2Syy*8YnmxnG+uQId*e)+3Z>~N;=jb3fiw7 zt!I{0Eeq!u7&4AY9KO~pM(T=O66r(^Oi_oul<}vYLzX|xj-fN3$)bIG_m4mRBGU#nhI~F(bIX?LM}sh6A&?IS*<^HhtSn38uPptG0kH72 z^|u|IEQW1MTFq(3j-xetJzi|Yx3*g@C!*{g!FWCxw2Q68A3K}JQYQeE#^Yz zD7yD24~<`OPV16F1}{N2173Lu#of8;rjhL7pwfE5YVdh|My!kWoqRQ0d zRGxYhSOrPc+U5f@|FALKo0+{ z279J5F+E*lv{NaS%Iq;A#t}nYt7hhHAho@3|9;Bu0^gvfPyhezU0aMB)fql#Zl3Xd zvyOAw-CQ=n5*n@{1;RxQ5-Lamfuh0_t*WYh=u01~s(tN4k&vpYeW?0Sp{k;Ywkj+{ zP%4NZA%%owlTEV8CMDTyVz0exk7sPpea`7Q<2mE8vmwbgkdow=X4iL*=l{-s{`-Gu zauU4v&-Xx~P=K2@ZBmHNkiYM~&Dp^eIQC@T`T2Rb_WFde@&_mN^y3NJgV~fsB#VHT zDIAyew7ouZ7l2ER34uF3bG|V;lmyQ{lHKZiSt4A{1K(rAcG*Sxf{Vps9m#0l(sAXNQmYd;x zf-7?W_5fh=vyYF0&p!Ssy|ZR$=r%Et&WS;>jUTl53CG&_2`8cJj?pw-UDsQtN%b+> zcqmSEBzbT)uxD*Wb~g^6>ubXvAc}kzt`I3f4yw@_@9+AcG&VY#d~Dm}gJT=ke>$2e zO`bH;+jgsiKdv^Ev9wIbI%WZHsgs^-w~3;vB(e%TK6Pwv@19*NR{iY3Z9pmlzy2YS zp`CH8eW^wuTIJDQ6rOcA40P{e0*IjqRC?AkWq>1Zb?c(!e6 zhHYzFtEF)+v~60r7~?DK`g!74^a61dzZccKE%G#*yhWQh5IYx7PE8r~*#7j>!pQKN zWviB@|M9&w8$R8u=3bmK2EV`DuU9y$wv#9(Sp;3obJ%qBIT^%Ds zhdpi0Ac(5lx?Z5znS>4^wyIeiqw5&N(LUdG5yY6yR(Ko~ejhj@B`~)7V-CfRv?1c3 z7&T6&NZ$zOb^gd)&72`t2u)-Ft!C3Im&-xv*HW2JNkD$kArt9Z17zjo95Lwz4a-tZ z%Wl-`HLHhJfwvZhYkA3f$3-cW1i?-Ehk@a(@o_2~tJz$h-}cxe%|{=4;HAy0R=@8o zXbq1;6^<8V0$NZiRjfv>1h$Ov&w$JZ;OjMnZDRgOxiC&2eh=(`t(y?i4OVp?h%p$1 z%w_vRqzm9K0Af}_+z${>=I8-IpBWer0)=9ddfjPZhz3R6abnBh-{R?`aj&gOtDMG zM!rT-fw0q9B<6z?311r%{gF<j0Y1zx*I{D&UJJ`hB1;V7r1YhiSMJb5xx04DwQ zm0@emTg^e^2cIyu?`{Eoau`ZQx-!-5*|A~<^1jszn@vZepF2e9GEzK2<-aPW1AIW z9v<8OE*ePXK??eicim3kU=H>%9z;4IW-wrk4PVo(&xhUZfPnzTKoGM8qKsY)dcA%z zfb@v`MSdrgWnjuVBENiYde-{1GH;g18#8prwhW`Cs;bXk`@hhV#>QQ13KdCA-Ne=E zJR}EegaBTC?d+@S!)v#0D(Ekk8yd(L3n(XQoa7mupjMbn+W8>R-40@kUjzyid(LP3 z0PJ%L_1%sugBW!E1766ii>-P}EJd9F7AA|JjU`A&+s&!?uv%}Rl)^#UtgTk5G}t@9 zmmvYZE)@2^#i2P80-Q<%;Tzuq(3OB~ zS>E2ml@ECy$+qibVgb5ps}+d1z!DN=zECyVP6UYB#xG{_(@{6Y4XQKYZ%0 zUu4wXFSt!B-5kh+T-HHJzr}HuM@OJ^q(OhqL$LRwjz!2SqT?=KwoUYygE5iMK*6G_ zC?+-H-^DUrsDdIT0M07J>{CD+SxBqT;epkw`5MZeo-LoLAdCc``dJ_}ap*w18uYaX zKtH)2qHWTNzU0G(Zq$#qHola=g}2wi=@*yRr?y)STU7EHkjr^UK?dXvA0jhsZ4*?N zgiA7R2F4j4lLm27-VOeQua8!L_&&z?AO$+SlMsofFB4@?0k*_r!||}Gw>ao>oL@T$ z%Ja#o@dK6LHnf%rkrxatnpag#Z8VmY{IM8acL1P7%cSW4J^$XL+q&uB^T0Q)+KRiU z2c)&HZ;)r6AFQ8x!fe!133B1nIS;3^J`ioPy4n~Z69tpM11<*ApV0$5^6C&oZCqG2`-aDsf#Kn6I1hz-xfR?Fv*N>E5tRvQm%XPbkc>^mdAIq`Y* z6IE*(0*6z!Vx+ zl{Io{dr_PHwxv~u8wm|4IUb}2Tr3v=mK+KO2$4m2X!-y-%ry)^loSh?=?D9Cfld`s zQ0=5hM6{C|645*8&<#4o3?giK3Fzns^lg`zsS(Xwp(=U4x-ux4afz5o6#D@Jb}J3iN5 zcS7NYUl~ek4-DbTlR3M3cd}Y7@Uj6DNed`x+C$nRQd&eJRnBY#p&M3oJFzrx#s_*O z3Aa!pMGz!o582p-K6Zrx&apYd@CNWbCCEyrQ6J4)S=GJlsS~x-zfM=V!>3BdlwsBp z@-2ynAV>R_c)p8i)DHzCMKDs;dX2isua@{{r+wWLQxKWo;Oy)Bqe8!HlUAti_&Cn_GzDgv~BIxh-Tl+^I(Dqe?R|b9TZ1Y_4wC z9Is8@W{C+Nr3B~$n&s`fi01hP(~*hUXOcai?EG5Y3_DR@O+U?b4FE>{Kq!~z+`st@^L+e7lm6 zq{_xS?nsZ_dP||OyqFaPk*_q|>S@b)1A(L?RiG#e8KQ-+l!zin^YA$ur0VDcey1xs zf?+y8qJ5QJ+wqK+>DU;VWGinHv5q8~GC@>;yhIr8cwWcgq#aXS-}g-0Y8vgPs%v^f zBbuLS#CyG8O{w2QHxvLEu`mbM=|l!?$H5a56D@iS4i0h~Z{L`d1U`Su=!OjDL7wMC zqJR;xOw0?Hj^V^XsL%^EV^_7@=Wk&WFuK<{v9BN|b3+3lPVtCi$OPEbb;arn6?a}rY?a$P}lXvzP3N9<|~iG-2^}%#WI$dt`M<_f@Hp-#=QKOPq7JJ z!Dok=71uoQdJ_O&K;2+amrZ^7jEwppynw6MoYNPfn*g{%x(FZtFPVEebQ1tKIQ