From 9c06977e039abb97682d8ec68d3a397498d4682d Mon Sep 17 00:00:00 2001 From: Panfilov Konstantin Date: Wed, 21 Oct 2026 20:42:28 +0300 Subject: [PATCH] =?UTF-8?q?feat(theme):=20=D0=BB=D0=BE=D0=BA=D0=B0=D0=BB?= =?UTF-8?q?=D1=8C=D0=BD=D1=8B=D0=B9=20=D0=B2=D1=8B=D0=B1=D0=BE=D1=80=20?= =?UTF-8?q?=D0=B8=20=D0=BF=D0=BE=D0=BA=D0=B0=D0=B7=D0=B0=D0=BD=D0=BD=D0=B0?= =?UTF-8?q?=D1=8F=20=D1=82=D0=B5=D0=BC=D0=B0=20handoff=20(#13)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- components/form.php | 2 + components/theme-client.php | 70 ++++++++++++++++++++++++++++++++ components/theme-preference.php | 16 ++++++++ components/theme-style.php | 27 ++++++++++++ components/theme-switch.php | 15 +++++++ index.php | 3 ++ messages/en/app.php | 3 ++ release/assets.json | 2 +- style.css | 48 +--------------------- template.php | 6 ++- tests/check-theme-preference.php | 20 +++++++++ 11 files changed, 163 insertions(+), 49 deletions(-) create mode 100644 components/theme-client.php create mode 100644 components/theme-preference.php create mode 100644 components/theme-style.php create mode 100644 components/theme-switch.php create mode 100644 tests/check-theme-preference.php diff --git a/components/form.php b/components/form.php index 803682e..fe0fa71 100644 --- a/components/form.php +++ b/components/form.php @@ -198,6 +198,8 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a function redirectToTour(record) { let targetUrl = record.meta.redirectBase + encodeURIComponent(record.key); if (record.meta.redirectUtm) targetUrl += '&' + record.meta.redirectUtm; + const shownTheme = window.ZoomzillaTheme.current(); + targetUrl += '&shown_theme=' + encodeURIComponent(shownTheme); window.PublicNavigationGuard.allowNavigation(() => { window.location.href = targetUrl; }); } diff --git a/components/theme-client.php b/components/theme-client.php new file mode 100644 index 0000000..c2f76a1 --- /dev/null +++ b/components/theme-client.php @@ -0,0 +1,70 @@ + + diff --git a/components/theme-preference.php b/components/theme-preference.php new file mode 100644 index 0000000..0dce82f --- /dev/null +++ b/components/theme-preference.php @@ -0,0 +1,16 @@ + 128 + || !preg_match('/\Av1:(light|dark):(manual|preland):([A-Za-z0-9_-]{16,64})\z/', $value, $match)) { + return null; + } + return ['theme' => $match[1], 'source' => $match[2], 'revision' => $match[3]]; +} + +function prelandThemeResolve(string $fallback): string +{ + return prelandThemePreference()['theme'] ?? $fallback; +} diff --git a/components/theme-style.php b/components/theme-style.php new file mode 100644 index 0000000..0aad049 --- /dev/null +++ b/components/theme-style.php @@ -0,0 +1,27 @@ + diff --git a/components/theme-switch.php b/components/theme-switch.php new file mode 100644 index 0000000..48019e1 --- /dev/null +++ b/components/theme-switch.php @@ -0,0 +1,15 @@ + 'Тема оформления', 'light' => 'Светлая тема', 'dark' => 'Тёмная тема']; +$themeInitial = isset($themeInitial) && $themeInitial === 'dark' ? 'dark' : 'light'; +$themeEscape = static fn(string $value): string => htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8'); +?> +
+ + +
+ diff --git a/index.php b/index.php index a76b206..3c7504e 100644 --- a/index.php +++ b/index.php @@ -3,6 +3,7 @@ require_once __DIR__ . '/i18n.php'; require_once __DIR__ . '/components/asset-url.php'; require_once __DIR__ . '/components/site-domain.php'; +require_once __DIR__ . '/components/theme-preference.php'; $configFile = __DIR__ . '/config.json'; $config = file_exists($configFile) ? json_decode(file_get_contents($configFile), true) : []; @@ -70,6 +71,8 @@ if (isset($config['utm']) && is_array($config['utm'])) { } $UTM = $get_params; +$themeInitial = prelandThemeResolve(($UTM['theme'] ?? 'light') === 'dark' ? 'dark' : 'light'); +$themeSurface = 'preland'; $host = $config['host'] ?? ($_SERVER['HTTP_HOST'] ?? ''); $apiHost = $host ? (preg_match('#^https?://#', $host) ? rtrim($host, '/') : 'https://' . rtrim($host, '/')) : ''; diff --git a/messages/en/app.php b/messages/en/app.php index a608f11..7332af8 100644 --- a/messages/en/app.php +++ b/messages/en/app.php @@ -370,4 +370,7 @@ return [ 'х4' => '4×', '—' => '—', '₽' => '$', + 'Тема оформления' => 'Appearance', + 'Светлая тема' => 'Light theme', + 'Тёмная тема' => 'Dark theme', ]; diff --git a/release/assets.json b/release/assets.json index 90e0f8e..f4a56da 100644 --- a/release/assets.json +++ b/release/assets.json @@ -724,7 +724,7 @@ "source": "https://zoomstage.online/images/sprite.svg" }, "style.css": { - "sha256": "cc88246e4efb4f2884d0f268b897950cd75620998e0d3ad9985076ce1021abfc", + "sha256": "839d51ff9e4541fc1a585c6c715a2d6b26685eefb615c90f372a4d82e24b1f63", "source": "https://zoomstage.online/style.css" }, "style.min.c.css": { diff --git a/style.css b/style.css index 70ea04d..573c0ec 100644 --- a/style.css +++ b/style.css @@ -108,39 +108,6 @@ body { border: none; } -.theme-toggle { - align-items: center; - background-color: #F8F9FB; - border: 1px solid #E5E5E7; - padding: 4px; - box-sizing: border-box; - border-radius: 16px; -} - -.theme-toggle .btn { - padding: 0; - border: none; - border-radius: 0; - background-color: transparent; - display: flex; - justify-content: center; - align-items: center; - width: 32px; - height: 32px; - outline: none !important; - box-shadow: none; - color: #728490; -} - -.theme-toggle .btn:not(.dark) { - color: #990694; -} - -.theme-toggle .btn svg { - width: 20px; - height: 20px; -} - .image-compare { border-radius: 24px; } @@ -2137,15 +2104,6 @@ body:not(.theme-dark) .theme-light-hidden { background-image: url(../images/dark-bg.jpg); } -.theme-dark .theme-toggle { - background-color: #0B0B10; - border-color: #323539; -} - -.theme-dark .theme-toggle .btn:not(.dark) { - color: #323539; -} - .theme-dark .btn-header { border-color: #fff; color: #fff; @@ -2368,11 +2326,7 @@ body:not(.theme-dark) .theme-light-hidden { border-radius: 8px; } - .theme-toggle { - border-radius: 8px; - } - - .image-examples { +.image-examples { display: block; text-align: center; } diff --git a/template.php b/template.php index 81df815..8a7b095 100644 --- a/template.php +++ b/template.php @@ -3,6 +3,8 @@ + + <?= $page_title ?? $default_title ?> @@ -51,7 +53,8 @@ - + +
diff --git a/tests/check-theme-preference.php b/tests/check-theme-preference.php new file mode 100644 index 0000000..313186e --- /dev/null +++ b/tests/check-theme-preference.php @@ -0,0 +1,20 @@ +$theme,'source'=>$source,'revision'=>$revision] + || prelandThemeResolve($theme === 'light' ? 'dark' : 'light') !== $theme) throw new RuntimeException('Canonical preference не побеждает fallback'); + $checks++; +} +foreach ([null, [], false, 1, '', "v1:light:manual:$revision\n", 'v1:dark:other:'.$revision, 'v1:dark:manual:'.str_repeat('a',15), 'v1:dark:manual:'.str_repeat('a',65), str_repeat('a',129)] as $value) { + $_COOKIE['theme_choice'] = $value; + if (prelandThemePreference() !== null || prelandThemeResolve('light') !== 'light') throw new RuntimeException('Невалидная preference принята'); + $checks++; +} +$source = file_get_contents(dirname(__DIR__) . '/components/form.php'); +if (!str_contains($source, 'const shownTheme = window.ZoomzillaTheme.current();') + || !str_contains($source, "targetUrl += '&shown_theme=' + encodeURIComponent(shownTheme)")) throw new RuntimeException('Handoff должен читать показанную тему в момент навигации'); +echo "OK: $checks standalone theme cases + dynamic shown_theme handoff\n";