diff --git a/dist/assets/index-B1EUgva9.css b/dist/assets/index-B1EUgva9.css deleted file mode 100644 index 4d82558..0000000 --- a/dist/assets/index-B1EUgva9.css +++ /dev/null @@ -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:#7c3aed;opacity:.55;align-self:center}.word-extra[hidden]{display:none}.word-extra-list{margin:0 0 10px;padding:10px 12px;background:#f4f1fa;border-radius:10px;display:flex;flex-direction:column;gap:8px}.word-extra-row dt{font-size:.8rem;font-weight:700;color:#6d6390}.word-extra-row dd{margin:2px 0 0;word-break:break-word}.empty-val{opacity:.5}button.word-row-wrap{border:none;border-bottom:1px solid #eee9fb}button.word-row-wrap:last-child{border-bottom:none}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}.deck-form{display:flex;flex-direction:column;gap:8px;margin-top:12px}.deck-form .field-label{margin-top:4px}.deck-form .btn-row{margin-top:8px}.deck-form .card-hint:empty{display:none}.deck-sub{margin:16px 0 8px;font-size:1rem;color:#4c446b}.deck-row{display:flex;align-items:stretch;gap:4px}.deck-name{flex:1;min-width:0}.deck-play{flex:none;align-self:center;width:44px;height:44px;border:2px solid #d9d2f0;border-radius:12px;background:#f1eafe;color:#6d28d9;font-size:1rem;cursor:pointer;touch-action:manipulation}.deck-play:active{background:#e9defc;border-color:#7c3aed}.hist-row-wrap{padding:9px 4px;border-bottom:1px solid #eee9fb}.hist-row-wrap:last-child{border-bottom:none}.hist-main{overflow-wrap:break-word}.hist-meta{display:flex;align-items:baseline;gap:8px;margin-top:4px}.hist-grade{flex:none;padding:2px 10px;border-radius:999px;font-size:.8rem;font-weight:700}.hist-grade.grade-fail,.hist-grade.grade-hard,.hist-grade.grade-good,.hist-grade.grade-easy{border:none}#review-body{display:flex;flex-direction:column;gap:12px;margin-top:12px}.review-card{display:block;width:100%;border:2px solid #d9d2f0;border-radius:14px;background:#fbfaff;padding:20px 16px;font:inherit;color:inherit;text-align:center;cursor:pointer;touch-action:manipulation}.review-card:active{border-color:#7c3aed}.review-side{display:flex;flex-direction:column;gap:8px}.review-line{margin:0;overflow-wrap:break-word}.review-front .review-line:first-child{font-size:1.5rem;font-weight:800}.review-hr{border:none;border-top:1px solid #eee9fb;margin:14px 0}.review-tap{margin:12px 0 0;font-size:.85rem;color:#6d6390}.review-grades{display:grid;grid-template-columns:1fr 1fr;gap:8px}.review-grade{margin:0}.grade-fail{background:#fee2e2;color:#991b1b;box-shadow:none}.grade-hard{background:#ffedd5;color:#9a3412;box-shadow:none}.grade-good{background:#dcfce7;color:#166534;box-shadow:none}.grade-easy{background:#dbeafe;color:#1e40af;box-shadow:none}.review-grade:active{filter:brightness(.95)} diff --git a/dist/assets/index-Bk0XDs7i.js b/dist/assets/index-C0jVdQGX.js similarity index 100% rename from dist/assets/index-Bk0XDs7i.js rename to dist/assets/index-C0jVdQGX.js diff --git a/dist/assets/index-Db5EzFTV.css b/dist/assets/index-Db5EzFTV.css new file mode 100644 index 0000000..d938c87 --- /dev/null +++ b/dist/assets/index-Db5EzFTV.css @@ -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-primary{background:var(--primary);color:var(--on-primary)}.btn-primary:not(:disabled):active{background:var(--primary-strong)}.btn:disabled{opacity:.5;cursor:not-allowed}.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)}.status-bar{margin:0;padding:10px 12px;border-radius:var(--radius);background:#908c7c2e;border:1px solid rgb(144 140 124 / .45);color:var(--fg);font-size:.95rem;word-break:break-word}.status-bar[hidden]{display:none}.db-banner{display:flex;align-items:center;gap:var(--space-half);padding:10px 12px;border-radius:var(--radius);background:#d799214d;border:1px solid var(--warning);color:var(--fg);font-size:.9rem}.db-banner[hidden]{display:none}.db-banner-text{flex:1;min-width:0}.db-banner-link{flex:none;border:none;border-radius:var(--radius);background:transparent;padding:4px 6px;font:inherit;font-weight:700;color:var(--fg);cursor:pointer;touch-action:manipulation}.db-banner-link:active{background:var(--surface-press)}.db-banner-link[hidden]{display:none}.words-toolbar{display:flex;gap:var(--space-half)}.words-search{flex:1;min-width:0}.words-group{flex:none;max-width:42%;padding:12px 8px;font-size:.95rem;font-weight:600}.words-list{display:flex;flex-direction:column}.word-row-wrap{border-bottom:1px solid var(--bg-strong)}.word-row-wrap:last-child{border-bottom:none}.word-row{display:flex;align-items:baseline;gap:var(--space-half);width:100%;padding:11px 6px;border:none;background:transparent;font:inherit;color:inherit;text-align:left;cursor:pointer;touch-action:manipulation;border-radius:var(--radius-xs)}.word-row:disabled{cursor:default}.word-row:not(:disabled):active,.word-row.open{background:var(--surface-press-soft)}.word-main{flex:1;min-width:0;overflow-wrap:break-word}.word-key{font-weight:700}.word-sep{color:var(--muted)}.word-value{color:var(--fg-soft)}.word-tag{flex:none;max-width:38%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;font-size:.8rem;color:var(--muted);text-align:right}.word-dot{flex:none;width:6px;height:6px;border-radius:50%;background:var(--primary-strong);align-self:center}.word-extra[hidden]{display:none}.word-extra-list{margin:0 0 10px;padding:10px 12px;background:var(--bg-weak);border-radius:var(--radius-xs);display:flex;flex-direction:column;gap:var(--space-half)}.word-extra-row dt{font-size:.8rem;font-weight:700;color:var(--muted)}.word-extra-row dd{margin:2px 0 0;word-break:break-word}.empty-val{color:var(--fg-faint)}button.word-row-wrap{border:none;border-bottom:1px solid var(--bg-strong)}button.word-row-wrap:last-child{border-bottom:none}button.word-row-wrap:not(:disabled):hover{background:var(--surface-press-soft)}.deck-form{display:flex;flex-direction:column;gap:var(--space-half);margin-top:12px}.deck-form .field-label{margin-top:var(--space-quarter)}.deck-form .btn-row{margin-top:var(--space-half)}.deck-form .card-hint:empty{display:none}.deck-sub{margin:var(--space) 0 var(--space-half);padding-bottom:var(--space-half);border-bottom:1px solid var(--bg-strong);font-size:1rem;color:var(--fg)}.deck-row{display:flex;align-items:stretch;gap:var(--space-quarter)}.deck-name{flex:1;min-width:0}.deck-play{flex:none;align-self:center;width:44px;height:44px;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:var(--primary-strong)}.hist-row-wrap{padding:9px 6px;border-bottom:1px solid var(--bg-strong)}.hist-row-wrap:last-child{border-bottom:none}.hist-main{overflow-wrap:break-word}.hist-meta{display:flex;align-items:baseline;gap:var(--space-half);margin-top:var(--space-quarter)}.hist-grade{flex:none;padding:2px 10px;border:1px solid transparent;border-radius:999px;font-size:.8rem;font-weight:700;color:var(--fg)}.hist-grade.grade-fail{background:#cc241d4d;border-color:var(--danger)}.hist-grade.grade-hard{background:#d799214d;border-color:var(--warning)}.hist-grade.grade-good{background:#98971a4d;border-color:var(--success)}.hist-grade.grade-easy{background:#4585884d;border-color:var(--primary)}#review-body{display:flex;flex-direction:column;gap:12px;margin-top:12px}.review-card{display:block;width:100%;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;cursor:pointer;touch-action:manipulation}.review-card:active{border-color:var(--primary-strong)}.review-side{display:flex;flex-direction:column;gap:var(--space-half)}.review-line{margin:0;overflow-wrap:break-word;font-size:1.15rem}.review-front .review-line:first-child{font-size:36px;font-weight:600;line-height:1.25}.review-hr{border:none;border-top:2px solid var(--bg-strong);margin:14px 0}.review-tap{margin:12px 0 0;font-size:.85rem;color:var(--fg-faint)}.review-grades{display:grid;grid-template-columns:1fr 1fr;gap:var(--space-half)}.review-grade{margin:0}.review-grade.grade-fail{background:var(--danger);color:var(--on-danger)}.review-grade.grade-fail:not(:disabled):active{background:var(--danger-strong)}.review-grade.grade-hard{background:var(--warning);color:var(--on-warning)}.review-grade.grade-hard:not(:disabled):active{background:var(--warning-strong)}.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}} diff --git a/dist/fonts/.gitkeep b/dist/fonts/.gitkeep new file mode 100644 index 0000000..83e9385 --- /dev/null +++ b/dist/fonts/.gitkeep @@ -0,0 +1,3 @@ +# Положи сюда файл шрифта Noto Sans JP (например NotoSansJP-Variable.woff2). +# Папка public/fonts целиком копируется в dist при сборке — +# шрифт будет доступен офлайн в PWA. diff --git a/dist/fonts/NotoSansJP-VariableFont_wght.ttf b/dist/fonts/NotoSansJP-VariableFont_wght.ttf new file mode 100644 index 0000000..6c730cd Binary files /dev/null and b/dist/fonts/NotoSansJP-VariableFont_wght.ttf differ diff --git a/dist/index.html b/dist/index.html index d0a6558..96e8e18 100644 --- a/dist/index.html +++ b/dist/index.html @@ -3,10 +3,11 @@ - + + JapLearn — интервальные повторения - - + +
diff --git a/index.html b/index.html index d537e3e..dd54c1d 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,8 @@ - + + JapLearn — интервальные повторения diff --git a/public/fonts/.gitkeep b/public/fonts/.gitkeep new file mode 100644 index 0000000..83e9385 --- /dev/null +++ b/public/fonts/.gitkeep @@ -0,0 +1,3 @@ +# Положи сюда файл шрифта Noto Sans JP (например NotoSansJP-Variable.woff2). +# Папка public/fonts целиком копируется в dist при сборке — +# шрифт будет доступен офлайн в PWA. diff --git a/public/fonts/NotoSansJP-VariableFont_wght.ttf b/public/fonts/NotoSansJP-VariableFont_wght.ttf new file mode 100644 index 0000000..6c730cd Binary files /dev/null and b/public/fonts/NotoSansJP-VariableFont_wght.ttf differ diff --git a/src/style.css b/src/style.css index 4af0ce0..d4826e2 100644 --- a/src/style.css +++ b/src/style.css @@ -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; + } }