This commit is contained in:
micialware committed 2026-09-15 21:36:06 +03:00
1 parent b5b4254d20
commit 834bef915e
10 files changed
+373 -205

No files matched your search

-1
View File
@@ -1 +0,0 @@
:root{font-family:system-ui,-apple-system,Segoe UI,Roboto,sans-serif;line-height:1.5;color:#1e1b2e;background:#f4f1fa}*{box-sizing:border-box}body{margin:0;min-height:100dvh;display:flex;justify-content:center;padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}#app{width:100%;max-width:480px;padding:24px 16px 32px;display:flex;flex-direction:column;gap:16px}.app-header h1{margin:0;font-size:1.75rem;letter-spacing:.02em}.app-subtitle{margin:4px 0 0;color:#6d6390}.top-bar{display:flex;align-items:center;gap:12px}.burger-btn{width:44px;height:44px;border:2px solid #d9d2f0;border-radius:12px;background:#fff;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;touch-action:manipulation;flex:none}.burger-btn span{display:block;width:20px;height:2px;border-radius:2px;background:#4c446b}.burger-btn[aria-expanded=true]{border-color:#7c3aed}.top-bar-title h1{margin:0;font-size:1.4rem;line-height:1.2}.top-bar-title p{margin:0;font-size:.9rem;color:#6d6390}.top-bar-spacer{flex:1}.app-content{display:flex;flex-direction:column;gap:16px}.drawer-overlay{position:fixed;inset:0;background:#1e1b2e73;z-index:40}.drawer-overlay[hidden]{display:none}.drawer{position:fixed;top:0;left:0;bottom:0;width:min(300px,85vw);background:#fff;z-index:50;transform:translate(-105%);transition:transform .22s ease;display:flex;flex-direction:column;gap:8px;padding:20px 14px;box-shadow:8px 0 32px #1e1b2e2e}.drawer.open{transform:translate(0)}.drawer-header{padding:4px 8px 14px;border-bottom:1px solid #eee9fb;margin-bottom:8px}.drawer-logo{font-size:1.3rem;font-weight:800;color:#7c3aed}.drawer-subtitle{font-size:.9rem;color:#6d6390}.drawer-btn{border:none;background:transparent;text-align:left;font-size:1.05rem;font-weight:600;color:#1e1b2e;padding:13px 12px;border-radius:12px;cursor:pointer;touch-action:manipulation}.drawer-btn.active{background:#f1eafe;color:#6d28d9}.drawer-btn:active{background:#e9defc}body.drawer-open{overflow:hidden}.card{background:#fff;border-radius:16px;padding:20px 16px;box-shadow:0 8px 24px #7c3aed1f;display:flex;flex-direction:column;gap:12px}.card-title{margin:0;font-size:1.25rem}.card-hint{margin:0;color:#6d6390;font-size:.95rem}.field-label{font-weight:600;font-size:.95rem}.token-input{width:100%;padding:14px 12px;font-size:1.05rem;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em;border:2px solid #d9d2f0;border-radius:12px;outline:none}.token-input:focus{border-color:#7c3aed}.token-input.invalid{border-color:#dc2626;background:#fef2f2}.token-meta{display:flex;justify-content:space-between;align-items:baseline;gap:8px;min-height:1.4em}.token-error{color:#dc2626;font-size:.9rem}.token-counter{color:#6d6390;font-variant-numeric:tabular-nums;white-space:nowrap}.btn-row{display:flex;gap:12px}.btn{flex:1;padding:14px 12px;font-size:1rem;font-weight:600;border-radius:12px;border:2px solid #7c3aed;background:#fff;color:#7c3aed;cursor:pointer;touch-action:manipulation}.btn-primary{background:#7c3aed;color:#fff}.btn:disabled{opacity:.4;cursor:not-allowed}.btn:not(:disabled):active{transform:scale(.98)}.status-bar{margin:0;padding:10px 12px;border-radius:10px;background:#f4f1fa;color:#4c446b;font-size:.95rem;word-break:break-word}.status-bar[hidden]{display:none}.db-banner{display:flex;align-items:center;gap:8px;padding:8px 12px;border-radius:10px;background:#fff7e6;color:#7a5b00;font-size:.85rem}.db-banner[hidden]{display:none}.db-banner-text{flex:1;min-width:0}.db-banner-link{flex:none;border:none;background:transparent;padding:4px;font:inherit;font-weight:700;color:#7c3aed;cursor:pointer;touch-action:manipulation}.db-banner-link[hidden]{display:none}.words-toolbar{display:flex;gap:8px}.words-search{flex:1;min-width:0;padding:12px;font-size:1rem;border:2px solid #d9d2f0;border-radius:12px;outline:none;background:#fff;color:inherit}.words-search:focus{border-color:#7c3aed}.words-group{flex:none;max-width:42%;padding:12px 8px;font-size:.95rem;font-weight:600;color:#4c446b;border:2px solid #d9d2f0;border-radius:12px;background:#fff;outline:none}.words-group:focus{border-color:#7c3aed}.words-list{display:flex;flex-direction:column}.word-row-wrap{border-bottom:1px solid #eee9fb}.word-row-wrap:last-child{border-bottom:none}.word-row{display:flex;align-items:baseline;gap:8px;width:100%;padding:11px 4px;border:none;background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;touch-action:manipulation;border-radius:8px}.word-row:disabled{cursor:default}.word-row:not(:disabled):active{background:#f4f1fa}.word-row.open{background:#f4f1fa}.word-main{flex:1;min-width:0;overflow-wrap:break-word}.word-key{font-weight:700}.word-sep{color:#6d6390}.word-value{color:#4c446b}.word-tag{flex:none;max-width:38%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.8rem;color:#6d6390;text-align:right}.word-dot{flex:none;width:6px;height:6px;border-radius:50%;background:#7c3aedLine truncated
File renamed without changes.
+1
View File
@@ -0,0 +1 @@
@font-face{font-family:Noto Sans JP;src:url(/fonts/NotoSansJP-VariableFont_wght.ttf) format("truetype");font-weight:100 900;font-style:normal;font-display:swap}:root{color-scheme:dark;--bg: #282828;--fg: #fbf1c7;--bg-weakest: #2f2f2f;--bg-weak: #414141;--bg-strong: #4f4f4f;--muted: #908c7c;--secondary: #908c7c;--secondary-strong: #9f9b8d;--on-secondary: #1f1e1a;--primary: #458588;--primary-weak: #3b6c6e;--primary-strong: #49a8ac;--on-primary: #0a1c1d;--success: #98971a;--success-strong: #7c7900;--on-success: #222102;--warning: #d79921;--warning-strong: #b67a00;--on-warning: #342203;--danger: #cc241d;--danger-strong: #ff0000;--on-danger: #fdf9f9;--danger-text: #fb4934;--fg-soft: rgb(251 241 199 / .85);--fg-faint: rgb(251 241 199 / .3);--surface-press: rgb(255 255 255 / .1);--surface-press-soft: rgb(255 255 255 / .08);--space: 16px;--space-half: 8px;--space-quarter: 4px;--radius: 8px;--radius-sm: 5px;--radius-xs: 2px;font-family:Noto Sans JP,system-ui,-apple-system,Segoe UI,Roboto,sans-serif;font-size:17px;line-height:1.5;color:var(--fg);background:var(--bg)}*{box-sizing:border-box}body{margin:0;min-height:100dvh;display:flex;justify-content:center;background:var(--bg);color:var(--fg);padding:env(safe-area-inset-top) env(safe-area-inset-right) env(safe-area-inset-bottom) env(safe-area-inset-left)}::selection{background:var(--primary-weak);color:var(--fg)}#app{width:100%;max-width:480px;padding:var(--space) var(--space) calc(var(--space) * 2);display:flex;flex-direction:column;gap:var(--space)}.top-bar{display:flex;align-items:center;gap:12px}.burger-btn{width:44px;height:44px;border:none;border-radius:var(--radius);background:transparent;color:var(--fg);cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:5px;touch-action:manipulation;flex:none}.burger-btn span{display:block;width:20px;height:2px;border-radius:1px;background:currentColor}.burger-btn:active,.burger-btn[aria-expanded=true]{background:var(--surface-press)}.burger-btn[aria-expanded=true] span{background:var(--primary-strong)}.top-bar-title h1{margin:0;font-size:22px;font-weight:700;line-height:1.2}.top-bar-title p{margin:0;font-size:.9rem;color:var(--muted)}.top-bar-spacer{flex:1}.app-content{display:flex;flex-direction:column;gap:var(--space)}.drawer-overlay{position:fixed;inset:0;background:#282828b8;z-index:40}.drawer-overlay[hidden]{display:none}.drawer{position:fixed;top:0;left:0;bottom:0;width:min(300px,85vw);background:var(--bg-weakest);border-right:1px solid var(--bg-weak);z-index:50;transform:translate(-105%);visibility:hidden;transition:transform .22s ease,visibility 0s linear .22s;display:flex;flex-direction:column;gap:var(--space-quarter);padding:var(--space) var(--space-half)}.drawer.open{transform:translate(0);visibility:visible;transition:transform .22s ease,visibility 0s}.drawer-header{padding:var(--space-quarter) var(--space-half) 14px;border-bottom:1px solid var(--bg-strong);margin-bottom:var(--space-half)}.drawer-logo{font-size:22px;font-weight:800;color:var(--primary-strong)}.drawer-subtitle{font-size:.9rem;color:var(--muted)}.drawer-btn{border:none;background:transparent;text-align:left;font:inherit;font-size:1.05rem;font-weight:600;color:var(--fg);padding:13px 12px;border-radius:var(--radius);cursor:pointer;touch-action:manipulation}.drawer-btn:hover,.drawer-btn:active{background:var(--surface-press)}.drawer-btn.active{background:var(--surface-press);color:var(--primary-strong)}body.drawer-open{overflow:hidden}.card{background:var(--bg-weakest);border:1px solid var(--bg-weak);border-radius:var(--radius-sm);padding:var(--space);display:flex;flex-direction:column;gap:12px}.card-title{margin:0;font-size:1.25rem;font-weight:700}.card-hint{margin:0;color:var(--muted);font-size:.95rem}.field-label{font-weight:600;font-size:.95rem}.token-input,.words-search,.words-group{padding:12px;font:inherit;font-size:1rem;color:var(--fg);background:var(--bg);border:1px solid var(--bg-strong);border-radius:var(--radius-xs);outline:none;appearance:none}.token-input::placeholder,.words-search::placeholder{color:var(--muted)}.token-input:focus,.words-search:focus,.words-group:focus{border-color:var(--primary-strong)}.token-input:disabled{background:var(--bg-weak);color:var(--muted)}.token-input{width:100%;font-family:ui-monospace,SFMono-Regular,Menlo,monospace;letter-spacing:.04em}.token-input.invalid{border-color:var(--danger-text)}.token-meta{display:flex;justify-content:space-between;align-items:baseline;gap:var(--space-half);min-height:1.4em}.token-error{color:var(--danger-text);font-size:.9rem}.token-counter{color:var(--muted);font-variant-numeric:tabular-nums;white-space:nowrap}.btn-row{display:flex;gap:12px}.btn{flex:1;padding:12px 14px;font:inherit;font-size:1rem;font-weight:600;border:1px solid transparent;border-radius:var(--radius);background:var(--secondary);color:var(--on-secondary);cursor:pointer;touch-action:manipulation}.btn:not(:disabled):active{background:var(--secondary-strong)}.btn-prLine truncated
+3
View File
@@ -0,0 +1,3 @@
# Положи сюда файл шрифта Noto Sans JP (например NotoSansJP-Variable.woff2).
# Папка public/fonts целиком копируется в dist при сборке —
# шрифт будет доступен офлайн в PWA.
Binary file not shown.
+4 -3
View File
@@ -3,10 +3,11 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#7c3aed" />
<meta name="theme-color" content="#282828" />
<link rel="preload" href="/fonts/NotoSansJP-VariableFont_wght.ttf" as="font" type="font/ttf" crossorigin />
<title>JapLearn — интервальные повторения</title>
<script type="module" crossorigin src="/assets/index-Bk0XDs7i.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-B1EUgva9.css">
<script type="module" crossorigin src="/assets/index-C0jVdQGX.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-Db5EzFTV.css">
</head>
<body>
<div id="app">
+2 -1
View File
@@ -3,7 +3,8 @@
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<meta name="theme-color" content="#7c3aed" />
<meta name="theme-color" content="#282828" />
<link rel="preload" href="/fonts/NotoSansJP-VariableFont_wght.ttf" as="font" type="font/ttf" crossorigin />
<title>JapLearn — интервальные повторения</title>
</head>
<body>
+3
View File
@@ -0,0 +1,3 @@
# Положи сюда файл шрифта Noto Sans JP (например NotoSansJP-Variable.woff2).
# Папка public/fonts целиком копируется в dist при сборке —
# шрифт будет доступен офлайн в PWA.
Binary file not shown.
+360 -200
View File
@@ -1,10 +1,89 @@
/* JapLearn — мобильный PWA, интервальные повторения (Vanilla + Vite) */
/* JapLearn — мобильный PWA, интервальные повторения (Vanilla + Vite).
Тема перенесена из десктопного приложения JapLearn
(/home/micial/projects/rust/jap_learn, Rust + iced 0.14, Theme::GruvboxDark).
Значения цветов посчитаны той же математикой, что в iced:
palette::Palette::GRUVBOX_DARK + Extended::generate (mix — в linear-light,
deviate — в OKLCH), readable() — подбор текста под фон.
Геометрия и типографика — из десктопного src/styling.rs:
DEFAULT_SPACING = 16 (half 8 / quarter 4), BUTTON_RADIUS = 8,
ACCENT_FONT_SIZE = 22, default_text_size = 18, шрифт Noto Sans JP.
Плоско: ни одна iced-стиль-функция десктопа не задаёт shadow -> теней нет. */
/* Локальный Noto Sans JP (variable, 100–900): файл лежит в public/fonts,
в проде отдаётся как /fonts/NotoSansJP-VariableFont_wght.ttf. */
@font-face {
font-family: 'Noto Sans JP';
src: url('/fonts/NotoSansJP-VariableFont_wght.ttf') format('truetype');
font-weight: 100 900;
font-style: normal;
font-display: swap;
}
:root {
font-family: system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
color-scheme: dark;
/* --- Gruvbox Dark: 4 акцента + фон/текст (Palette::GRUVBOX_DARK) --- */
--bg: #282828; /* BG_0 */
--fg: #fbf1c7; /* FG0_29 */
/* Поверхности background.* = deviate(#282828, amount) в OKLCH; полный набор
десктопа — .03 / .07 / .1 / .125 / .15 / .175 / .2 (#2f2f2f … #5d5d5d). */
--bg-weakest: #2f2f2f; /* .03 — карточки */
--bg-weak: #414141; /* .1 — рамки карточек, раскрытые доп. поля */
--bg-strong: #4f4f4f; /* .15 — рамки инпутов и rule::default */
/* secondary.* = mix(deviate(bg, .1/.2/.3), text, .2); это же — «приглушённый»
текст десктопа (placeholder у text_input) и фон cta_button */
--muted: #908c7c; /* secondary.base */
--secondary: #908c7c;
--secondary-strong: #9f9b8d;
--on-secondary: #1f1e1a;
/* primary.* : base #458588 (BLUE_4), weak = 0.6*base + 0.4*bg, strong = +0.1 L */
--primary: #458588;
--primary-weak: #3b6c6e;
--primary-strong: #49a8ac;
--on-primary: #0a1c1d; /* readable(primary, text) в iced даёт почти чёрный */
/* success/warning/danger: strong = deviate(base, .1), weak = mix(base, bg, .4) */
--success: #98971a; /* GREEN_2 */
--success-strong: #7c7900;
--on-success: #222102;
--warning: #d79921; /* YELLOW_3 */
--warning-strong: #b67a00;
--on-warning: #342203;
--danger: #cc241d; /* RED_1 */
--danger-strong: #ff0000; /* deviate(#cc241d, .1) */
--on-danger: #fdf9f9;
/* Текст ошибки: чистый --danger даёт на тёмном фоне контраст 2.4:1,
поэтому для текста берём bright red из той же палитры gruvbox (red_1). */
--danger-text: #fb4934;
/* Приглушённый текст = iced text.scale_alpha(...) */
--fg-soft: rgb(251 241 199 / 0.85);
--fg-faint: rgb(251 241 199 / 0.3);
/* Оверлей кнопок-строк: iced nav_style -> WHITE.scale_alpha(0.1) */
--surface-press: rgb(255 255 255 / 0.1);
--surface-press-soft: rgb(255 255 255 / 0.08);
/* --- Геометрия (styling.rs) --- */
--space: 16px;
--space-half: 8px;
--space-quarter: 4px;
--radius: 8px; /* BUTTON_RADIUS */
--radius-sm: 5px; /* container::bordered_box */
--radius-xs: 2px; /* iced border::rounded(2) — карточки/инпуты по умолчанию */
/* Noto Sans JP первым: японский и латиница — из него, кириллица
(её в Noto Sans JP нет) автоматически доберётся из системного
шрифта — браузеры подбирают fallback поглифно. */
font-family: 'Noto Sans JP', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
font-size: 17px; /* десктоп: default_text_size 18 */
line-height: 1.5;
color: #1e1b2e;
background: #f4f1fa;
color: var(--fg);
background: var(--bg);
}
* {
@@ -16,28 +95,25 @@ body {
min-height: 100dvh;
display: flex;
justify-content: center;
background: var(--bg);
color: var(--fg);
padding: env(safe-area-inset-top) env(safe-area-inset-right)
env(safe-area-inset-bottom) env(safe-area-inset-left);
}
/* iced: selection = primary.weak, а ярких фонов при выделении нет */
::selection {
background: var(--primary-weak);
color: var(--fg);
}
#app {
width: 100%;
max-width: 480px;
padding: 24px 16px 32px;
padding: var(--space) var(--space) calc(var(--space) * 2);
display: flex;
flex-direction: column;
gap: 16px;
}
.app-header h1 {
margin: 0;
font-size: 1.75rem;
letter-spacing: 0.02em;
}
.app-subtitle {
margin: 4px 0 0;
color: #6d6390;
gap: var(--space);
}
/* --- Топ-бар + бургер --- */
@@ -48,12 +124,15 @@ body {
gap: 12px;
}
/* Кнопки навигации десктопа — button::text / nav_style: без фона и рамки, радиус 8,
на нажатие — 10% белого поверх фона. */
.burger-btn {
width: 44px;
height: 44px;
border: 2px solid #d9d2f0;
border-radius: 12px;
background: #fff;
border: none;
border-radius: var(--radius);
background: transparent;
color: var(--fg);
cursor: pointer;
display: flex;
flex-direction: column;
@@ -68,24 +147,30 @@ body {
display: block;
width: 20px;
height: 2px;
border-radius: 2px;
background: #4c446b;
border-radius: 1px;
background: currentColor;
}
.burger-btn:active,
.burger-btn[aria-expanded='true'] {
border-color: #7c3aed;
background: var(--surface-press);
}
.burger-btn[aria-expanded='true'] span {
background: var(--primary-strong);
}
.top-bar-title h1 {
margin: 0;
font-size: 1.4rem;
font-size: 22px; /* ACCENT_FONT_SIZE */
font-weight: 700;
line-height: 1.2;
}
.top-bar-title p {
margin: 0;
font-size: 0.9rem;
color: #6d6390;
color: var(--muted);
}
.top-bar-spacer {
@@ -95,7 +180,7 @@ body {
.app-content {
display: flex;
flex-direction: column;
gap: 16px;
gap: var(--space);
}
/* --- Выдвижное меню --- */
@@ -103,7 +188,7 @@ body {
.drawer-overlay {
position: fixed;
inset: 0;
background: rgb(30 27 46 / 0.45);
background: rgb(40 40 40 / 0.72);
z-index: 40;
}
@@ -111,75 +196,87 @@ body {
display: none;
}
/* Панель — поверхность background.weakest; тень не используем (iced — плоско),
поэтому отделяем её рамкой. */
.drawer {
position: fixed;
top: 0;
left: 0;
bottom: 0;
width: min(300px, 85vw);
background: #fff;
background: var(--bg-weakest);
border-right: 1px solid var(--bg-weak);
z-index: 50;
transform: translateX(-105%);
transition: transform 0.22s ease;
/* visibility гасит панель до/после анимации: закрытая она не видна и не мешает
(в dev Vite вставляет CSS скриптом, поэтому без этого панель мигала бы на первом кадре) */
visibility: hidden;
transition: transform 0.22s ease, visibility 0s linear 0.22s;
display: flex;
flex-direction: column;
gap: 8px;
padding: 20px 14px;
box-shadow: 8px 0 32px rgb(30 27 46 / 0.18);
gap: var(--space-quarter);
padding: var(--space) var(--space-half);
}
.drawer.open {
transform: translateX(0);
visibility: visible;
transition: transform 0.22s ease, visibility 0s;
}
.drawer-header {
padding: 4px 8px 14px;
border-bottom: 1px solid #eee9fb;
margin-bottom: 8px;
padding: var(--space-quarter) var(--space-half) 14px;
border-bottom: 1px solid var(--bg-strong);
margin-bottom: var(--space-half);
}
.drawer-logo {
font-size: 1.3rem;
font-size: 22px; /* ACCENT_FONT_SIZE */
font-weight: 800;
color: #7c3aed;
color: var(--primary-strong);
}
.drawer-subtitle {
font-size: 0.9rem;
color: #6d6390;
color: var(--muted);
}
.drawer-btn {
border: none;
background: transparent;
text-align: left;
font: inherit;
font-size: 1.05rem;
font-weight: 600;
color: #1e1b2e;
color: var(--fg);
padding: 13px 12px;
border-radius: 12px;
border-radius: var(--radius);
cursor: pointer;
touch-action: manipulation;
}
.drawer-btn.active {
background: #f1eafe;
color: #6d28d9;
.drawer-btn:hover,
.drawer-btn:active {
background: var(--surface-press);
}
.drawer-btn:active {
background: #e9defc;
/* Активный пункт = nav_style с primary-цветом текста (как «Сбросить» в словаре) */
.drawer-btn.active {
background: var(--surface-press);
color: var(--primary-strong);
}
body.drawer-open {
overflow: hidden;
}
/* --- Карточка = container::bordered_box: background.weakest + рамка background.weak --- */
.card {
background: #fff;
border-radius: 16px;
padding: 20px 16px;
box-shadow: 0 8px 24px rgb(124 58 237 / 0.12);
background: var(--bg-weakest);
border: 1px solid var(--bg-weak);
border-radius: var(--radius-sm);
padding: var(--space);
display: flex;
flex-direction: column;
gap: 12px;
@@ -188,11 +285,12 @@ body.drawer-open {
.card-title {
margin: 0;
font-size: 1.25rem;
font-weight: 700;
}
.card-hint {
margin: 0;
color: #6d6390;
color: var(--muted);
font-size: 0.95rem;
}
@@ -201,83 +299,137 @@ body.drawer-open {
font-size: 0.95rem;
}
.token-input {
width: 100%;
padding: 14px 12px;
font-size: 1.05rem;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: 0.04em;
border: 2px solid #d9d2f0;
border-radius: 12px;
/* --- Инпуты = text_input::default ---
фон background.base, рамка 1px background.strong, радиус 2,
placeholder = secondary.base, focus -> рамка primary.strong,
disabled -> фон background.weak. */
.token-input,
.words-search,
.words-group {
padding: 12px;
font: inherit;
font-size: 1rem;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--bg-strong);
border-radius: var(--radius-xs);
outline: none;
appearance: none;
}
.token-input:focus {
border-color: #7c3aed;
.token-input::placeholder,
.words-search::placeholder {
color: var(--muted);
}
.token-input:focus,
.words-search:focus,
.words-group:focus {
border-color: var(--primary-strong);
}
.token-input:disabled {
background: var(--bg-weak);
color: var(--muted);
}
.token-input {
width: 100%;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
letter-spacing: 0.04em;
}
.token-input.invalid {
border-color: #dc2626;
background: #fef2f2;
border-color: var(--danger-text);
}
.token-meta {
display: flex;
justify-content: space-between;
align-items: baseline;
gap: 8px;
gap: var(--space-half);
min-height: 1.4em;
}
.token-error {
color: #dc2626;
color: var(--danger-text);
font-size: 0.9rem;
}
.token-counter {
color: #6d6390;
color: var(--muted);
font-variant-numeric: tabular-nums;
white-space: nowrap;
}
/* --- Кнопки --- */
.btn-row {
display: flex;
gap: 12px;
}
/* cta_button: secondary.base с рамкой 1px (рамка в iced прозрачная) + BUTTON_RADIUS */
.btn {
flex: 1;
padding: 14px 12px;
padding: 12px 14px;
font: inherit;
font-size: 1rem;
font-weight: 600;
border-radius: 12px;
border: 2px solid #7c3aed;
background: #fff;
color: #7c3aed;
border: 1px solid transparent;
border-radius: var(--radius);
background: var(--secondary);
color: var(--on-secondary);
cursor: pointer;
touch-action: manipulation;
}
.btn-primary {
background: #7c3aed;
color: #fff;
.btn:not(:disabled):active {
background: var(--secondary-strong);
}
/* jl_button! -> button::primary: заливка primary, текст readable(primary, text) */
.btn-primary {
background: var(--primary);
color: var(--on-primary);
}
.btn-primary:not(:disabled):active {
background: var(--primary-strong);
}
/* disabled(base) в iced: фон и текст на 50% альфы */
.btn:disabled {
opacity: 0.4;
opacity: 0.5;
cursor: not-allowed;
}
.btn:not(:disabled):active {
transform: scale(0.98);
/* Кнопка-ссылка «назад» = nav_style: прозрачная, текст основного цвета */
.btn-ghost {
align-self: flex-start;
flex: none;
padding: 10px 12px;
border-color: transparent;
background: transparent;
color: var(--fg);
}
.btn-ghost:not(:disabled):active {
background: var(--surface-press);
}
/* --- Статусы ---
Форма уведомления iced: фон = color.scale_alpha(0.3), рамка 1px тем же цветом,
радиус 8. Нейтральный статус — «цвет» secondary, баннер базы — warning. */
.status-bar {
margin: 0;
padding: 10px 12px;
border-radius: 10px;
background: #f4f1fa;
color: #4c446b;
border-radius: var(--radius);
background: rgb(144 140 124 / 0.18);
border: 1px solid rgb(144 140 124 / 0.45);
color: var(--fg);
font-size: 0.95rem;
word-break: break-word;
}
@@ -286,17 +438,16 @@ body.drawer-open {
display: none;
}
/* Компактный баннер статуса БД: виден только при проблеме.
В норме скрыт — слова занимают весь экран. */
.db-banner {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 12px;
border-radius: 10px;
background: #fff7e6;
color: #7a5b00;
font-size: 0.85rem;
gap: var(--space-half);
padding: 10px 12px;
border-radius: var(--radius);
background: rgb(215 153 33 / 0.3);
border: 1px solid var(--warning);
color: var(--fg);
font-size: 0.9rem;
}
.db-banner[hidden] {
@@ -311,15 +462,20 @@ body.drawer-open {
.db-banner-link {
flex: none;
border: none;
border-radius: var(--radius);
background: transparent;
padding: 4px;
padding: 4px 6px;
font: inherit;
font-weight: 700;
color: #7c3aed;
color: var(--fg);
cursor: pointer;
touch-action: manipulation;
}
.db-banner-link:active {
background: var(--surface-press);
}
.db-banner-link[hidden] {
display: none;
}
@@ -328,23 +484,12 @@ body.drawer-open {
.words-toolbar {
display: flex;
gap: 8px;
gap: var(--space-half);
}
.words-search {
flex: 1;
min-width: 0;
padding: 12px;
font-size: 1rem;
border: 2px solid #d9d2f0;
border-radius: 12px;
outline: none;
background: #fff;
color: inherit;
}
.words-search:focus {
border-color: #7c3aed;
}
.words-group {
@@ -353,15 +498,6 @@ body.drawer-open {
padding: 12px 8px;
font-size: 0.95rem;
font-weight: 600;
color: #4c446b;
border: 2px solid #d9d2f0;
border-radius: 12px;
background: #fff;
outline: none;
}
.words-group:focus {
border-color: #7c3aed;
}
.words-list {
@@ -369,9 +505,10 @@ body.drawer-open {
flex-direction: column;
}
/* Плоская строка: ключ + перевод слева, тег справа. Без карточек. */
/* Плоская строка: ключ + перевод слева, тег справа. Разделитель = rule::default
(background.strong), нажатие — 10% белого (nav_style десктопа). */
.word-row-wrap {
border-bottom: 1px solid #eee9fb;
border-bottom: 1px solid var(--bg-strong);
}
.word-row-wrap:last-child {
@@ -381,9 +518,9 @@ body.drawer-open {
.word-row {
display: flex;
align-items: baseline;
gap: 8px;
gap: var(--space-half);
width: 100%;
padding: 11px 4px;
padding: 11px 6px;
border: none;
background: transparent;
font: inherit;
@@ -391,19 +528,16 @@ body.drawer-open {
text-align: left;
cursor: pointer;
touch-action: manipulation;
border-radius: 8px;
border-radius: var(--radius-xs);
}
.word-row:disabled {
cursor: default;
}
.word-row:not(:disabled):active {
background: #f4f1fa;
}
.word-row:not(:disabled):active,
.word-row.open {
background: #f4f1fa;
background: var(--surface-press-soft);
}
.word-main {
@@ -417,14 +551,14 @@ body.drawer-open {
}
.word-sep {
color: #6d6390;
color: var(--muted);
}
.word-value {
color: #4c446b;
color: var(--fg-soft);
}
/* Тег справа — плоской подписью, без плашки */
/* Тег справа — плоской подписью в цвете secondary.base (как placeholder в iced) */
.word-tag {
flex: none;
max-width: 38%;
@@ -432,7 +566,7 @@ body.drawer-open {
text-overflow: ellipsis;
white-space: nowrap;
font-size: 0.8rem;
color: #6d6390;
color: var(--muted);
text-align: right;
}
@@ -442,8 +576,7 @@ body.drawer-open {
width: 6px;
height: 6px;
border-radius: 50%;
background: #7c3aed;
opacity: 0.55;
background: var(--primary-strong);
align-self: center;
}
@@ -452,20 +585,21 @@ body.drawer-open {
display: none;
}
/* container::rounded_box: фон background.weak, радиус 2 */
.word-extra-list {
margin: 0 0 10px;
padding: 10px 12px;
background: #f4f1fa;
border-radius: 10px;
background: var(--bg-weak);
border-radius: var(--radius-xs);
display: flex;
flex-direction: column;
gap: 8px;
gap: var(--space-half);
}
.word-extra-row dt {
font-size: 0.8rem;
font-weight: 700;
color: #6d6390;
color: var(--muted);
}
.word-extra-row dd {
@@ -475,14 +609,14 @@ body.drawer-open {
/* Пустые значения в раскрытии колоды — приглушённое тире */
.empty-val {
opacity: 0.5;
color: var(--fg-faint);
}
/* Кнопка-строка колоды: div-обёртка + button в одном элементе.
.word-row сбрасывает border — возвращаем разделитель. */
button.word-row-wrap {
border: none;
border-bottom: 1px solid #eee9fb;
border-bottom: 1px solid var(--bg-strong);
}
button.word-row-wrap:last-child {
@@ -490,55 +624,43 @@ button.word-row-wrap:last-child {
}
button.word-row-wrap:not(:disabled):hover {
background: #f4f1fa;
}
/* Призрачная кнопка «назад» + подзаголовок на странице колоды */
.btn-ghost {
align-self: flex-start;
flex: none;
border: 2px solid #d9d2f0;
background: #fff;
color: #4c446b;
box-shadow: none;
}
.btn-ghost:active {
background: #f1eafe;
border-color: #7c3aed;
background: var(--surface-press-soft);
}
/* Форма редактирования колоды: инпуты + статус. id/name — только чтение. */
.deck-form {
display: flex;
flex-direction: column;
gap: 8px;
gap: var(--space-half);
margin-top: 12px;
}
.deck-form .field-label {
margin-top: 4px;
margin-top: var(--space-quarter);
}
.deck-form .btn-row {
margin-top: 8px;
margin-top: var(--space-half);
}
.deck-form .card-hint:empty {
display: none;
}
/* Подзаголовок блока = text + rule::default под ним */
.deck-sub {
margin: 16px 0 8px;
margin: var(--space) 0 var(--space-half);
padding-bottom: var(--space-half);
border-bottom: 1px solid var(--bg-strong);
font-size: 1rem;
color: #4c446b;
color: var(--fg);
}
/* Строка колоды: div-обёртка — name слева тянется, «▶» справа фиксирован. */
.deck-row {
display: flex;
align-items: stretch;
gap: 4px;
gap: var(--space-quarter);
}
.deck-name {
@@ -546,30 +668,30 @@ button.word-row-wrap:not(:disabled):hover {
min-width: 0;
}
/* deck-play -> button::primary, но компактная цель под палец */
.deck-play {
flex: none;
align-self: center;
width: 44px;
height: 44px;
border: 2px solid #d9d2f0;
border-radius: 12px;
background: #f1eafe;
color: #6d28d9;
border: none;
border-radius: var(--radius);
background: var(--primary);
color: var(--on-primary);
font-size: 1rem;
cursor: pointer;
touch-action: manipulation;
}
.deck-play:active {
background: #e9defc;
border-color: #7c3aed;
background: var(--primary-strong);
}
/* --- История ответов колоды: плоские строки, цветы оценок --- */
/* --- История ответов колоды: плоские строки, оценки-плашки --- */
.hist-row-wrap {
padding: 9px 4px;
border-bottom: 1px solid #eee9fb;
padding: 9px 6px;
border-bottom: 1px solid var(--bg-strong);
}
.hist-row-wrap:last-child {
@@ -583,26 +705,45 @@ button.word-row-wrap:not(:disabled):hover {
.hist-meta {
display: flex;
align-items: baseline;
gap: 8px;
margin-top: 4px;
gap: var(--space-half);
margin-top: var(--space-quarter);
}
/* Плашка оценки = форма уведомления iced: 30% цвета + рамка 1px тем же цветом */
.hist-grade {
flex: none;
padding: 2px 10px;
border: 1px solid transparent;
border-radius: 999px;
font-size: 0.8rem;
font-weight: 700;
color: var(--fg);
}
.hist-grade.grade-fail {
background: rgb(204 36 29 / 0.3);
border-color: var(--danger);
}
.hist-grade.grade-hard {
background: rgb(215 153 33 / 0.3);
border-color: var(--warning);
}
.hist-grade.grade-good {
background: rgb(152 151 26 / 0.3);
border-color: var(--success);
}
.hist-grade.grade-fail,
.hist-grade.grade-hard,
.hist-grade.grade-good,
.hist-grade.grade-easy {
border: none;
background: rgb(69 133 136 / 0.3);
border-color: var(--primary);
}
/* --- Повторение: карточка + 4 кнопки оценок --- */
/* --- Повторение: карточка + 4 кнопки оценок ---
Десктоп: forward и backward на всю ширину, между ними rule::horizontal(2),
размеры текста draw_key = 36, draw_value/reading/context = 24,
до открытия backward показывает «?», подсказка — text.scale_alpha(0.3). */
#review-body {
display: flex;
@@ -615,10 +756,10 @@ button.word-row-wrap:not(:disabled):hover {
.review-card {
display: block;
width: 100%;
border: 2px solid #d9d2f0;
border-radius: 14px;
background: #fbfaff;
padding: 20px 16px;
border: 1px solid var(--bg-weak);
border-radius: var(--radius-sm);
background: var(--bg-weakest);
padding: var(--space) 12px;
font: inherit;
color: inherit;
text-align: center;
@@ -627,72 +768,91 @@ button.word-row-wrap:not(:disabled):hover {
}
.review-card:active {
border-color: #7c3aed;
border-color: var(--primary-strong);
}
.review-side {
display: flex;
flex-direction: column;
gap: 8px;
gap: var(--space-half);
}
.review-line {
margin: 0;
overflow-wrap: break-word;
font-size: 1.15rem;
}
/* Первая строка лицевой стороны — крупный ключ (draw_key, 36px) */
.review-front .review-line:first-child {
font-size: 1.5rem;
font-weight: 800;
font-size: 36px;
font-weight: 600;
line-height: 1.25;
}
/* Разделитель лицевой и обратной стороны — rule::horizontal(2) */
.review-hr {
border: none;
border-top: 1px solid #eee9fb;
border-top: 2px solid var(--bg-strong);
margin: 14px 0;
}
.review-tap {
margin: 12px 0 0;
font-size: 0.85rem;
color: #6d6390;
color: var(--fg-faint);
}
.review-grades {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
gap: var(--space-half);
}
.review-grade {
margin: 0;
}
/* Оценки — плоские оттенки смысла, текст всегда тёмный для контраста. */
.grade-fail {
background: #fee2e2;
color: #991b1b;
box-shadow: none;
/* Оценки — семантические заливки палитры; текст подобран readable(), как в iced.
Не получилось — danger, трудно — warning, хорошо — success, легко — primary. */
.review-grade.grade-fail {
background: var(--danger);
color: var(--on-danger);
}
.grade-hard {
background: #ffedd5;
color: #9a3412;
box-shadow: none;
.review-grade.grade-fail:not(:disabled):active {
background: var(--danger-strong);
}
.grade-good {
background: #dcfce7;
color: #166534;
box-shadow: none;
.review-grade.grade-hard {
background: var(--warning);
color: var(--on-warning);
}
.grade-easy {
background: #dbeafe;
color: #1e40af;
box-shadow: none;
.review-grade.grade-hard:not(:disabled):active {
background: var(--warning-strong);
}
.review-grade:active {
filter: brightness(0.95);
.review-grade.grade-good {
background: var(--success);
color: var(--on-success);
}
.review-grade.grade-good:not(:disabled):active {
background: var(--success-strong);
}
.review-grade.grade-easy {
background: var(--primary);
color: var(--on-primary);
}
.review-grade.grade-easy:not(:disabled):active {
background: var(--primary-strong);
}
@media (prefers-reduced-motion: reduce) {
.drawer {
transition: none;
}
}