feat(admin-ui): softer pre-material styling and dark theme
- Rebuild admin console styling on semantic CSS variables (light + dark palettes) - Soften palette, shadows and corner radii for a calmer pre-material look - Add theme provider with light/dark toggle in the topbar and login header - Respect prefers-color-scheme and persist choice, with anti-FOUC inline script - Polish Russian translations (yo letters, consistent 'Звёзды' currency, wording) - Rebuild dist bundle to match the updated source
This commit is contained in:
parent
045e0f6db4
commit
ad9d535edc
15 changed files with 601 additions and 251 deletions
9
cmd/telesrv-admin/web/dist/assets/index-CqgHld2y.js
vendored
Normal file
9
cmd/telesrv-admin/web/dist/assets/index-CqgHld2y.js
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
cmd/telesrv-admin/web/dist/assets/index-DuOdm70q.css
vendored
Normal file
1
cmd/telesrv-admin/web/dist/assets/index-DuOdm70q.css
vendored
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
43
cmd/telesrv-admin/web/dist/index.html
vendored
43
cmd/telesrv-admin/web/dist/index.html
vendored
|
|
@ -1,13 +1,30 @@
|
|||
<!doctype html>
|
||||
<html lang="en" translate="no">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>telesrv admin</title>
|
||||
<script type="module" crossorigin src="/assets/index-Duge82ST.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DHdrFM5j.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
<!doctype html>
|
||||
<html lang="en" translate="no">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>telesrv admin</title>
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var stored = localStorage.getItem("telesrv.admin.theme");
|
||||
var theme =
|
||||
stored === "light" || stored === "dark"
|
||||
? stored
|
||||
: window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
} catch (e) {
|
||||
document.documentElement.setAttribute("data-theme", "light");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
<script type="module" crossorigin src="/assets/index-CqgHld2y.js"></script>
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-DuOdm70q.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
|||
|
|
@ -4,6 +4,23 @@
|
|||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>telesrv admin</title>
|
||||
<script>
|
||||
(function () {
|
||||
try {
|
||||
var stored = localStorage.getItem("telesrv.admin.theme");
|
||||
var theme =
|
||||
stored === "light" || stored === "dark"
|
||||
? stored
|
||||
: window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches
|
||||
? "dark"
|
||||
: "light";
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
} catch (e) {
|
||||
document.documentElement.setAttribute("data-theme", "light");
|
||||
}
|
||||
})();
|
||||
</script>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import { useEffect, useState, type ReactNode } from "react";
|
|||
import { api } from "../api";
|
||||
import { LanguageSwitch, useI18n } from "../i18n";
|
||||
import { type Navigate, type RouteState, routeSubtitle, routeTitle } from "../routing";
|
||||
import { ThemeSwitch } from "../theme";
|
||||
import { AppLink } from "./AppLink";
|
||||
|
||||
export function BootScreen() {
|
||||
|
|
@ -123,6 +124,7 @@ export function Shell({
|
|||
<h1>{routeTitle(route.path, t)}</h1>
|
||||
</div>
|
||||
<div className="topbar-actions">
|
||||
<ThemeSwitch />
|
||||
<LanguageSwitch />
|
||||
<span className="actor-pill">{t("layout.actor", { actor })}</span>
|
||||
<button className="btn ghost icon-text" type="button" onClick={logout} title={t("layout.logout")}>
|
||||
|
|
|
|||
|
|
@ -84,6 +84,8 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"language.en": "EN",
|
||||
"language.zh": "中文",
|
||||
"language.ru": "RU",
|
||||
"theme.switchToDark": "Switch to dark theme",
|
||||
"theme.switchToLight": "Switch to light theme",
|
||||
"login.heading": "Operations Admin",
|
||||
"login.body": "Enter credentials to open the console.",
|
||||
"login.secret": "Admin password or token",
|
||||
|
|
@ -456,6 +458,8 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"language.en": "EN",
|
||||
"language.zh": "中文",
|
||||
"language.ru": "RU",
|
||||
"theme.switchToDark": "切换到深色主题",
|
||||
"theme.switchToLight": "切换到浅色主题",
|
||||
"login.heading": "运维后台",
|
||||
"login.body": "输入凭据后进入控制台。",
|
||||
"login.secret": "管理员密码或 token",
|
||||
|
|
@ -772,7 +776,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"common.group": "Группа",
|
||||
"common.id": "ID",
|
||||
"common.limit": "Лимит",
|
||||
"common.loading": "Загрузка...",
|
||||
"common.loading": "Загрузка…",
|
||||
"common.member": "Участник",
|
||||
"common.members": "Участники",
|
||||
"common.messageId": "ID сообщения",
|
||||
|
|
@ -794,7 +798,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"common.updatedAt": "Обновлено",
|
||||
"common.username": "Имя пользователя",
|
||||
"common.valid": "Действителен",
|
||||
"common.verified": "Подтвержден",
|
||||
"common.verified": "Подтверждён",
|
||||
"common.views": "Просмотры",
|
||||
"common.yes": "Да",
|
||||
"route.accounts": "Аккаунты",
|
||||
|
|
@ -805,15 +809,15 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"route.dashboardSubtitle": "Консоль / Обзор",
|
||||
"route.messages": "Аудит сообщений",
|
||||
"route.messagesSubtitle": "Консоль / Сообщения",
|
||||
"route.gifts": "Звездные подарки",
|
||||
"route.giftsSubtitle": "Консоль / Звездные подарки",
|
||||
"route.gifts": "Звёздные подарки",
|
||||
"route.giftsSubtitle": "Консоль / Звёздные подарки",
|
||||
"layout.navigation": "Навигация",
|
||||
"layout.primaryNav": "Основное меню",
|
||||
"layout.dashboard": "Обзор",
|
||||
"layout.accounts": "Аккаунты",
|
||||
"layout.channels": "Супергруппы / Каналы",
|
||||
"layout.messages": "Сообщения",
|
||||
"layout.gifts": "Звездные подарки",
|
||||
"layout.gifts": "Звёздные подарки",
|
||||
"layout.privateMessages": "Личные",
|
||||
"layout.groupMessages": "Группы",
|
||||
"layout.runtime": "Среда выполнения",
|
||||
|
|
@ -823,16 +827,18 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"layout.readOnly": "Только чтение",
|
||||
"layout.writeOps": "Операции записи",
|
||||
"layout.dryRun": "Тестовый запуск",
|
||||
"layout.actor": "Вход выполнен как: {actor}",
|
||||
"layout.actor": "Вы вошли как: {actor}",
|
||||
"layout.logout": "Выйти",
|
||||
"language.en": "EN",
|
||||
"language.zh": "中文",
|
||||
"language.ru": "RU",
|
||||
"theme.switchToDark": "Тёмная тема",
|
||||
"theme.switchToLight": "Светлая тема",
|
||||
"login.heading": "Панель администратора",
|
||||
"login.body": "Введите учетные данные для входа в консоль.",
|
||||
"login.body": "Введите учётные данные для входа в консоль.",
|
||||
"login.secret": "Пароль или токен администратора",
|
||||
"login.submit": "Войти",
|
||||
"login.submitting": "Вход...",
|
||||
"login.submitting": "Вход…",
|
||||
"dashboard.eyebrow": "Состояние системы",
|
||||
"dashboard.title": "Обзор консоли",
|
||||
"dashboard.readPath": "Путь чтения",
|
||||
|
|
@ -840,8 +846,8 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"dashboard.writePath": "Путь записи",
|
||||
"dashboard.executionPolicy": "Политика выполнения",
|
||||
"dashboard.dryRunFirst": "Сначала тестовый запуск",
|
||||
"dashboard.accountsText": "Статус аккаунтов, премиум, верификация, сессии.",
|
||||
"dashboard.channelsText": "Публичные каналы и группы, количество участников, статус верификации.",
|
||||
"dashboard.accountsText": "Статус аккаунтов, Premium, подтверждение, сессии.",
|
||||
"dashboard.channelsText": "Публичные каналы и группы, число участников, статус подтверждения.",
|
||||
"dashboard.messagesText": "Ящики сообщений, обновления, состояние исходящих.",
|
||||
"dashboard.strip.dryRun": "Все опасные действия начинаются с тестового запуска",
|
||||
"dashboard.strip.token": "Браузер никогда не сохраняет внутренние токены",
|
||||
|
|
@ -858,10 +864,10 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"account.userID": "ID пользователя",
|
||||
"account.phone": "Телефон",
|
||||
"account.lastActive": "Последняя активность",
|
||||
"account.notVerified": "Не подтвержден",
|
||||
"account.notVerified": "Не подтверждён",
|
||||
"account.notPremium": "Без Premium",
|
||||
"account.premiumUntil": "Premium истекает",
|
||||
"account.starsBalance": "Баланс Звезд",
|
||||
"account.starsBalance": "Баланс Звёзд",
|
||||
"account.startingGrantApplied": "стартовый бонус начислен",
|
||||
"account.startingGrantPending": "ожидает стартового бонуса",
|
||||
"account.activeSessions": "Авторизованные устройства",
|
||||
|
|
@ -894,17 +900,17 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"account.premiumMonthsAria": "Указать срок действия Premium в месяцах",
|
||||
"account.setPremium": "Выдать Premium",
|
||||
"account.clearPremium": "Снять Premium",
|
||||
"account.starsAmount": "Количество звёзд",
|
||||
"account.starsAmountAria": "Указать количество начисляемых звёзд",
|
||||
"account.grantStars": "Начислить звёзды",
|
||||
"account.starsAmount": "Количество Звёзд",
|
||||
"account.starsAmountAria": "Указать количество начисляемых Звёзд",
|
||||
"account.grantStars": "Начислить Звёзды",
|
||||
"account.setVerified": "Подтвердить аккаунт",
|
||||
"account.clearVerified": "Снять подтверждение",
|
||||
"channel.pageTitle": "Супергруппы и каналы",
|
||||
"channel.recentUpdated": "Недавно обновленные",
|
||||
"channel.recentUpdated": "Недавно обновлённые",
|
||||
"channel.currentPage": "Объекты на странице",
|
||||
"channel.megagroups": "Супергруппы",
|
||||
"channel.broadcasts": "Каналы",
|
||||
"channel.verifiedCount": "Подтверждено",
|
||||
"channel.verifiedCount": "Подтверждённые",
|
||||
"channel.searchPlaceholder": "ID канала / имя пользователя / название",
|
||||
"channel.channelID": "ID канала",
|
||||
"channel.kind": "Тип",
|
||||
|
|
@ -943,7 +949,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"messages.outgoing": "Исходящее",
|
||||
"messages.incoming": "Входящее",
|
||||
"messages.ownerPeer": "Владелец / Собеседник",
|
||||
"messages.deleteSelected": "Указать и удалить выбранные сообщения",
|
||||
"messages.deleteSelected": "Удалить выбранные сообщения",
|
||||
"messages.idsPlaceholder": "ID сообщений через запятую",
|
||||
"messages.revoke": "Удалить для обеих сторон",
|
||||
"messages.previewDelete": "Тестовое удаление",
|
||||
|
|
@ -989,7 +995,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"messages.channelGroup": "Канал / Группа",
|
||||
"messages.pinned": "Закреплено",
|
||||
"messages.channelPost": "Пост в канале",
|
||||
"gifts.pageTitle": "Каталог звездных подарков",
|
||||
"gifts.pageTitle": "Каталог звёздных подарков",
|
||||
"gifts.eyebrow": "Каталог, неизменяемые версии и файлы анимаций",
|
||||
"gifts.total": "Подарков в каталоге",
|
||||
"gifts.enabled": "Включено",
|
||||
|
|
@ -1000,7 +1006,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"gifts.listSummary": "Показано {shown} из {total}",
|
||||
"gifts.idRevision": "ID / Версия",
|
||||
"gifts.price": "Цена / Конвертация",
|
||||
"gifts.importTitle": "Импорт звездного подарка",
|
||||
"gifts.importTitle": "Импорт звёздного подарка",
|
||||
"gifts.importEyebrow": "Управление каталогом подарков",
|
||||
"gifts.newRevision": "Создать версию для подарка #{id}",
|
||||
"gifts.importHint": "Загрузите файл TGS или обычный Lottie JSON. Lottie нормализуется и сжимается в формат TGS.",
|
||||
|
|
@ -1031,8 +1037,8 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"gifts.changeFile": "Изменить файл",
|
||||
"gifts.title": "Отображаемое название",
|
||||
"gifts.titlePlaceholder": "например, Праздничная звезда",
|
||||
"gifts.stars": "Цена в Звездах",
|
||||
"gifts.convertStars": "Звезд при конвертации",
|
||||
"gifts.stars": "Цена в Звёздах",
|
||||
"gifts.convertStars": "Звёзд при конвертации",
|
||||
"gifts.sortOrder": "Порядок сортировки",
|
||||
"gifts.reason": "Причина для аудита",
|
||||
"gifts.reasonPlaceholder": "Кратко опишите причину импорта этого подарка",
|
||||
|
|
@ -1047,7 +1053,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"gifts.replace": "Новая версия",
|
||||
"gifts.disable": "Отключить",
|
||||
"gifts.enable": "Включить",
|
||||
"gifts.empty": "Звездные подарки еще не импортированы.",
|
||||
"gifts.empty": "Звёздные подарки ещё не импортированы.",
|
||||
"gifts.emptyHint": "Импортируйте первую анимацию, чтобы начать наполнение каталога.",
|
||||
"gifts.validationReady": "Проверка пройдена",
|
||||
"gifts.validationHint": "Проверьте нормализованные метаданные и подтвердите импорт.",
|
||||
|
|
@ -1061,7 +1067,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"collectibles.noPoolHint": "Опубликуйте модели, узоры и фоны для активации улучшений.",
|
||||
"collectibles.publishNew": "Опубликовать новую неизменяемую версию",
|
||||
"collectibles.immutableHint": "Тестовый запуск проверяет каждый файл и структуру набора атрибутов перед активацией версии.",
|
||||
"collectibles.upgradeStars": "Цена улучшения в Звездах",
|
||||
"collectibles.upgradeStars": "Цена улучшения в Звёздах",
|
||||
"collectibles.supply": "Уникальный тираж",
|
||||
"collectibles.slug": "Публичный префикс ссылки (slug)",
|
||||
"collectibles.models": "Модели",
|
||||
|
|
@ -1071,7 +1077,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"collectibles.pattern": "Узор",
|
||||
"collectibles.backdrop": "Фон",
|
||||
"collectibles.rarity": "Редкость ‰",
|
||||
"collectibles.rarityHint": "В каждой категории должно быть не менее двух атрибутов. При добавлении и удалении веса permille перераспределяются до 1000.",
|
||||
"collectibles.rarityHint": "В каждой категории должно быть не менее двух атрибутов. Значения в промилле задают относительные веса обычного улучшения; при добавлении или удалении они перераспределяются до суммы 1000.",
|
||||
"collectibles.minimumAttributes": "Модели, узоры и фоны должны содержать не менее двух атрибутов в каждой категории.",
|
||||
"collectibles.duplicateBackdropID": "ID фонов в одном наборе должны быть уникальными.",
|
||||
"collectibles.colorHint": "Цвета сохраняются как 24-битные RGB-значения.",
|
||||
|
|
@ -1093,10 +1099,10 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"auth.lastActive": "Последняя активность",
|
||||
"auth.revokeCurrent": "Отозвать текущую",
|
||||
"auth.keepCurrent": "Оставить текущую",
|
||||
"auth.revokeAll": "Разлогинить все устройства",
|
||||
"auth.revokeAll": "Отозвать все устройства",
|
||||
"picker.userPlaceholder": "Поиск по user_id / телефону / имени пользователя",
|
||||
"picker.channelPlaceholder": "Поиск по channel_id / имени пользователя / названию",
|
||||
"picker.verified": "Подтвержденные",
|
||||
"picker.verified": "Подтверждённые",
|
||||
"picker.regular": "Обычные",
|
||||
"action.reasonRequired": "Пожалуйста, укажите причину операции",
|
||||
"action.flow": "Процесс выполнения",
|
||||
|
|
@ -1111,7 +1117,7 @@ const translations: Record<Language, Record<string, string>> = {
|
|||
"action.commandID": "ID команды",
|
||||
"action.status": "Статус",
|
||||
"action.dryRun": "Тестовый запуск",
|
||||
"action.runAgain": "Запустить тестовый запуск снова",
|
||||
"action.runAgain": "Повторить тестовый запуск",
|
||||
"action.runDry": "Сначала выполните тестовый запуск",
|
||||
"action.confirm": "Подтвердить выполнение",
|
||||
"audit.id": "ID",
|
||||
|
|
|
|||
|
|
@ -2,12 +2,15 @@ import React from "react";
|
|||
import ReactDOM from "react-dom/client";
|
||||
import { App } from "./App";
|
||||
import { I18nProvider } from "./i18n";
|
||||
import { ThemeProvider } from "./theme";
|
||||
import "./styles.css";
|
||||
|
||||
ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||
<React.StrictMode>
|
||||
<I18nProvider>
|
||||
<App />
|
||||
</I18nProvider>
|
||||
<ThemeProvider>
|
||||
<I18nProvider>
|
||||
<App />
|
||||
</I18nProvider>
|
||||
</ThemeProvider>
|
||||
</React.StrictMode>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { useState } from "react";
|
|||
import { api, errorMessage } from "../api";
|
||||
import { Alert } from "../components/ui";
|
||||
import { LanguageSwitch, useI18n } from "../i18n";
|
||||
import { ThemeSwitch } from "../theme";
|
||||
|
||||
export function LoginPage({ onLogin }: { onLogin: (actor: string) => void }) {
|
||||
const { t } = useI18n();
|
||||
|
|
@ -36,6 +37,7 @@ export function LoginPage({ onLogin }: { onLogin: (actor: string) => void }) {
|
|||
</span>
|
||||
</div>
|
||||
<div className="login-head-actions">
|
||||
<ThemeSwitch />
|
||||
<LanguageSwitch />
|
||||
<span className="login-chip">{t("app.localAccess")}</span>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,24 +1,155 @@
|
|||
:root {
|
||||
color-scheme: light;
|
||||
--bg: #f3f5f7;
|
||||
|
||||
/* Surfaces */
|
||||
--bg: #eef1f5;
|
||||
--bg-accent: #e7ecf1;
|
||||
--panel: #ffffff;
|
||||
--panel-subtle: #f8fafb;
|
||||
--panel-strong: #eef2f5;
|
||||
--line: #d9e1e8;
|
||||
--line-strong: #c2ccd6;
|
||||
--text: #101828;
|
||||
--muted: #667085;
|
||||
--muted-2: #98a2b3;
|
||||
--brand: #176d61;
|
||||
--brand-2: #245b9d;
|
||||
--good: #167447;
|
||||
--warn: #a15c07;
|
||||
--danger: #b42318;
|
||||
--sidebar: #11161d;
|
||||
--sidebar-soft: #1b222b;
|
||||
--sidebar-line: #2c3541;
|
||||
--focus: rgba(23, 109, 97, 0.16);
|
||||
--shadow: 0 18px 52px rgba(16, 24, 40, 0.14);
|
||||
--panel-subtle: #f5f8fb;
|
||||
--panel-strong: #eef2f6;
|
||||
--surface-soft: #f2f7f6;
|
||||
--overlay: rgba(24, 34, 47, 0.42);
|
||||
--topbar-bg: rgba(255, 255, 255, 0.86);
|
||||
|
||||
/* Lines */
|
||||
--line: #e5eaf0;
|
||||
--line-strong: #d3dce4;
|
||||
|
||||
/* Text */
|
||||
--heading: #253040;
|
||||
--text: #333f4d;
|
||||
--text-soft: #45525f;
|
||||
--muted: #6d7885;
|
||||
--muted-2: #9aa4b1;
|
||||
|
||||
/* Brand */
|
||||
--brand: #1f7d6f;
|
||||
--brand-strong: #196155;
|
||||
--brand-2: #3a6cae;
|
||||
--brand-tint: #e8f4f0;
|
||||
--brand-tint-border: #c8e2db;
|
||||
--brand-tint-text: #235d53;
|
||||
|
||||
/* Semantic */
|
||||
--good: #1f8a57;
|
||||
--good-tint: #eaf6ef;
|
||||
--good-border: #c1e1cf;
|
||||
--warn: #a86a12;
|
||||
--warn-tint: #fcf4e4;
|
||||
--warn-border: #e7d09e;
|
||||
--danger: #c0392b;
|
||||
--danger-tint: #fcefec;
|
||||
--danger-border: #eecac3;
|
||||
--danger-text: #8f2f27;
|
||||
|
||||
/* Accent (collectibles / craft) */
|
||||
--purple: #6a4fa3;
|
||||
--purple-tint: #f4effb;
|
||||
--purple-border: #dcd0f0;
|
||||
--purple-text: #5a4590;
|
||||
|
||||
/* Inputs & controls */
|
||||
--input-bg: #ffffff;
|
||||
--btn-bg: #ffffff;
|
||||
--btn-text: #29323d;
|
||||
--btn-hover: #f4f7fa;
|
||||
--switch-track: #c8d0d6;
|
||||
|
||||
/* Code / JSON blocks */
|
||||
--code-bg: #1b2733;
|
||||
--code-text: #d6e3ef;
|
||||
--code-border: #2b3a49;
|
||||
|
||||
/* Sidebar */
|
||||
--sidebar: #1c2530;
|
||||
--sidebar-soft: #26313d;
|
||||
--sidebar-line: #313c4a;
|
||||
--sidebar-row: #232d38;
|
||||
--sidebar-text: #dbe3ec;
|
||||
--sidebar-muted: #8b98a8;
|
||||
--sidebar-faint: #7c8a9a;
|
||||
--sidebar-heading: #ffffff;
|
||||
|
||||
/* Effects */
|
||||
--focus: rgba(31, 125, 111, 0.16);
|
||||
--shadow: 0 12px 34px rgba(24, 39, 56, 0.1);
|
||||
--shadow-sm: 0 2px 10px rgba(24, 39, 56, 0.05);
|
||||
--shadow-brand: 0 8px 22px rgba(31, 125, 111, 0.22);
|
||||
|
||||
/* Radii */
|
||||
--radius-xs: 8px;
|
||||
--radius-sm: 9px;
|
||||
--radius: 11px;
|
||||
--radius-lg: 14px;
|
||||
}
|
||||
|
||||
[data-theme="dark"] {
|
||||
color-scheme: dark;
|
||||
|
||||
--bg: #0f141a;
|
||||
--bg-accent: #131a22;
|
||||
--panel: #171f28;
|
||||
--panel-subtle: #1c2530;
|
||||
--panel-strong: #212c38;
|
||||
--surface-soft: #1a232d;
|
||||
--overlay: rgba(5, 8, 12, 0.62);
|
||||
--topbar-bg: rgba(21, 28, 36, 0.86);
|
||||
|
||||
--line: #29333f;
|
||||
--line-strong: #38434f;
|
||||
|
||||
--heading: #eef3f8;
|
||||
--text: #d5dde6;
|
||||
--text-soft: #c2ccd6;
|
||||
--muted: #98a4b1;
|
||||
--muted-2: #6d7885;
|
||||
|
||||
--brand: #37a596;
|
||||
--brand-strong: #45b6a6;
|
||||
--brand-2: #6fa8e6;
|
||||
--brand-tint: #14322d;
|
||||
--brand-tint-border: #245349;
|
||||
--brand-tint-text: #7fd3c4;
|
||||
|
||||
--good: #47c281;
|
||||
--good-tint: #12301f;
|
||||
--good-border: #245639;
|
||||
--warn: #e0aa4d;
|
||||
--warn-tint: #322810;
|
||||
--warn-border: #574413;
|
||||
--danger: #e6695c;
|
||||
--danger-tint: #35201d;
|
||||
--danger-border: #5c332d;
|
||||
--danger-text: #f0a49b;
|
||||
|
||||
--purple: #ac90e2;
|
||||
--purple-tint: #221b31;
|
||||
--purple-border: #3d3357;
|
||||
--purple-text: #c9b6ef;
|
||||
|
||||
--input-bg: #131a22;
|
||||
--btn-bg: #1e2731;
|
||||
--btn-text: #dbe2ea;
|
||||
--btn-hover: #26313d;
|
||||
--switch-track: #3a454f;
|
||||
|
||||
--code-bg: #0c1218;
|
||||
--code-text: #cdd9e5;
|
||||
--code-border: #232f3b;
|
||||
|
||||
--sidebar: #10151b;
|
||||
--sidebar-soft: #1c242f;
|
||||
--sidebar-line: #262f3a;
|
||||
--sidebar-row: #161d25;
|
||||
--sidebar-text: #cbd4de;
|
||||
--sidebar-muted: #7c8794;
|
||||
--sidebar-faint: #6f7b88;
|
||||
--sidebar-heading: #f0f4f8;
|
||||
|
||||
--focus: rgba(55, 165, 150, 0.24);
|
||||
--shadow: 0 16px 40px rgba(0, 0, 0, 0.46);
|
||||
--shadow-sm: 0 2px 12px rgba(0, 0, 0, 0.38);
|
||||
--shadow-brand: 0 8px 22px rgba(55, 165, 150, 0.26);
|
||||
}
|
||||
|
||||
* {
|
||||
|
|
@ -35,7 +166,10 @@ body {
|
|||
margin: 0;
|
||||
color: var(--text);
|
||||
background: var(--bg);
|
||||
font: 13px/1.45 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
font: 13px/1.5 Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
text-rendering: optimizeLegibility;
|
||||
transition: background-color 200ms ease, color 200ms ease;
|
||||
}
|
||||
|
||||
button,
|
||||
|
|
@ -64,7 +198,7 @@ a {
|
|||
gap: 16px;
|
||||
overflow-y: auto;
|
||||
padding: 18px 12px;
|
||||
color: #eef2f6;
|
||||
color: var(--sidebar-text);
|
||||
background: var(--sidebar);
|
||||
border-right: 1px solid var(--sidebar-line);
|
||||
}
|
||||
|
|
@ -82,7 +216,7 @@ a {
|
|||
}
|
||||
|
||||
.brand-elevated .brand-mark {
|
||||
box-shadow: 0 8px 24px rgba(23, 109, 97, 0.26);
|
||||
box-shadow: var(--shadow-brand);
|
||||
}
|
||||
|
||||
.brand-mark {
|
||||
|
|
@ -93,7 +227,7 @@ a {
|
|||
color: #ffffff;
|
||||
background: var(--brand);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
|
|
@ -106,16 +240,17 @@ a {
|
|||
.brand small {
|
||||
display: block;
|
||||
margin-top: 3px;
|
||||
color: #aeb8c4;
|
||||
color: var(--sidebar-muted);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
.sidebar-label {
|
||||
padding: 0 8px;
|
||||
color: #8492a6;
|
||||
color: var(--sidebar-faint);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
||||
.nav-list {
|
||||
|
|
@ -136,26 +271,27 @@ a {
|
|||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 0 10px;
|
||||
color: #8fa0b4;
|
||||
color: var(--sidebar-muted);
|
||||
background: transparent;
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
font-size: 12px;
|
||||
font-weight: 800;
|
||||
text-align: left;
|
||||
transition: color 140ms ease, background-color 140ms ease, border-color 140ms ease;
|
||||
}
|
||||
|
||||
.nav-section-toggle:hover,
|
||||
.nav-section.active .nav-section-toggle {
|
||||
color: #ffffff;
|
||||
color: var(--sidebar-heading);
|
||||
background: var(--sidebar-soft);
|
||||
border-color: #34404d;
|
||||
border-color: var(--sidebar-line);
|
||||
}
|
||||
|
||||
.nav-section-chevron {
|
||||
justify-self: end;
|
||||
color: #8fa0b4;
|
||||
color: var(--sidebar-muted);
|
||||
transition: transform 140ms ease;
|
||||
}
|
||||
|
||||
|
|
@ -176,24 +312,25 @@ a {
|
|||
align-items: center;
|
||||
gap: 9px;
|
||||
padding: 0 10px;
|
||||
color: #c6d0dc;
|
||||
color: var(--sidebar-text);
|
||||
border: 1px solid transparent;
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
transition: color 140ms ease, background-color 140ms ease, border-color 140ms ease;
|
||||
}
|
||||
|
||||
.nav-dot {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
justify-self: center;
|
||||
background: #687789;
|
||||
background: var(--sidebar-faint);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
.nav-item:hover,
|
||||
.nav-item.active {
|
||||
color: #ffffff;
|
||||
color: var(--sidebar-heading);
|
||||
background: var(--sidebar-soft);
|
||||
border-color: #34404d;
|
||||
border-color: var(--sidebar-line);
|
||||
}
|
||||
|
||||
.nav-item.active .nav-dot {
|
||||
|
|
@ -213,14 +350,14 @@ a {
|
|||
align-items: center;
|
||||
gap: 7px;
|
||||
padding: 0 8px;
|
||||
color: #cbd5df;
|
||||
background: #171d25;
|
||||
border: 1px solid #27313c;
|
||||
border-radius: 7px;
|
||||
color: var(--sidebar-text);
|
||||
background: var(--sidebar-row);
|
||||
border: 1px solid var(--sidebar-line);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.runtime-row strong {
|
||||
color: #ffffff;
|
||||
color: var(--sidebar-heading);
|
||||
font-size: 11px;
|
||||
}
|
||||
|
||||
|
|
@ -238,13 +375,14 @@ a {
|
|||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
padding: 12px 24px;
|
||||
background: rgba(255, 255, 255, 0.94);
|
||||
background: var(--topbar-bg);
|
||||
border-bottom: 1px solid var(--line);
|
||||
backdrop-filter: blur(12px);
|
||||
}
|
||||
|
||||
.topbar h1 {
|
||||
margin: 2px 0 0;
|
||||
color: var(--heading);
|
||||
font-size: 20px;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
|
@ -281,6 +419,7 @@ a {
|
|||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
font-weight: 800;
|
||||
transition: color 140ms ease, background-color 140ms ease;
|
||||
}
|
||||
|
||||
.language-switch button.active {
|
||||
|
|
@ -293,12 +432,36 @@ a {
|
|||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.theme-toggle {
|
||||
display: inline-grid;
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
place-items: center;
|
||||
color: var(--muted);
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
cursor: pointer;
|
||||
transition: color 160ms ease, background-color 160ms ease, border-color 160ms ease;
|
||||
}
|
||||
|
||||
.theme-toggle:hover {
|
||||
color: var(--brand);
|
||||
border-color: var(--brand-tint-border);
|
||||
background: var(--brand-tint);
|
||||
}
|
||||
|
||||
.theme-toggle:focus-visible {
|
||||
outline: 2px solid var(--brand);
|
||||
outline-offset: 2px;
|
||||
}
|
||||
|
||||
.actor-pill {
|
||||
display: inline-flex;
|
||||
min-height: 30px;
|
||||
align-items: center;
|
||||
padding: 0 10px;
|
||||
color: #344054;
|
||||
color: var(--text-soft);
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
|
|
@ -315,4 +478,5 @@ a {
|
|||
font-size: 11px;
|
||||
font-weight: 800;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.04em;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,8 @@
|
|||
min-width: 0;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.overview-band {
|
||||
|
|
@ -25,6 +26,7 @@
|
|||
.section-head h2,
|
||||
.modal h2 {
|
||||
margin: 0;
|
||||
color: var(--heading);
|
||||
font-size: 18px;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
|
@ -47,7 +49,7 @@
|
|||
padding: 10px;
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.status-item span,
|
||||
|
|
@ -70,16 +72,16 @@
|
|||
|
||||
.status-item.good,
|
||||
.metric.good {
|
||||
border-color: #afd8bf;
|
||||
border-color: var(--good-border);
|
||||
}
|
||||
|
||||
.status-item.warn,
|
||||
.metric.warn {
|
||||
border-color: #e7c77e;
|
||||
border-color: var(--warn-border);
|
||||
}
|
||||
|
||||
.metric.danger {
|
||||
border-color: #efb4ad;
|
||||
border-color: var(--danger-border);
|
||||
}
|
||||
|
||||
.command-grid {
|
||||
|
|
@ -97,11 +99,15 @@
|
|||
padding: 14px;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: border-color 160ms ease, box-shadow 160ms ease, transform 160ms ease;
|
||||
}
|
||||
|
||||
.launcher:hover {
|
||||
border-color: var(--brand);
|
||||
border-color: var(--brand-tint-border);
|
||||
box-shadow: var(--shadow);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.launcher-icon {
|
||||
|
|
@ -110,9 +116,9 @@
|
|||
height: 38px;
|
||||
place-items: center;
|
||||
color: var(--brand);
|
||||
background: #edf7f4;
|
||||
border: 1px solid #c9e2dc;
|
||||
border-radius: 8px;
|
||||
background: var(--brand-tint);
|
||||
border: 1px solid var(--brand-tint-border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.launcher-copy {
|
||||
|
|
@ -121,6 +127,7 @@
|
|||
}
|
||||
|
||||
.launcher-copy strong {
|
||||
color: var(--heading);
|
||||
font-size: 15px;
|
||||
}
|
||||
|
||||
|
|
@ -140,10 +147,10 @@
|
|||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 0 10px;
|
||||
color: #344054;
|
||||
color: var(--text-soft);
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.page-frame {
|
||||
|
|
@ -165,7 +172,7 @@
|
|||
padding: 10px;
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.toolbar {
|
||||
|
|
@ -195,9 +202,9 @@
|
|||
min-width: 0;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
background: #ffffff;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.picker-head {
|
||||
|
|
@ -206,7 +213,7 @@
|
|||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 8px;
|
||||
color: #344054;
|
||||
color: var(--text-soft);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
|
|
@ -217,10 +224,10 @@
|
|||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 7px 9px;
|
||||
color: #0f3f38;
|
||||
background: #eef8f5;
|
||||
border: 1px solid #b9dcd3;
|
||||
border-radius: 7px;
|
||||
color: var(--brand-tint-text);
|
||||
background: var(--brand-tint);
|
||||
border: 1px solid var(--brand-tint-border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.selected-entity strong,
|
||||
|
|
@ -237,8 +244,9 @@
|
|||
}
|
||||
|
||||
.selected-entity div span {
|
||||
color: #52606d;
|
||||
color: var(--brand-tint-text);
|
||||
font-size: 11px;
|
||||
opacity: 0.85;
|
||||
}
|
||||
|
||||
.picker-search {
|
||||
|
|
@ -250,7 +258,7 @@
|
|||
padding: 0 6px 0 9px;
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.picker-search input {
|
||||
|
|
@ -267,7 +275,7 @@
|
|||
max-height: 236px;
|
||||
overflow: auto;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.picker-row {
|
||||
|
|
@ -278,7 +286,7 @@
|
|||
gap: 8px;
|
||||
padding: 6px 8px;
|
||||
color: var(--text);
|
||||
background: #ffffff;
|
||||
background: var(--panel);
|
||||
border: 0;
|
||||
border-bottom: 1px solid var(--line);
|
||||
cursor: pointer;
|
||||
|
|
@ -291,7 +299,7 @@
|
|||
|
||||
.picker-row:hover,
|
||||
.picker-row.selected {
|
||||
background: #f3f8f6;
|
||||
background: var(--surface-soft);
|
||||
}
|
||||
|
||||
.picker-row strong,
|
||||
|
|
@ -311,18 +319,24 @@
|
|||
|
||||
.picker-error {
|
||||
color: var(--danger);
|
||||
background: #fff2f0;
|
||||
border: 1px solid #efb4ad;
|
||||
border-radius: 7px;
|
||||
background: var(--danger-tint);
|
||||
border: 1px solid var(--danger-border);
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
input,
|
||||
textarea {
|
||||
color: var(--text);
|
||||
background: #ffffff;
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
outline: none;
|
||||
transition: border-color 140ms ease, box-shadow 140ms ease;
|
||||
}
|
||||
|
||||
input::placeholder,
|
||||
textarea::placeholder {
|
||||
color: var(--muted-2);
|
||||
}
|
||||
|
||||
input {
|
||||
|
|
@ -366,9 +380,10 @@ textarea:focus {
|
|||
width: min(380px, 100%);
|
||||
height: 34px;
|
||||
padding: 0 10px;
|
||||
background: #ffffff;
|
||||
color: var(--text);
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.searchbox input {
|
||||
|
|
@ -386,16 +401,17 @@ textarea:focus {
|
|||
justify-content: center;
|
||||
gap: 6px;
|
||||
padding: 0 12px;
|
||||
color: #1d2939;
|
||||
background: #ffffff;
|
||||
color: var(--btn-text);
|
||||
background: var(--btn-bg);
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease, box-shadow 140ms ease;
|
||||
}
|
||||
|
||||
.btn:hover:not(:disabled) {
|
||||
background: #f7f9fb;
|
||||
background: var(--btn-hover);
|
||||
}
|
||||
|
||||
.btn:disabled {
|
||||
|
|
@ -410,7 +426,8 @@ textarea:focus {
|
|||
}
|
||||
|
||||
.btn.primary:hover:not(:disabled) {
|
||||
background: #12594f;
|
||||
background: var(--brand-strong);
|
||||
border-color: var(--brand-strong);
|
||||
}
|
||||
|
||||
.btn.ghost {
|
||||
|
|
@ -419,22 +436,24 @@ textarea:focus {
|
|||
|
||||
.btn.danger {
|
||||
color: var(--danger);
|
||||
background: #fff7f5;
|
||||
border-color: #efb4ad;
|
||||
background: var(--danger-tint);
|
||||
border-color: var(--danger-border);
|
||||
}
|
||||
|
||||
.btn.danger:hover:not(:disabled) {
|
||||
background: #ffeceb;
|
||||
background: var(--danger-tint);
|
||||
border-color: var(--danger);
|
||||
}
|
||||
|
||||
.btn.warn {
|
||||
color: var(--warn);
|
||||
background: #fff8ec;
|
||||
border-color: #e7c77e;
|
||||
background: var(--warn-tint);
|
||||
border-color: var(--warn-border);
|
||||
}
|
||||
|
||||
.btn.warn:hover:not(:disabled) {
|
||||
background: #fff1d6;
|
||||
background: var(--warn-tint);
|
||||
border-color: var(--warn);
|
||||
}
|
||||
|
||||
.btn:disabled,
|
||||
|
|
@ -442,7 +461,7 @@ textarea:focus {
|
|||
.btn.warn:disabled,
|
||||
.btn.danger:disabled {
|
||||
color: var(--muted-2);
|
||||
background: #f3f5f7;
|
||||
background: var(--panel-strong);
|
||||
border-color: var(--line);
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
|
@ -476,8 +495,9 @@ textarea:focus {
|
|||
.table-wrap {
|
||||
width: 100%;
|
||||
overflow-x: auto;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.data-table {
|
||||
|
|
@ -500,13 +520,13 @@ textarea:focus {
|
|||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 0;
|
||||
color: #475467;
|
||||
color: var(--muted);
|
||||
background: var(--panel-strong);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.data-table tbody tr:hover {
|
||||
background: #fbfcfd;
|
||||
background: var(--panel-subtle);
|
||||
}
|
||||
|
||||
.data-table tr:last-child td {
|
||||
|
|
@ -528,29 +548,29 @@ textarea:focus {
|
|||
min-height: 22px;
|
||||
align-items: center;
|
||||
padding: 1px 8px;
|
||||
color: #4f5b68;
|
||||
background: #f3f6f8;
|
||||
border: 1px solid #d7e0e8;
|
||||
color: var(--muted);
|
||||
background: var(--panel-strong);
|
||||
border: 1px solid var(--line-strong);
|
||||
border-radius: 999px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.badge.good {
|
||||
color: var(--good);
|
||||
background: #eef8f2;
|
||||
border-color: #b9dcc7;
|
||||
background: var(--good-tint);
|
||||
border-color: var(--good-border);
|
||||
}
|
||||
|
||||
.badge.danger {
|
||||
color: var(--danger);
|
||||
background: #fff2f0;
|
||||
border-color: #efb4ad;
|
||||
background: var(--danger-tint);
|
||||
border-color: var(--danger-border);
|
||||
}
|
||||
|
||||
.badge.warn {
|
||||
color: var(--warn);
|
||||
background: #fff8e7;
|
||||
border-color: #e7c77e;
|
||||
background: var(--warn-tint);
|
||||
border-color: var(--warn-border);
|
||||
}
|
||||
|
||||
.empty-cell {
|
||||
|
|
|
|||
|
|
@ -18,10 +18,11 @@
|
|||
padding: 14px;
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.entity-title {
|
||||
color: var(--heading);
|
||||
font-size: 20px;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
|
|
@ -41,10 +42,10 @@
|
|||
.about-text {
|
||||
margin: 0;
|
||||
padding: 10px;
|
||||
color: #344054;
|
||||
background: #fbfcfd;
|
||||
color: var(--text-soft);
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.section-block,
|
||||
|
|
@ -54,7 +55,8 @@
|
|||
padding: 12px;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
box-shadow: var(--shadow-sm);
|
||||
}
|
||||
|
||||
.section-head {
|
||||
|
|
@ -79,7 +81,7 @@
|
|||
|
||||
.dock-title {
|
||||
padding-bottom: 4px;
|
||||
color: #344054;
|
||||
color: var(--text-soft);
|
||||
font-weight: 800;
|
||||
border-bottom: 1px solid var(--line);
|
||||
}
|
||||
|
|
@ -184,7 +186,7 @@
|
|||
padding: 10px;
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.operation-title {
|
||||
|
|
@ -192,6 +194,7 @@
|
|||
width: 100%;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
color: var(--heading);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
|
|
@ -212,10 +215,10 @@
|
|||
align-items: flex-start;
|
||||
gap: 8px;
|
||||
padding: 9px 10px;
|
||||
color: #8a251d;
|
||||
background: #fff2f0;
|
||||
border: 1px solid #efb4ad;
|
||||
border-radius: 8px;
|
||||
color: var(--danger-text);
|
||||
background: var(--danger-tint);
|
||||
border: 1px solid var(--danger-border);
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.json-block {
|
||||
|
|
@ -223,10 +226,10 @@
|
|||
overflow: auto;
|
||||
margin: 0;
|
||||
padding: 12px;
|
||||
color: #d8e6f0;
|
||||
background: #141a22;
|
||||
border: 1px solid #2a3542;
|
||||
border-radius: 8px;
|
||||
color: var(--code-text);
|
||||
background: var(--code-bg);
|
||||
border: 1px solid var(--code-border);
|
||||
border-radius: var(--radius);
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
|
|
@ -250,12 +253,12 @@
|
|||
color: var(--muted);
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
.gift-metrics .metric {
|
||||
min-height: 68px;
|
||||
padding: 12px;
|
||||
background: linear-gradient(145deg, #ffffff, #f6f9f9);
|
||||
background: var(--panel-subtle);
|
||||
}
|
||||
|
||||
.gift-metrics .metric strong { font-size: 17px; }
|
||||
|
|
@ -265,12 +268,12 @@
|
|||
flex: 0 0 auto;
|
||||
place-items: center;
|
||||
color: var(--brand);
|
||||
background: #eaf6f3;
|
||||
border: 1px solid #c7e3dc;
|
||||
background: var(--brand-tint);
|
||||
border: 1px solid var(--brand-tint-border);
|
||||
}
|
||||
|
||||
.gift-format-chips { display: flex; flex: 0 0 auto; flex-wrap: wrap; justify-content: flex-end; gap: 6px; }
|
||||
.gift-format-chips span { padding: 4px 8px; color: #33645d; background: #eef8f5; border: 1px solid #cfe5df; border-radius: 999px; font-size: 10px; font-weight: 800; letter-spacing: .02em; }
|
||||
.gift-format-chips span { padding: 4px 8px; color: var(--brand-tint-text); background: var(--brand-tint); border: 1px solid var(--brand-tint-border); border-radius: 999px; font-size: 10px; font-weight: 800; letter-spacing: .02em; }
|
||||
|
||||
.gift-list-summary { margin-left: auto; color: var(--muted); font-size: 11px; font-weight: 700; }
|
||||
|
||||
|
|
@ -284,48 +287,48 @@
|
|||
.official-gift-categories { display: flex; flex-wrap: wrap; gap: 7px; }
|
||||
.official-gift-categories button {
|
||||
display: inline-flex; align-items: center; gap: 7px; min-height: 32px; padding: 5px 10px;
|
||||
color: #49605c; background: #f7faf9; border: 1px solid #d7e2df; border-radius: 999px;
|
||||
color: var(--text-soft); background: var(--panel-subtle); border: 1px solid var(--line-strong); border-radius: 999px;
|
||||
font: inherit; font-size: 11px; font-weight: 800; cursor: pointer;
|
||||
transition: color .15s ease, background .15s ease, border-color .15s ease, box-shadow .15s ease;
|
||||
}
|
||||
.official-gift-categories button:hover { color: var(--brand); border-color: #9fc9c0; }
|
||||
.official-gift-categories button.active { color: #ffffff; background: var(--brand); border-color: var(--brand); box-shadow: 0 4px 12px rgba(23, 109, 97, .17); }
|
||||
.official-gift-categories button:hover { color: var(--brand); border-color: var(--brand-tint-border); }
|
||||
.official-gift-categories button.active { color: #ffffff; background: var(--brand); border-color: var(--brand); box-shadow: var(--shadow-brand); }
|
||||
.official-gift-categories button span {
|
||||
display: grid; min-width: 20px; height: 20px; padding: 0 5px; place-items: center;
|
||||
color: inherit; background: rgba(255,255,255,.65); border-radius: 999px; font-size: 10px;
|
||||
color: inherit; background: rgba(125, 140, 155, .22); border-radius: 999px; font-size: 10px;
|
||||
}
|
||||
.official-gift-categories button.active span { color: var(--brand); }
|
||||
.official-gift-categories button.active span { color: var(--brand); background: rgba(255, 255, 255, .85); }
|
||||
.official-gift-list {
|
||||
display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 8px; max-height: 314px;
|
||||
min-height: 126px; padding: 8px; overflow: auto; border: 1px solid var(--line); border-radius: 14px;
|
||||
background: #f6f9f8; scrollbar-gutter: stable;
|
||||
min-height: 126px; padding: 8px; overflow: auto; border: 1px solid var(--line); border-radius: var(--radius-lg);
|
||||
background: var(--panel-subtle); scrollbar-gutter: stable;
|
||||
}
|
||||
.official-gift-option {
|
||||
display: grid; min-width: 0; gap: 8px; padding: 11px 12px; text-align: left; color: var(--text);
|
||||
background: #ffffff; border: 1px solid #dce6e3; border-radius: 11px; cursor: pointer;
|
||||
box-shadow: 0 1px 2px rgba(32, 54, 50, .03);
|
||||
background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); cursor: pointer;
|
||||
box-shadow: var(--shadow-sm);
|
||||
transition: border-color .15s ease, box-shadow .15s ease, transform .15s ease;
|
||||
}
|
||||
.official-gift-option:hover { border-color: #9fc9c0; box-shadow: 0 5px 14px rgba(32, 76, 68, .08); transform: translateY(-1px); }
|
||||
.official-gift-option.selected { border-color: var(--brand); box-shadow: 0 0 0 2px rgba(23, 109, 97, .12), 0 5px 14px rgba(32, 76, 68, .08); }
|
||||
.official-gift-option:hover { border-color: var(--brand-tint-border); box-shadow: var(--shadow); transform: translateY(-1px); }
|
||||
.official-gift-option.selected { border-color: var(--brand); box-shadow: 0 0 0 2px var(--focus), var(--shadow); }
|
||||
.official-gift-option-head { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 8px; align-items: baseline; }
|
||||
.official-gift-option-head strong { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 12px; }
|
||||
.official-gift-option-head .mono { color: var(--muted); font-size: 9px; }
|
||||
.official-gift-option-meta { display: flex; flex-wrap: wrap; gap: 10px; color: #667773; font-size: 10px; font-weight: 700; }
|
||||
.official-gift-option-meta { display: flex; flex-wrap: wrap; gap: 10px; color: var(--muted); font-size: 10px; font-weight: 700; }
|
||||
.official-gift-capabilities { display: flex; flex-wrap: wrap; gap: 5px; }
|
||||
.official-gift-capabilities > span {
|
||||
padding: 3px 7px; border: 1px solid transparent; border-radius: 999px; font-size: 9px; font-weight: 850; letter-spacing: .01em;
|
||||
}
|
||||
.official-gift-capabilities > span.yes { color: #136b4d; background: #e9f8f0; border-color: #bde6cf; }
|
||||
.official-gift-capabilities > span.craft { color: #6e3ca0; background: #f3ebfb; border-color: #d9c5ef; }
|
||||
.official-gift-capabilities > span.no { color: #78837f; background: #f1f3f2; border-color: #dde2e0; }
|
||||
.official-gift-capabilities > span.yes { color: var(--good); background: var(--good-tint); border-color: var(--good-border); }
|
||||
.official-gift-capabilities > span.craft { color: var(--purple); background: var(--purple-tint); border-color: var(--purple-border); }
|
||||
.official-gift-capabilities > span.no { color: var(--muted); background: var(--panel-strong); border-color: var(--line-strong); }
|
||||
.official-gift-empty {
|
||||
display: grid; grid-column: 1 / -1; min-height: 108px; place-items: center; padding: 20px;
|
||||
color: var(--muted); text-align: center; font-size: 12px;
|
||||
}
|
||||
.official-gift-selected {
|
||||
display: grid; grid-template-columns: 108px minmax(0, 1fr); gap: 14px; align-items: center;
|
||||
padding: 12px; border: 1px solid var(--line); border-radius: 14px; background: var(--surface-soft);
|
||||
padding: 12px; border: 1px solid var(--line); border-radius: var(--radius-lg); background: var(--surface-soft);
|
||||
}
|
||||
.official-gift-selected .gift-animation-shell { width: 96px; height: 96px; }
|
||||
.official-gift-selected > div:last-child { display: grid; gap: 5px; min-width: 0; }
|
||||
|
|
@ -341,22 +344,22 @@
|
|||
gap: 12px;
|
||||
padding: 12px 14px;
|
||||
color: var(--text);
|
||||
background: #ffffff;
|
||||
border: 1px dashed #b7ccc8;
|
||||
border-radius: 10px;
|
||||
background: var(--panel);
|
||||
border: 1px dashed var(--line-strong);
|
||||
border-radius: var(--radius);
|
||||
cursor: pointer;
|
||||
transition: border-color .16s ease, background .16s ease, box-shadow .16s ease;
|
||||
}
|
||||
|
||||
.gift-file-picker:hover,
|
||||
.gift-file-picker.has-file { background: #f8fcfb; border-color: var(--brand); box-shadow: 0 0 0 2px rgba(23, 109, 97, .05); }
|
||||
.gift-file-picker.has-file { background: var(--brand-tint); border-color: var(--brand); box-shadow: 0 0 0 2px var(--focus); }
|
||||
.gift-file-picker input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
.gift-file-icon { width: 40px; height: 40px; border-radius: 9px; }
|
||||
.gift-file-icon { width: 40px; height: 40px; border-radius: var(--radius-sm); }
|
||||
.gift-file-copy { display: grid; min-width: 0; gap: 2px; }
|
||||
.gift-field-label { color: var(--muted); font-size: 10px; font-weight: 800; text-transform: uppercase; letter-spacing: .04em; }
|
||||
.gift-file-copy strong { overflow: hidden; font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gift-file-copy strong { overflow: hidden; color: var(--heading); font-size: 13px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.gift-file-copy small { color: var(--muted); font-size: 11px; font-weight: 500; }
|
||||
.gift-file-action { padding: 7px 10px; color: var(--brand); background: #f0f8f6; border: 1px solid #c7e3dc; border-radius: 7px; font-size: 11px; font-weight: 800; }
|
||||
.gift-file-action { padding: 7px 10px; color: var(--brand); background: var(--brand-tint); border: 1px solid var(--brand-tint-border); border-radius: var(--radius-sm); font-size: 11px; font-weight: 800; }
|
||||
|
||||
.gift-fields-grid {
|
||||
display: grid;
|
||||
|
|
@ -380,37 +383,37 @@
|
|||
height: 38px;
|
||||
padding: 0 10px;
|
||||
color: var(--text);
|
||||
background: #fff;
|
||||
background: var(--input-bg);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.gift-fields-grid input:focus,
|
||||
.gift-reason-field input:focus { border-color: #77b6aa; box-shadow: 0 0 0 3px rgba(23, 109, 97, .08); outline: none; }
|
||||
.gift-reason-field input:focus { border-color: var(--brand); box-shadow: 0 0 0 3px var(--focus); outline: none; }
|
||||
|
||||
.gift-switch { display: inline-flex; align-items: center; gap: 9px; color: #344054; font-size: 12px; font-weight: 700; cursor: pointer; }
|
||||
.gift-switch { display: inline-flex; align-items: center; gap: 9px; color: var(--text-soft); font-size: 12px; font-weight: 700; cursor: pointer; }
|
||||
.gift-switch input { position: absolute; width: 1px; height: 1px; opacity: 0; }
|
||||
.gift-switch-track { display: flex; width: 34px; height: 19px; align-items: center; padding: 2px; background: #c8d0d5; border-radius: 999px; transition: background .16s ease; }
|
||||
.gift-switch-track { display: flex; width: 34px; height: 19px; align-items: center; padding: 2px; background: var(--switch-track); border-radius: 999px; transition: background .16s ease; }
|
||||
.gift-switch-track span { width: 15px; height: 15px; background: #ffffff; border-radius: 50%; box-shadow: 0 1px 3px rgba(16, 24, 40, .22); transition: transform .16s ease; }
|
||||
.gift-switch input:checked + .gift-switch-track { background: var(--brand); }
|
||||
.gift-switch input:checked + .gift-switch-track span { transform: translateX(15px); }
|
||||
.gift-switch input:focus-visible + .gift-switch-track { outline: 3px solid rgba(23, 109, 97, .16); outline-offset: 2px; }
|
||||
.gift-validation { overflow: hidden; color: #d5fff5; background: #173631; border: 1px solid #24564e; border-radius: 9px; }
|
||||
.gift-validation-head { display: flex; align-items: center; gap: 9px; padding: 10px 12px; color: #e3fff9; background: rgba(255, 255, 255, .035); border-bottom: 1px solid rgba(255, 255, 255, .09); }
|
||||
.gift-switch input:focus-visible + .gift-switch-track { outline: 3px solid var(--focus); outline-offset: 2px; }
|
||||
.gift-validation { overflow: hidden; color: var(--code-text); background: var(--code-bg); border: 1px solid var(--code-border); border-radius: var(--radius-sm); }
|
||||
.gift-validation-head { display: flex; align-items: center; gap: 9px; padding: 10px 12px; color: var(--code-text); background: rgba(255, 255, 255, .035); border-bottom: 1px solid rgba(255, 255, 255, .09); }
|
||||
.gift-validation-head div { display: grid; gap: 2px; }
|
||||
.gift-validation-head span { color: #99cfc4; font-size: 10px; }
|
||||
.gift-validation pre { max-height: 180px; overflow: auto; margin: 0; padding: 11px 12px; color: #d5fff5; font-size: 11px; }
|
||||
.gift-validation-head span { color: var(--brand); font-size: 10px; }
|
||||
.gift-validation pre { max-height: 180px; overflow: auto; margin: 0; padding: 11px 12px; color: var(--code-text); font-size: 11px; }
|
||||
|
||||
.gift-animation-shell { position: relative; display: grid; min-height: 210px; place-items: center; background: radial-gradient(circle, #f9f3ff, #eef8f5); }
|
||||
.gift-animation-shell { position: relative; display: grid; min-height: 210px; place-items: center; background: var(--surface-soft); }
|
||||
.gift-animation { width: 200px; height: 200px; }
|
||||
.gift-animation canvas { width: 100% !important; height: 100% !important; }
|
||||
.gift-play { position: absolute; right: 8px; bottom: 8px; display: grid; width: 30px; height: 30px; place-items: center; color: var(--text); background: rgba(255,255,255,.9); border: 1px solid var(--line); border-radius: 50%; }
|
||||
.gift-play { position: absolute; right: 8px; bottom: 8px; display: grid; width: 30px; height: 30px; place-items: center; color: var(--text); background: var(--panel); border: 1px solid var(--line); border-radius: 50%; }
|
||||
|
||||
.gift-table-wrap { background: #ffffff; }
|
||||
.gift-table-wrap { background: var(--panel); }
|
||||
.gift-table { min-width: 1080px; }
|
||||
.gift-table th:first-child { width: 74px; }
|
||||
.gift-table td { vertical-align: middle; }
|
||||
.gift-animation-shell.compact { width: 56px; min-height: 56px; overflow: hidden; border: 1px solid var(--line); border-radius: 9px; }
|
||||
.gift-animation-shell.compact { width: 56px; min-height: 56px; overflow: hidden; border: 1px solid var(--line); border-radius: var(--radius-sm); }
|
||||
.gift-animation-shell.compact .gift-animation { width: 54px; height: 54px; }
|
||||
.gift-animation-shell.compact .gift-play { right: 3px; bottom: 3px; width: 20px; height: 20px; }
|
||||
.gift-row-disabled { opacity: .68; }
|
||||
|
|
@ -422,60 +425,60 @@
|
|||
.gift-sort-order,
|
||||
.gift-source-size,
|
||||
.gift-convert-price { margin-top: 3px; color: var(--muted); font-size: 10px; }
|
||||
.gift-table-price { color: #755b00; }
|
||||
.gift-table-price { color: var(--warn); }
|
||||
.gift-table-actions { display: flex; align-items: center; gap: 6px; }
|
||||
.collectible-button { color: #6548a8; background: #f7f3ff; border-color: #ddd2f5; }
|
||||
.collectible-button:hover { background: #efe8ff; border-color: #cbbaf0; }
|
||||
.collectible-button { color: var(--purple); background: var(--purple-tint); border-color: var(--purple-border); }
|
||||
.collectible-button:hover { background: var(--purple-tint); border-color: var(--purple); }
|
||||
|
||||
.collectible-modal { width: min(1180px, 100%); max-height: min(92vh, 980px); }
|
||||
.collectible-modal .modal-head p { margin: 4px 0 0; color: var(--muted); font-size: 11px; }
|
||||
.collectible-modal-body { gap: 16px; overflow: auto; padding: 16px 18px 22px; background: #f5f7fa; }
|
||||
.collectible-modal-body { gap: 16px; overflow: auto; padding: 16px 18px 22px; background: var(--bg); }
|
||||
.collectible-loading { display: flex; min-height: 90px; align-items: center; justify-content: center; gap: 8px; color: var(--muted); }
|
||||
.collectible-empty { display: flex; align-items: center; gap: 12px; padding: 16px; color: #66568c; background: linear-gradient(135deg, #fbf9ff, #f2f7ff); border: 1px dashed #cfc3e9; border-radius: 12px; }
|
||||
.collectible-empty { display: flex; align-items: center; gap: 12px; padding: 16px; color: var(--purple-text); background: var(--purple-tint); border: 1px dashed var(--purple-border); border-radius: var(--radius); }
|
||||
.collectible-empty div,
|
||||
.collectible-definition-head > div:first-child,
|
||||
.collectible-section-head > div:first-child { display: grid; gap: 3px; }
|
||||
.collectible-empty span,
|
||||
.collectible-definition-head span,
|
||||
.collectible-section-head span { color: var(--muted); font-size: 10px; font-weight: 500; }
|
||||
.collectible-active { overflow: hidden; background: #ffffff; border: 1px solid #ddd6ee; border-radius: 12px; box-shadow: 0 5px 16px rgba(66, 46, 110, .05); }
|
||||
.collectible-active-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; background: linear-gradient(100deg, #fbf9ff, #f4f9ff); border-bottom: 1px solid #e9e4f3; }
|
||||
.collectible-active-head > div { display: flex; align-items: center; gap: 9px; color: #60458f; }
|
||||
.collectible-active { overflow: hidden; background: var(--panel); border: 1px solid var(--purple-border); border-radius: var(--radius); box-shadow: var(--shadow-sm); }
|
||||
.collectible-active-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 14px; background: var(--purple-tint); border-bottom: 1px solid var(--purple-border); }
|
||||
.collectible-active-head > div { display: flex; align-items: center; gap: 9px; color: var(--purple-text); }
|
||||
.collectible-active-head > div > div { display: grid; gap: 2px; }
|
||||
.collectible-active-head span { color: var(--muted); font-size: 10px; }
|
||||
.collectible-active-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(145px, 1fr)); gap: 1px; background: var(--line); }
|
||||
.collectible-active-grid article { display: flex; min-width: 0; align-items: center; gap: 9px; padding: 9px 11px; background: #ffffff; }
|
||||
.collectible-active-grid article { display: flex; min-width: 0; align-items: center; gap: 9px; padding: 9px 11px; background: var(--panel); }
|
||||
.collectible-active-grid article > div:last-child { display: grid; min-width: 0; gap: 2px; }
|
||||
.collectible-active-grid article strong { overflow: hidden; font-size: 11px; text-overflow: ellipsis; white-space: nowrap; }
|
||||
.collectible-active-grid article span { color: var(--muted); font-size: 9px; }
|
||||
.collectible-definition { overflow: hidden; background: #ffffff; border: 1px solid var(--line); border-radius: 12px; box-shadow: 0 8px 24px rgba(16, 24, 40, .04); }
|
||||
.collectible-definition-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; background: linear-gradient(110deg, #f8fbfa, #fbf9ff); border-bottom: 1px solid var(--line); }
|
||||
.collectible-main-fields { padding: 14px 16px; background: #fbfcfd; border-bottom: 1px solid var(--line); }
|
||||
.collectible-definition { overflow: hidden; background: var(--panel); border: 1px solid var(--line); border-radius: var(--radius); box-shadow: var(--shadow-sm); }
|
||||
.collectible-definition-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 14px 16px; background: var(--panel-subtle); border-bottom: 1px solid var(--line); }
|
||||
.collectible-main-fields { padding: 14px 16px; background: var(--panel-subtle); border-bottom: 1px solid var(--line); }
|
||||
.collectible-section { padding: 14px 16px; border-bottom: 1px solid var(--line); }
|
||||
.collectible-section:last-child { border-bottom: 0; }
|
||||
.collectible-section-head { display: flex; align-items: center; justify-content: space-between; gap: 12px; margin-bottom: 10px; }
|
||||
.collectible-section-tools { display: flex; align-items: center; gap: 7px; }
|
||||
.collectible-rows { display: grid; gap: 7px; }
|
||||
.collectible-row { position: relative; display: grid; align-items: end; gap: 7px; padding: 9px 9px 9px 36px; background: #fafbfc; border: 1px solid #e1e6eb; border-radius: 9px; }
|
||||
.collectible-row:hover { background: #ffffff; border-color: #cbd7dd; box-shadow: 0 3px 10px rgba(16, 24, 40, .035); }
|
||||
.collectible-row { position: relative; display: grid; align-items: end; gap: 7px; padding: 9px 9px 9px 36px; background: var(--panel-subtle); border: 1px solid var(--line); border-radius: var(--radius-sm); }
|
||||
.collectible-row:hover { background: var(--panel); border-color: var(--line-strong); box-shadow: var(--shadow-sm); }
|
||||
.collectible-row.animated { grid-template-columns: minmax(120px, 1.2fr) 90px 78px minmax(160px, 1.4fr) 48px 30px; }
|
||||
.collectible-row.backdrop { grid-template-columns: minmax(110px, 1.2fr) 70px 80px 70px repeat(4, 52px) 48px 30px; }
|
||||
.collectible-row-index { position: absolute; top: 0; bottom: 0; left: 0; display: grid; width: 27px; place-items: center; color: #71668c; background: #f0edf7; border-right: 1px solid #e0d9ed; border-radius: 8px 0 0 8px; font-size: 10px; font-weight: 800; }
|
||||
.collectible-row-index { position: absolute; top: 0; bottom: 0; left: 0; display: grid; width: 27px; place-items: center; color: var(--purple-text); background: var(--purple-tint); border-right: 1px solid var(--purple-border); border-radius: var(--radius-xs) 0 0 var(--radius-xs); font-size: 10px; font-weight: 800; }
|
||||
.collectible-row label { display: grid; min-width: 0; gap: 4px; }
|
||||
.collectible-row label > span { color: var(--muted); font-size: 9px; font-weight: 800; text-transform: uppercase; letter-spacing: .025em; }
|
||||
.collectible-row input:not([type="file"]) { width: 100%; min-width: 0; height: 32px; padding: 0 8px; color: var(--text); background: #ffffff; border: 1px solid #d5dde3; border-radius: 7px; font: inherit; font-size: 11px; }
|
||||
.collectible-row input:focus { border-color: #8d7aba; box-shadow: 0 0 0 3px rgba(111, 91, 174, .08); outline: none; }
|
||||
.collectible-row input:not([type="file"]) { width: 100%; min-width: 0; height: 32px; padding: 0 8px; color: var(--text); background: var(--input-bg); border: 1px solid var(--line-strong); border-radius: var(--radius-sm); font: inherit; font-size: 11px; }
|
||||
.collectible-row input:focus { border-color: var(--purple); box-shadow: 0 0 0 3px var(--purple-tint); outline: none; }
|
||||
.collectible-file input { position: absolute; width: 1px; height: 1px; opacity: 0; pointer-events: none; }
|
||||
.collectible-file em { display: flex; min-width: 0; height: 32px; align-items: center; gap: 5px; overflow: hidden; padding: 0 8px; color: #625080; background: #f7f4fd; border: 1px dashed #cfc4e1; border-radius: 7px; font-size: 10px; font-style: normal; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
|
||||
.collectible-inline-preview { display: grid; width: 42px; height: 42px; place-items: center; overflow: hidden; color: #8c7cae; background: radial-gradient(circle, #ffffff, #eee8f8); border: 1px solid #ded5ed; border-radius: 8px; }
|
||||
.collectible-file em { display: flex; min-width: 0; height: 32px; align-items: center; gap: 5px; overflow: hidden; padding: 0 8px; color: var(--purple-text); background: var(--purple-tint); border: 1px dashed var(--purple-border); border-radius: var(--radius-sm); font-size: 10px; font-style: normal; font-weight: 700; text-overflow: ellipsis; white-space: nowrap; cursor: pointer; }
|
||||
.collectible-inline-preview { display: grid; width: 42px; height: 42px; place-items: center; overflow: hidden; color: var(--purple); background: var(--purple-tint); border: 1px solid var(--purple-border); border-radius: var(--radius-sm); }
|
||||
.collectible-animation { width: 100%; height: 100%; overflow: hidden; }
|
||||
.collectible-animation.compact { display: grid; width: 42px; height: 42px; flex: 0 0 42px; place-items: center; background: radial-gradient(circle, #ffffff, #f0ebfa); border: 1px solid #e0d9ec; border-radius: 8px; }
|
||||
.collectible-animation.compact { display: grid; width: 42px; height: 42px; flex: 0 0 42px; place-items: center; background: var(--purple-tint); border: 1px solid var(--purple-border); border-radius: var(--radius-sm); }
|
||||
.collectible-animation canvas { width: 100% !important; height: 100% !important; }
|
||||
.collectible-animation.failed { color: #b42318; background: #fff4f2; }
|
||||
.collectible-animation.loading { color: #807397; }
|
||||
.collectible-file-error { grid-column: 1 / -1; color: #b42318; font-size: 10px; }
|
||||
.collectible-animation.failed { color: var(--danger); background: var(--danger-tint); }
|
||||
.collectible-animation.loading { color: var(--purple-text); }
|
||||
.collectible-file-error { grid-column: 1 / -1; color: var(--danger); font-size: 10px; }
|
||||
.collectible-color input { height: 32px !important; padding: 3px !important; cursor: pointer; }
|
||||
.collectible-backdrop-preview { display: grid; width: 42px; height: 42px; flex: 0 0 42px; place-items: center; border: 1px solid rgba(42, 31, 71, .18); border-radius: 8px; box-shadow: inset 0 0 0 1px rgba(255,255,255,.2); font-size: 11px; font-weight: 900; }
|
||||
.collectible-backdrop-preview { display: grid; width: 42px; height: 42px; flex: 0 0 42px; place-items: center; border: 1px solid rgba(42, 31, 71, .18); border-radius: var(--radius-sm); box-shadow: inset 0 0 0 1px rgba(255,255,255,.2); font-size: 11px; font-weight: 900; }
|
||||
.collectible-row .icon-btn { align-self: center; }
|
||||
.collectible-row .icon-btn:disabled { opacity: .28; }
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,8 @@
|
|||
display: grid;
|
||||
place-items: center;
|
||||
padding: 24px;
|
||||
background: rgba(17, 24, 39, 0.52);
|
||||
background: var(--overlay);
|
||||
backdrop-filter: blur(2px);
|
||||
}
|
||||
|
||||
.modal {
|
||||
|
|
@ -13,9 +14,9 @@
|
|||
max-height: min(820px, calc(100vh - 48px));
|
||||
overflow: hidden;
|
||||
padding: 0;
|
||||
background: #ffffff;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
|
|
@ -43,10 +44,17 @@
|
|||
width: 30px;
|
||||
height: 30px;
|
||||
place-items: center;
|
||||
color: var(--text-soft);
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 7px;
|
||||
border-radius: var(--radius-sm);
|
||||
cursor: pointer;
|
||||
transition: background-color 140ms ease, border-color 140ms ease, color 140ms ease;
|
||||
}
|
||||
|
||||
.icon-btn:hover {
|
||||
background: var(--btn-hover);
|
||||
border-color: var(--line-strong);
|
||||
}
|
||||
|
||||
.command-steps {
|
||||
|
|
@ -73,7 +81,7 @@
|
|||
color: var(--muted);
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-sm);
|
||||
}
|
||||
|
||||
.command-step span {
|
||||
|
|
@ -81,7 +89,7 @@
|
|||
width: 20px;
|
||||
height: 20px;
|
||||
place-items: center;
|
||||
background: #ffffff;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 999px;
|
||||
font-size: 11px;
|
||||
|
|
@ -90,12 +98,12 @@
|
|||
|
||||
.command-step.active {
|
||||
color: var(--brand);
|
||||
border-color: #a9d8ce;
|
||||
border-color: var(--brand-tint-border);
|
||||
}
|
||||
|
||||
.command-step.done {
|
||||
color: var(--good);
|
||||
border-color: #b9dcc7;
|
||||
border-color: var(--good-border);
|
||||
}
|
||||
|
||||
.form-field {
|
||||
|
|
@ -105,7 +113,7 @@
|
|||
|
||||
.form-field span,
|
||||
.form-stack span {
|
||||
color: #4b5563;
|
||||
color: var(--text-soft);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +131,7 @@
|
|||
display: flex;
|
||||
align-items: center;
|
||||
gap: 7px;
|
||||
color: #344054;
|
||||
color: var(--text-soft);
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
|
|
@ -131,9 +139,9 @@
|
|||
display: grid;
|
||||
gap: 8px;
|
||||
padding: 10px;
|
||||
background: #fbfcfd;
|
||||
background: var(--panel-subtle);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius);
|
||||
}
|
||||
|
||||
.result-line {
|
||||
|
|
@ -151,13 +159,13 @@
|
|||
}
|
||||
|
||||
.result-message {
|
||||
color: #344054;
|
||||
color: var(--text-soft);
|
||||
}
|
||||
|
||||
.modal-actions {
|
||||
justify-content: flex-end;
|
||||
padding: 12px 18px;
|
||||
background: #ffffff;
|
||||
background: var(--panel);
|
||||
border-top: 1px solid var(--line);
|
||||
}
|
||||
|
||||
|
|
@ -174,9 +182,9 @@
|
|||
width: min(420px, 100%);
|
||||
gap: 18px;
|
||||
padding: 22px;
|
||||
background: #ffffff;
|
||||
background: var(--panel);
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 8px;
|
||||
border-radius: var(--radius-lg);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
|
|
@ -201,14 +209,15 @@
|
|||
align-items: center;
|
||||
padding: 0 8px;
|
||||
color: var(--brand);
|
||||
background: #edf7f4;
|
||||
border: 1px solid #c9e2dc;
|
||||
background: var(--brand-tint);
|
||||
border: 1px solid var(--brand-tint-border);
|
||||
border-radius: 999px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.login-copy h1 {
|
||||
margin: 0;
|
||||
color: var(--heading);
|
||||
font-size: 22px;
|
||||
}
|
||||
|
||||
|
|
@ -237,13 +246,14 @@
|
|||
place-items: center;
|
||||
align-content: center;
|
||||
gap: 18px;
|
||||
background: var(--bg);
|
||||
}
|
||||
|
||||
.loader-bar {
|
||||
width: 180px;
|
||||
height: 4px;
|
||||
overflow: hidden;
|
||||
background: #d7dde4;
|
||||
background: var(--line-strong);
|
||||
border-radius: 999px;
|
||||
}
|
||||
|
||||
|
|
|
|||
106
cmd/telesrv-admin/web/src/theme.tsx
Normal file
106
cmd/telesrv-admin/web/src/theme.tsx
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
import { createContext, useCallback, useContext, useEffect, useMemo, useState, type ReactNode } from "react";
|
||||
import { Moon, Sun } from "lucide-react";
|
||||
import { useI18n } from "./i18n";
|
||||
|
||||
export type Theme = "light" | "dark";
|
||||
|
||||
const storageKey = "telesrv.admin.theme";
|
||||
|
||||
type ThemeContextValue = {
|
||||
theme: Theme;
|
||||
setTheme: (theme: Theme) => void;
|
||||
toggleTheme: () => void;
|
||||
};
|
||||
|
||||
const ThemeContext = createContext<ThemeContextValue | null>(null);
|
||||
|
||||
export function applyTheme(theme: Theme) {
|
||||
document.documentElement.setAttribute("data-theme", theme);
|
||||
document.documentElement.style.colorScheme = theme;
|
||||
}
|
||||
|
||||
export function ThemeProvider({ children }: { children: ReactNode }) {
|
||||
const [theme, setThemeState] = useState<Theme>(() => initialTheme());
|
||||
|
||||
useEffect(() => {
|
||||
applyTheme(theme);
|
||||
try {
|
||||
localStorage.setItem(storageKey, theme);
|
||||
} catch {
|
||||
// Theme persistence is best-effort.
|
||||
}
|
||||
}, [theme]);
|
||||
|
||||
// Follow the OS preference until the user makes an explicit choice.
|
||||
useEffect(() => {
|
||||
if (!window.matchMedia) {
|
||||
return;
|
||||
}
|
||||
const media = window.matchMedia("(prefers-color-scheme: dark)");
|
||||
const onChange = (event: MediaQueryListEvent) => {
|
||||
let stored: string | null = null;
|
||||
try {
|
||||
stored = localStorage.getItem(storageKey);
|
||||
} catch {
|
||||
stored = null;
|
||||
}
|
||||
if (stored !== "light" && stored !== "dark") {
|
||||
setThemeState(event.matches ? "dark" : "light");
|
||||
}
|
||||
};
|
||||
media.addEventListener("change", onChange);
|
||||
return () => media.removeEventListener("change", onChange);
|
||||
}, []);
|
||||
|
||||
const setTheme = useCallback((next: Theme) => setThemeState(next), []);
|
||||
const toggleTheme = useCallback(() => setThemeState((current) => (current === "dark" ? "light" : "dark")), []);
|
||||
|
||||
const value = useMemo<ThemeContextValue>(() => ({ theme, setTheme, toggleTheme }), [theme, setTheme, toggleTheme]);
|
||||
|
||||
return <ThemeContext.Provider value={value}>{children}</ThemeContext.Provider>;
|
||||
}
|
||||
|
||||
export function useTheme(): ThemeContextValue {
|
||||
const value = useContext(ThemeContext);
|
||||
if (!value) {
|
||||
throw new Error("useTheme must be used inside ThemeProvider");
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export function ThemeSwitch() {
|
||||
const { theme, toggleTheme } = useTheme();
|
||||
const { t } = useI18n();
|
||||
const nextIsDark = theme === "light";
|
||||
const label = t(nextIsDark ? "theme.switchToDark" : "theme.switchToLight");
|
||||
return (
|
||||
<button
|
||||
className="theme-toggle"
|
||||
type="button"
|
||||
onClick={toggleTheme}
|
||||
aria-label={label}
|
||||
title={label}
|
||||
>
|
||||
{theme === "dark" ? <Sun size={16} /> : <Moon size={16} />}
|
||||
</button>
|
||||
);
|
||||
}
|
||||
|
||||
function initialTheme(): Theme {
|
||||
try {
|
||||
const stored = localStorage.getItem(storageKey);
|
||||
if (stored === "light" || stored === "dark") {
|
||||
return stored;
|
||||
}
|
||||
} catch {
|
||||
// Storage is optional.
|
||||
}
|
||||
try {
|
||||
if (window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches) {
|
||||
return "dark";
|
||||
}
|
||||
} catch {
|
||||
// matchMedia can be unavailable in unusual embedded contexts.
|
||||
}
|
||||
return "light";
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue