feat: split logo scale by viewport

This commit is contained in:
3252a8
2026-06-03 12:24:18 +03:00
parent 602cbc7ee6
commit d8fe88eb01
14 changed files with 269 additions and 97 deletions
+9 -1
View File
@@ -1517,6 +1517,8 @@ _INITIAL_THEME_TOKEN_CSS_MAP = {
"font_logo": "--font-logo",
"font_mono": "--font-mono",
"home_logo_scale": "--home-logo-scale",
"home_logo_scale_desktop": "--home-logo-scale-desktop",
"home_logo_scale_mobile": "--home-logo-scale-mobile",
"admin_bg": "--admin-bg",
"admin_surface": "--admin-surface",
"admin_surface_2": "--admin-surface-2",
@@ -1528,6 +1530,12 @@ _INITIAL_THEME_TOKEN_CSS_MAP = {
"admin_dim": "--admin-dim",
}
_INITIAL_THEME_LOGO_SCALE_TOKENS = {
"home_logo_scale",
"home_logo_scale_desktop",
"home_logo_scale_mobile",
}
def _theme_css_href_for_html(theme: Any) -> str:
css_file = str(getattr(theme, "css_file", "") or "").strip()
@@ -1571,7 +1579,7 @@ def _initial_theme_head_markup(request: web.Request, theme: Any, primary_color:
tokens = tokens if isinstance(tokens, dict) else {}
declarations = []
for token_key, css_name in _INITIAL_THEME_TOKEN_CSS_MAP.items():
if token_key == "home_logo_scale":
if token_key in _INITIAL_THEME_LOGO_SCALE_TOKENS:
try:
scale = float(tokens.get(token_key) or 0)
except (TypeError, ValueError):
+4 -2
View File
@@ -54,6 +54,8 @@ class ThemeTokens(BaseModel):
font_logo: Optional[str] = None
font_mono: Optional[str] = None
home_logo_scale: Optional[int] = None
home_logo_scale_desktop: Optional[int] = None
home_logo_scale_mobile: Optional[int] = None
admin_bg: Optional[str] = None
admin_surface: Optional[str] = None
admin_surface_2: Optional[str] = None
@@ -80,14 +82,14 @@ class ThemeTokens(BaseModel):
hex_value = "".join(char * 2 for char in hex_value)
return f"#{hex_value}"
@field_validator("home_logo_scale")
@field_validator("home_logo_scale", "home_logo_scale_desktop", "home_logo_scale_mobile")
@classmethod
def _normalize_home_logo_scale(cls, value: Optional[int]) -> Optional[int]:
if value is None:
return None
scale = int(value)
if scale < 50 or scale > 300:
raise ValueError("home_logo_scale must be between 50 and 300 percent")
raise ValueError("home logo scale must be between 50 and 300 percent")
return scale
+47 -44
View File
@@ -115,7 +115,8 @@ Email-шаблоны берут тот же бренд из настроек в
"font_sans": "Inter, system-ui, sans-serif",
"font_logo": "Inter, system-ui, sans-serif",
"font_mono": "\"JetBrains Mono\", \"Fira Code\", monospace",
"home_logo_scale": 120,
"home_logo_scale_desktop": 120,
"home_logo_scale_mobile": 95,
"admin_bg": "#05040a",
"admin_surface": "#11101c",
"admin_surface_2": "#090815",
@@ -131,52 +132,54 @@ Email-шаблоны берут тот же бренд из настроек в
Поля верхнего уровня:
| Поле | Назначение |
| --- | --- |
| `key` | Уникальный ключ темы, 1-64 символа: латиница, цифры, `_` и `-`. Если ключ не указан, берется имя папки. |
| `names` | Локализованные названия, например `ru` и `en`. |
| `enabled` | Показывать тему пользователям. Отключенная тема не попадает в публичный каталог. |
| `default` | Делает тему выбранной по умолчанию, если `WEBAPP_DEFAULT_THEME` не задан. |
| Поле | Назначение |
| -------------------- | ------------------------------------------------------------------------------------------------------- |
| `key` | Уникальный ключ темы, 1-64 символа: латиница, цифры, `_` и `-`. Если ключ не указан, берется имя папки. |
| `names` | Локализованные названия, например `ru` и `en`. |
| `enabled` | Показывать тему пользователям. Отключенная тема не попадает в публичный каталог. |
| `default` | Делает тему выбранной по умолчанию, если `WEBAPP_DEFAULT_THEME` не задан. |
| `use_primary_accent` | Если `true`, тема может получить accent из настройки внешнего вида, когда в `tokens.accent` ничего нет. |
| `use_in_admin` | Если `false`, пользовательская часть использует тему, но админка откатывается на `dark`. |
| `css_file` | CSS-файл внутри папки темы. Может быть `style.css` или вложенный путь вроде `css/theme.css`. |
| `assets_version` | Версия ассетов. Для встроенных тем используется для обновления старых файлов в `data/themes`. |
| `tokens` | Дизайн-токены, которые превращаются в CSS-переменные на `.app-shell`. |
| `use_in_admin` | Если `false`, пользовательская часть использует тему, но админка откатывается на `dark`. |
| `css_file` | CSS-файл внутри папки темы. Может быть `style.css` или вложенный путь вроде `css/theme.css`. |
| `assets_version` | Версия ассетов. Для встроенных тем используется для обновления старых файлов в `data/themes`. |
| `tokens` | Дизайн-токены, которые превращаются в CSS-переменные на `.app-shell`. |
## Токены
Поддерживаемые токены:
| Токен | CSS-переменная | Что меняет |
| --- | --- | --- |
| `color_scheme` | `color-scheme` | Нативная светлая/темная схема браузера: `dark` или `light`. |
| `style_preset` | CSS-класс пресета | Сейчас `win95`/`windows95` добавляет `theme-preset-win95`; остальные значения не дают специального класса. |
| `accent` | `--accent` | Главный акцент: активные элементы, кнопки, прогресс, фокус. Только hex `#RGB` или `#RRGGBB`. |
| `bg` | `--bg` | Основной фон приложения. |
| `panel` | `--panel` | Основные карточки и поверхности. |
| `panel_2` | `--panel-2` | Вторичные поверхности. |
| `panel_3` | `--panel-3` | Поверхности повышенной вложенности, dropdown/popover. |
| `border` | `--border` | Обычные границы. |
| `border_strong` | `--border-strong` | Усиленные границы и hover-состояния. |
| `text` | `--text` | Основной текст. |
| `muted` | `--muted` | Вторичный текст. |
| `dim` | `--dim` | Еще более тихий текст и служебные подписи. |
| `danger` | `--danger` | Ошибки и опасные действия. |
| `blue` | `--blue` | Синий вспомогательный цвет. |
| `radius` | `--radius` | Базовый радиус карточек, кнопок и контролов. |
| `font_sans` | `--font-sans` | Основной шрифт интерфейса. |
| `font_logo` | `--font-logo` | Шрифт бренда и заголовка. |
| `font_mono` | `--font-mono` | Моноширинный шрифт. |
| `home_logo_scale` | `--home-logo-scale` | Масштаб логотипа на главной и входе, от `50` до `300` процентов. |
| `admin_bg` | `--admin-bg` | Фон админ-панели. |
| `admin_surface` | `--admin-surface` | Основные карточки админки. |
| `admin_surface_2` | `--admin-surface-2` | Вторичные поверхности админки. |
| `admin_elev` | `--admin-elev` | Elevated-поверхности админки. |
| `admin_border` | `--admin-border` | Границы админки. |
| `admin_border_strong` | `--admin-border-strong` | Усиленные границы админки. |
| `admin_text` | `--admin-text` | Основной текст админки. |
| `admin_muted` | `--admin-muted` | Вторичный текст админки. |
| `admin_dim` | `--admin-dim` | Тихие подписи админки. |
| Токен | CSS-переменная | Что меняет |
| ------------------------- | --------------------------- | ---------------------------------------------------------------------------------------------------------- |
| `color_scheme` | `color-scheme` | Нативная светлая/темная схема браузера: `dark` или `light`. |
| `style_preset` | CSS-класс пресета | Сейчас `win95`/`windows95` добавляет `theme-preset-win95`; остальные значения не дают специального класса. |
| `accent` | `--accent` | Главный акцент: активные элементы, кнопки, прогресс, фокус. Только hex `#RGB` или `#RRGGBB`. |
| `bg` | `--bg` | Основной фон приложения. |
| `panel` | `--panel` | Основные карточки и поверхности. |
| `panel_2` | `--panel-2` | Вторичные поверхности. |
| `panel_3` | `--panel-3` | Поверхности повышенной вложенности, dropdown/popover. |
| `border` | `--border` | Обычные границы. |
| `border_strong` | `--border-strong` | Усиленные границы и hover-состояния. |
| `text` | `--text` | Основной текст. |
| `muted` | `--muted` | Вторичный текст. |
| `dim` | `--dim` | Еще более тихий текст и служебные подписи. |
| `danger` | `--danger` | Ошибки и опасные действия. |
| `blue` | `--blue` | Синий вспомогательный цвет. |
| `radius` | `--radius` | Базовый радиус карточек, кнопок и контролов. |
| `font_sans` | `--font-sans` | Основной шрифт интерфейса. |
| `font_logo` | `--font-logo` | Шрифт бренда и заголовка. |
| `font_mono` | `--font-mono` | Моноширинный шрифт. |
| `home_logo_scale_desktop` | `--home-logo-scale-desktop` | Масштаб логотипа на desktop layout, от `50` до `300` процентов. |
| `home_logo_scale_mobile` | `--home-logo-scale-mobile` | Масштаб логотипа на mobile layout, от `50` до `300` процентов. |
| `home_logo_scale` | `--home-logo-scale` | Legacy fallback для старых тем; используется, если desktop/mobile token не задан. |
| `admin_bg` | `--admin-bg` | Фон админ-панели. |
| `admin_surface` | `--admin-surface` | Основные карточки админки. |
| `admin_surface_2` | `--admin-surface-2` | Вторичные поверхности админки. |
| `admin_elev` | `--admin-elev` | Elevated-поверхности админки. |
| `admin_border` | `--admin-border` | Границы админки. |
| `admin_border_strong` | `--admin-border-strong` | Усиленные границы админки. |
| `admin_text` | `--admin-text` | Основной текст админки. |
| `admin_muted` | `--admin-muted` | Вторичный текст админки. |
| `admin_dim` | `--admin-dim` | Тихие подписи админки. |
Если `css_file` не задан, интерфейс полностью строится на токенах и общих стилях. Если `css_file` задан, токены все равно применяются первыми, а CSS темы может уточнить или полностью переопределить внешний вид.
@@ -255,7 +258,8 @@ CSS можно писать для пользовательской части
content: "";
width: 16px;
height: 16px;
background: url("/webapp-theme-assets/neon/icons/spark.png") center / contain no-repeat;
background: url("/webapp-theme-assets/neon/icons/spark.png") center / contain
no-repeat;
}
```
@@ -303,7 +307,7 @@ CSS можно писать для пользовательской части
7. Подберите accent и масштаб логотипа.
В админке можно менять accent и `home_logo_scale` без ручного редактирования JSON. При сохранении backend перепишет `theme.json` в `WEBAPP_THEMES_DIR`, выставит ровно один `default` и сбросит кеш публичных настроек.
В админке можно менять accent, `home_logo_scale_desktop` и `home_logo_scale_mobile` без ручного редактирования JSON. При сохранении backend перепишет `theme.json` в `WEBAPP_THEMES_DIR`, выставит ровно один `default` и сбросит кеш публичных настроек. Старый `home_logo_scale` сохраняется как fallback для уже существующих тем.
8. Добавьте `style.css`, если токенов мало.
@@ -330,7 +334,6 @@ CSS можно писать для пользовательской части
11. Сделайте тему дефолтной.
Есть два способа:
- в админке выбрать тему и сохранить;
- указать `WEBAPP_DEFAULT_THEME=neon` в `.env`, если нужен жесткий override на уровне окружения.
@@ -178,10 +178,8 @@
return `#${hex}`;
}
function homeLogoScale(theme) {
const scale = Number(theme.tokens?.home_logo_scale || 100);
if (!Number.isFinite(scale)) return 100;
return Math.min(300, Math.max(50, Math.round(scale)));
function homeLogoScale(theme, mode) {
return themesStore.resolveThemeHomeLogoScale(theme, mode);
}
function openThemeAccentPicker(theme) {
@@ -296,8 +294,8 @@
themesStore.setThemeAccent(theme.key, value);
}
function setThemeHomeLogoScale(theme, value) {
themesStore.setThemeHomeLogoScale(theme.key, value);
function setThemeHomeLogoScale(theme, mode, value) {
themesStore.setThemeHomeLogoScale(theme.key, mode, value);
}
function isThemeControlTarget(target) {
@@ -586,11 +584,11 @@
<span>{at("themes_use_in_admin", {}, "Использовать в админке")}</span>
</label>
<div class="admin-theme-card-option appearance-logo-scale-row">
<span
<span class="appearance-logo-scale-label"
>{at(
"appearance_theme_home_logo_scale",
"appearance_theme_home_logo_scale_desktop",
{},
"Логотип на главной и входе"
"Логотип на десктопе"
)}</span
>
<RangeInput
@@ -598,9 +596,13 @@
min="50"
max="300"
step="5"
ariaLabel={at("appearance_theme_home_logo_scale", {}, "Home logo scale")}
value={homeLogoScale(theme)}
onValueChange={(value) => setThemeHomeLogoScale(theme, value)}
ariaLabel={at(
"appearance_theme_home_logo_scale_desktop",
{},
"Desktop logo scale"
)}
value={homeLogoScale(theme, "desktop")}
onValueChange={(value) => setThemeHomeLogoScale(theme, "desktop", value)}
/>
<span class="appearance-logo-scale-value">
<Input
@@ -609,8 +611,44 @@
min="50"
max="300"
step="5"
value={homeLogoScale(theme)}
oninput={(event) => setThemeHomeLogoScale(theme, event.currentTarget.value)}
value={homeLogoScale(theme, "desktop")}
oninput={(event) =>
setThemeHomeLogoScale(theme, "desktop", event.currentTarget.value)}
/>
%
</span>
</div>
<div class="admin-theme-card-option appearance-logo-scale-row">
<span class="appearance-logo-scale-label"
>{at(
"appearance_theme_home_logo_scale_mobile",
{},
"Логотип на мобильных"
)}</span
>
<RangeInput
class="appearance-logo-scale-range"
min="50"
max="300"
step="5"
ariaLabel={at(
"appearance_theme_home_logo_scale_mobile",
{},
"Mobile logo scale"
)}
value={homeLogoScale(theme, "mobile")}
onValueChange={(value) => setThemeHomeLogoScale(theme, "mobile", value)}
/>
<span class="appearance-logo-scale-value">
<Input
class="input"
type="number"
min="50"
max="300"
step="5"
value={homeLogoScale(theme, "mobile")}
oninput={(event) =>
setThemeHomeLogoScale(theme, "mobile", event.currentTarget.value)}
/>
%
</span>
@@ -847,10 +885,15 @@
.appearance-logo-scale-row {
display: grid;
grid-template-columns: auto minmax(96px, 1fr) auto;
grid-template-columns: minmax(112px, 0.8fr) minmax(96px, 1fr) auto;
width: 100%;
}
.appearance-logo-scale-label {
min-width: 0;
line-height: 1.25;
}
:global(.appearance-logo-scale-range) {
width: 100%;
accent-color: var(--accent);
@@ -910,5 +953,13 @@
.appearance-url-row {
grid-template-columns: 1fr;
}
.appearance-logo-scale-row {
grid-template-columns: minmax(0, 1fr) auto;
}
:global(.appearance-logo-scale-range) {
grid-column: 1 / -1;
}
}
</style>
+7 -3
View File
@@ -29,9 +29,13 @@ function applyDemoThemeTokens(catalog) {
const themes = catalog?.themes || [];
for (const theme of themes) {
theme.tokens = theme.tokens || {};
const currentScale = Number(theme.tokens.home_logo_scale);
if (!Number.isFinite(currentScale) || currentScale > DEMO_HOME_LOGO_SCALE) {
theme.tokens.home_logo_scale = DEMO_HOME_LOGO_SCALE;
const logoScaleKeys = ["home_logo_scale_desktop", "home_logo_scale_mobile"];
for (const key of ["home_logo_scale", ...logoScaleKeys]) {
if (key !== "home_logo_scale" && !(key in theme.tokens)) continue;
const currentScale = Number(theme.tokens[key]);
if (!Number.isFinite(currentScale) || currentScale > DEMO_HOME_LOGO_SCALE) {
theme.tokens[key] = DEMO_HOME_LOGO_SCALE;
}
}
}
}
+37 -17
View File
@@ -4,6 +4,23 @@ function cloneCatalog(catalog) {
import { writable } from "svelte/store";
const HOME_LOGO_SCALE_TOKEN = {
desktop: "home_logo_scale_desktop",
mobile: "home_logo_scale_mobile",
};
function normalizeHomeLogoScale(scale) {
if (String(scale ?? "").trim() === "") scale = 100;
const numeric = Number(scale);
return Number.isFinite(numeric) ? Math.min(300, Math.max(50, Math.round(numeric))) : 100;
}
function resolveThemeHomeLogoScale(theme, mode = "desktop") {
const tokens = theme?.tokens || {};
const modeKey = HOME_LOGO_SCALE_TOKEN[mode] || HOME_LOGO_SCALE_TOKEN.desktop;
return normalizeHomeLogoScale(tokens[modeKey] ?? tokens.home_logo_scale ?? 100);
}
export function createThemesStore({ api, onThemesSaved, flash, at }) {
const state = writable({
themesCatalog: { default_theme: "dark", themes: [] },
@@ -214,27 +231,29 @@ export function createThemesStore({ api, onThemesSaved, flash, at }) {
}));
}
function setThemeHomeLogoScale(key, scale) {
if (String(scale ?? "").trim() === "") scale = 100;
const numeric = Number(scale);
const nextScale = Number.isFinite(numeric)
? Math.min(300, Math.max(50, Math.round(numeric)))
: 100;
function setThemeHomeLogoScale(key, mode, scale) {
const normalizedMode = mode === "mobile" ? "mobile" : "desktop";
const nextScale = normalizeHomeLogoScale(scale);
state.update((s) => ({
...s,
themesCatalog: {
...s.themesCatalog,
themes: (s.themesCatalog.themes || []).map((theme) =>
theme.key === key
? {
...theme,
tokens: {
...(theme.tokens || {}),
home_logo_scale: nextScale === 100 ? null : nextScale,
},
}
: theme
),
themes: (s.themesCatalog.themes || []).map((theme) => {
if (theme.key !== key) return theme;
const desktopScale =
normalizedMode === "desktop" ? nextScale : resolveThemeHomeLogoScale(theme, "desktop");
const mobileScale =
normalizedMode === "mobile" ? nextScale : resolveThemeHomeLogoScale(theme, "mobile");
return {
...theme,
tokens: {
...(theme.tokens || {}),
home_logo_scale: null,
home_logo_scale_desktop: desktopScale === 100 ? null : desktopScale,
home_logo_scale_mobile: mobileScale === 100 ? null : mobileScale,
},
};
}),
},
}));
}
@@ -246,6 +265,7 @@ export function createThemesStore({ api, onThemesSaved, flash, at }) {
setCurrentTheme,
setThemeAccent,
setThemeHomeLogoScale,
resolveThemeHomeLogoScale,
togglePrimaryAccent,
toggleAdminUse,
uploadLogoFile,
+48
View File
@@ -113529,6 +113529,54 @@ export const DEMO_DATASET = {
},
},
},
{
key: "admin_appearance_theme_home_logo_scale_desktop",
audience: "internal",
values: {
ru: {
base: "Логотип на десктопе",
fallback: "Логотип на десктопе",
effective: "Логотип на десктопе",
override: "",
overridden: false,
updated_at: null,
updated_by: null,
},
en: {
base: "Desktop logo scale",
fallback: "Логотип на десктопе",
effective: "Desktop logo scale",
override: "",
overridden: false,
updated_at: null,
updated_by: null,
},
},
},
{
key: "admin_appearance_theme_home_logo_scale_mobile",
audience: "internal",
values: {
ru: {
base: "Логотип на мобильных",
fallback: "Логотип на мобильных",
effective: "Логотип на мобильных",
override: "",
overridden: false,
updated_at: null,
updated_by: null,
},
en: {
base: "Mobile logo scale",
fallback: "Логотип на мобильных",
effective: "Mobile logo scale",
override: "",
overridden: false,
updated_at: null,
updated_by: null,
},
},
},
{
key: "admin_appearance_themes_sub",
audience: "internal",
+9 -1
View File
@@ -33,6 +33,8 @@ const TOKEN_TO_CSS_VAR = {
font_logo: "--font-logo",
font_mono: "--font-mono",
home_logo_scale: "--home-logo-scale",
home_logo_scale_desktop: "--home-logo-scale-desktop",
home_logo_scale_mobile: "--home-logo-scale-mobile",
admin_bg: "--admin-bg",
admin_surface: "--admin-surface",
admin_surface_2: "--admin-surface-2",
@@ -44,6 +46,12 @@ const TOKEN_TO_CSS_VAR = {
admin_dim: "--admin-dim",
};
const LOGO_SCALE_TOKEN_KEYS = new Set([
"home_logo_scale",
"home_logo_scale_desktop",
"home_logo_scale_mobile",
]);
export function themeTokensToInlineStyle(tokens, primaryFallback = "#00fe7a", options = {}) {
const t = tokens && typeof tokens === "object" ? tokens : {};
const parts = [];
@@ -54,7 +62,7 @@ export function themeTokensToInlineStyle(tokens, primaryFallback = "#00fe7a", op
if (key === "accent") continue;
const value = t[key];
if (value === undefined || value === null || value === "") continue;
if (key === "home_logo_scale") {
if (LOGO_SCALE_TOKEN_KEYS.has(key)) {
const scale = Number(value);
if (!Number.isFinite(scale) || scale <= 0) continue;
parts.push(`${cssVar}:${scale / 100}`);
+7
View File
@@ -111,6 +111,7 @@ input {
}
.app-shell {
--home-logo-scale-effective: var(--home-logo-scale-mobile, var(--home-logo-scale, 1));
min-height: 100dvh;
background: var(--bg, #02070b) !important;
}
@@ -119,3 +120,9 @@ input {
--desktop-rail-width: 252px;
--desktop-page-gutter: clamp(28px, 4vw, 72px);
}
@media (min-width: 1024px) {
.app-shell {
--home-logo-scale-effective: var(--home-logo-scale-desktop, var(--home-logo-scale, 1));
}
}
+12 -12
View File
@@ -64,9 +64,9 @@ a {
}
.app-shell .loader .brand-mark.brand-mark-lg {
width: calc(4.125rem * var(--home-logo-scale, 1));
height: calc(4.125rem * var(--home-logo-scale, 1));
font-size: calc(2.875rem * var(--home-logo-scale, 1));
width: calc(4.125rem * var(--home-logo-scale-effective, 1));
height: calc(4.125rem * var(--home-logo-scale-effective, 1));
font-size: calc(2.875rem * var(--home-logo-scale-effective, 1));
}
.app-header,
@@ -2207,15 +2207,15 @@ a {
}
.app-shell .home-brand .brand-mark.brand-mark-xl {
width: calc(6rem * var(--home-logo-scale, 1));
height: calc(6rem * var(--home-logo-scale, 1));
font-size: calc(4.375rem * var(--home-logo-scale, 1));
width: calc(6rem * var(--home-logo-scale-effective, 1));
height: calc(6rem * var(--home-logo-scale-effective, 1));
font-size: calc(4.375rem * var(--home-logo-scale-effective, 1));
}
.app-shell .login-brand-auth .brand-mark.brand-mark-xl {
width: calc(116px * var(--home-logo-scale, 1));
height: calc(116px * var(--home-logo-scale, 1));
font-size: calc(84px * var(--home-logo-scale, 1));
width: calc(116px * var(--home-logo-scale-effective, 1));
height: calc(116px * var(--home-logo-scale-effective, 1));
font-size: calc(84px * var(--home-logo-scale-effective, 1));
}
.home-bottom {
@@ -3027,9 +3027,9 @@ a {
.app-shell .home-brand .brand-mark,
.app-shell .home-brand .brand-mark.brand-mark-lg,
.app-shell .home-brand .brand-mark.brand-mark-xl {
width: calc(clamp(180px, 18vw, 260px) * var(--home-logo-scale, 1));
height: calc(clamp(180px, 18vw, 260px) * var(--home-logo-scale, 1));
font-size: calc(clamp(124px, 13vw, 184px) * var(--home-logo-scale, 1));
width: calc(clamp(180px, 18vw, 260px) * var(--home-logo-scale-effective, 1));
height: calc(clamp(180px, 18vw, 260px) * var(--home-logo-scale-effective, 1));
font-size: calc(clamp(124px, 13vw, 184px) * var(--home-logo-scale-effective, 1));
}
.home-bottom {
+2
View File
@@ -1963,6 +1963,8 @@
"admin_appearance_theme_accent_empty": "Not set",
"admin_appearance_theme_accent_placeholder": "Not set",
"admin_appearance_theme_home_logo_scale": "Home and login logo",
"admin_appearance_theme_home_logo_scale_desktop": "Desktop logo scale",
"admin_appearance_theme_home_logo_scale_mobile": "Mobile logo scale",
"admin_appearance_themes_sub": "Global theme, accent color, and preview",
"admin_appearance_themes_title": "Themes",
"admin_appearance_use_custom_favicon": "Use a separate favicon",
+2
View File
@@ -1963,6 +1963,8 @@
"admin_appearance_theme_accent_empty": "Не задан",
"admin_appearance_theme_accent_placeholder": "Не задан",
"admin_appearance_theme_home_logo_scale": "Логотип на главной и входе",
"admin_appearance_theme_home_logo_scale_desktop": "Логотип на десктопе",
"admin_appearance_theme_home_logo_scale_mobile": "Логотип на мобильных",
"admin_appearance_themes_sub": "Глобальная тема, accent color и предпросмотр",
"admin_appearance_themes_title": "Темы",
"admin_appearance_use_custom_favicon": "Использовать отдельную favicon",
+8
View File
@@ -559,10 +559,14 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
def test_home_logo_scale_rules_beat_late_loaded_admin_brand_styles(self):
css = Path("frontend/src/styles/webapp.css").read_text(encoding="utf-8")
base_css = Path("frontend/src/styles/base.css").read_text(encoding="utf-8")
self.assertIn(".app-shell .home-brand .brand-mark.brand-mark-xl", css)
self.assertIn(".app-shell .login-brand-auth .brand-mark.brand-mark-xl", css)
self.assertIn(".app-shell .loader .brand-mark.brand-mark-lg", css)
self.assertIn("--home-logo-scale-effective", css)
self.assertIn("--home-logo-scale-mobile", base_css)
self.assertIn("--home-logo-scale-desktop", base_css)
def test_prune_unused_appearance_assets_keeps_only_referenced_logo_and_favicons(self):
with tempfile.TemporaryDirectory() as tmpdir:
@@ -647,6 +651,8 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
cfg = builtin_webapp_themes_config("#123456")
theme = cfg.theme_by_key("light")
theme.tokens.home_logo_scale = 135
theme.tokens.home_logo_scale_desktop = 150
theme.tokens.home_logo_scale_mobile = 85
request = SimpleNamespace(get=lambda key, default="": "nonce-value")
markup = subscription_webapp._initial_theme_head_markup(request, theme, "#123456")
@@ -655,6 +661,8 @@ class WebAppAssetTests(unittest.IsolatedAsyncioTestCase):
self.assertIn('nonce="nonce-value"', markup)
self.assertIn("--accent:#123456", markup)
self.assertIn("--home-logo-scale:1.35", markup)
self.assertIn("--home-logo-scale-desktop:1.5", markup)
self.assertIn("--home-logo-scale-mobile:0.85", markup)
self.assertIn("color-scheme:light", markup)
def test_theme_asset_version_bumps_for_saved_default_css_theme(self):
+11 -2
View File
@@ -296,7 +296,7 @@ class WebappThemesConfigTests(unittest.TestCase):
self.assertEqual(cfg.theme_by_key("custom").tokens.accent, "#00ff88")
def test_theme_home_logo_scale_is_public_token(self):
def test_theme_home_logo_scales_are_public_tokens(self):
cfg = WebappThemesConfig(
default_theme="custom",
themes=[
@@ -304,7 +304,12 @@ class WebappThemesConfigTests(unittest.TestCase):
"key": "custom",
"enabled": True,
"default": True,
"tokens": {"color_scheme": "dark", "home_logo_scale": 135},
"tokens": {
"color_scheme": "dark",
"home_logo_scale": 135,
"home_logo_scale_desktop": 150,
"home_logo_scale_mobile": 85,
},
}
],
)
@@ -313,7 +318,11 @@ class WebappThemesConfigTests(unittest.TestCase):
custom = payload["themes"][0]
self.assertEqual(cfg.theme_by_key("custom").tokens.home_logo_scale, 135)
self.assertEqual(cfg.theme_by_key("custom").tokens.home_logo_scale_desktop, 150)
self.assertEqual(cfg.theme_by_key("custom").tokens.home_logo_scale_mobile, 85)
self.assertEqual(custom["tokens"]["home_logo_scale"], 135)
self.assertEqual(custom["tokens"]["home_logo_scale_desktop"], 150)
self.assertEqual(custom["tokens"]["home_logo_scale_mobile"], 85)
def test_theme_accent_rejects_non_hex_values(self):
with self.assertRaises(ValueError):