baldav/frontend/css/styles.css

2032 lines
36 KiB
CSS
Raw Normal View History

* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
:root {
--bg: #121212;
--surface: #1e1e1e;
--primary: #bb86fc;
--on-bg: #e0e0e0;
--on-surface: #ffffff;
--positive: #cf6679;
--negative: #03dac6;
--muted: #888;
--border: #333;
--radius: 8px;
--safe-area-top: env(safe-area-inset-top, 0px);
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
}
html, body {
height: 100%;
}
body {
font-family: system-ui, -apple-system, sans-serif;
background: var(--bg);
color: var(--on-bg);
font-size: 16px;
line-height: 1.4;
overflow-x: hidden;
-webkit-tap-highlight-color: transparent;
}
#app {
max-width: 600px;
margin: 0 auto;
height: 100%;
display: flex;
flex-direction: column;
}
@media (min-width: 768px) {
#app { max-width: 100%; padding: 0 24px; }
.summary-row { gap: 12px; padding: 14px 16px; }
.gps-map { height: 450px; }
.sd-map { height: 400px; }
.modal { max-width: 100%; }
.sd-row { gap: 16px; }
.sd-val { font-size: 22px; }
.products-screen, .settings-screen { max-width: 700px; margin: 0 auto; }
.log-section { max-width: 700px; margin: 0 auto; }
.day-header, .summary-row, .micro-strip { max-width: 700px; margin-left: auto; margin-right: auto; }
}
/* Header / date navigation */
.day-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 16px;
padding-top: calc(12px + var(--safe-area-top));
background: var(--surface);
border-bottom: 1px solid var(--border);
position: sticky;
top: 0;
z-index: 10;
}
.day-header button {
background: none;
border: none;
color: var(--primary);
font-size: 24px;
padding: 8px;
cursor: pointer;
border-radius: var(--radius);
}
.day-header button:active {
background: rgba(187, 134, 252, 0.15);
}
.day-header .date-text {
font-size: 18px;
font-weight: 500;
color: var(--on-surface);
}
/* Weight display */
.weight-row {
padding: 8px 16px;
background: var(--surface);
border-bottom: 1px solid var(--border);
font-size: 14px;
color: var(--muted);
}
.weight-row span {
color: var(--on-surface);
font-weight: 500;
}
/* Entry list */
.entries {
flex: 1;
overflow-y: auto;
padding: 8px 0;
}
.entry-item {
display: grid;
grid-template-columns: 50px 1fr 70px;
align-items: center;
padding: 10px 16px;
border-bottom: 1px solid var(--border);
}
.entry-item:active {
background: rgba(255, 255, 255, 0.05);
}
.entry-time {
font-size: 13px;
color: var(--muted);
}
.entry-desc {
font-size: 15px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.entry-kcal {
text-align: right;
font-size: 14px;
font-weight: 500;
}
.entry-kcal.intake {
color: var(--positive);
}
.entry-kcal.expenditure {
color: var(--negative);
}
/* Balance summary */
.balance-bar {
display: flex;
justify-content: space-around;
padding: 12px 16px;
background: var(--surface);
border-top: 1px solid var(--border);
font-size: 14px;
}
.balance-bar .label {
color: var(--muted);
font-size: 12px;
}
.balance-bar .value {
font-size: 18px;
font-weight: 600;
}
.balance-bar .value.deficit {
color: var(--negative);
}
.balance-bar .value.surplus {
color: var(--positive);
}
/* FAB (add button) */
.fab {
position: fixed;
bottom: 80px;
right: 24px;
width: 56px;
height: 56px;
border-radius: 50%;
background: var(--primary);
color: #000;
border: none;
font-size: 28px;
font-weight: 300;
cursor: pointer;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
display: flex;
align-items: center;
justify-content: center;
}
.fab:active {
transform: scale(0.95);
}
/* Bottom nav — see .nav-btn below for button styles */
/* Modal / add entry overlay */
.modal-overlay {
position: fixed;
inset: 0;
background: rgba(0, 0, 0, 0.7);
z-index: 100;
display: flex;
align-items: flex-end;
justify-content: center;
}
.modal-sheet {
background: var(--surface);
width: 100%;
max-width: 480px;
border-radius: 16px 16px 0 0;
padding: 20px 16px;
max-height: 85vh;
overflow-y: auto;
}
.modal-sheet h2 {
font-size: 18px;
margin-bottom: 16px;
color: var(--on-surface);
}
/* Form elements */
.form-group {
position: relative;
margin-bottom: 14px;
}
.form-group label {
display: block;
font-size: 13px;
color: var(--muted);
margin-bottom: 4px;
}
.form-group input,
.form-group select,
.form-group textarea {
width: 100%;
padding: 10px 12px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--on-surface);
font-size: 16px;
font-family: inherit;
Recipes, nutrients_guessed, seed import fixes, search improvements Recipes: - Cooking methods table + 6 presets (boiling, frying, steaming etc.) - Recipe editor with live KBJU calculation including cooking loss - Recipe detail view, log-to-diary, ingredient search - Split food log into "Products" and "Dishes" sections - Products screen sub-tabs: Продукты / Рецепты Nutrient enrichment: - 1620 store products enriched with ION micronutrients (guessed flag) - Purple tint on guessed food entries, * on daily kcal total - Seed DB v4 with nutrients_guessed column Seed import fixes: - Rust: always refresh seed DB from bundled assets on launch - Rust: check app_config_dir for main DB (plugin-sql stores there on desktop) - Rust: ATTACH-based UPDATE to sync nutrients_guessed for existing products - Rust: read all 27 columns including micronutrients - Rust: emit seed_progress events + indeterminate progress bar animation - JS: boot screen with status labels during migrations/import - JS: nutrients_guessed column created in JS migration as safety net Search: - Results sorted by starts-with-query first, then by name length - Default limit raised to 200 - Modal search results scroll without max-height cap Bug fixes: - product_id=0 for custom recipe ingredients (NOT NULL constraint) - getRecipe JOIN for cooking_method_name - calcTotals applies cooking loss from selected method - Ingredient modal targets last overlay (not parent) - select elements dark theme (color-scheme: dark) - renderToday wrapped in try/catch
2026-05-21 00:06:50 +03:00
color-scheme: dark;
-webkit-appearance: none;
appearance: none;
}
select {
background: var(--bg);
color: var(--on-surface);
color-scheme: dark;
}
.form-group input:focus,
.form-group select:focus,
.form-group textarea:focus {
outline: none;
border-color: var(--primary);
}
.type-chips {
display: flex;
flex-wrap: wrap;
gap: 8px;
}
.type-chip {
padding: 6px 14px;
border-radius: 20px;
border: 1px solid var(--border);
background: none;
color: var(--on-bg);
font-size: 14px;
cursor: pointer;
}
.type-chip.selected {
background: var(--primary);
color: #000;
border-color: var(--primary);
}
.btn-row {
display: flex;
gap: 12px;
margin-top: 16px;
}
.btn {
flex: 1;
padding: 12px;
border-radius: var(--radius);
border: none;
font-size: 16px;
font-family: inherit;
cursor: pointer;
}
.btn-primary {
background: var(--primary);
color: #000;
font-weight: 600;
}
.btn-secondary {
background: var(--border);
color: var(--on-bg);
}
/* Setup screen */
.setup-screen {
padding: 24px 16px;
display: flex;
flex-direction: column;
gap: 20px;
min-height: 100vh;
justify-content: center;
}
.setup-screen h1 {
font-size: 24px;
color: var(--on-surface);
text-align: center;
}
.setup-screen p {
color: var(--muted);
text-align: center;
font-size: 14px;
}
/* Kcal preview */
.kcal-preview {
padding: 10px 12px;
background: rgba(187, 134, 252, 0.1);
border-radius: var(--radius);
text-align: center;
font-size: 18px;
font-weight: 500;
color: var(--primary);
}
/* Empty state */
.empty-state {
flex: 1;
display: flex;
align-items: center;
justify-content: center;
color: var(--muted);
font-size: 15px;
padding: 40px;
text-align: center;
}
/* Screen layout */
.bottom-nav {
order: 2;
display: flex;
background: var(--surface);
border-top: 1px solid var(--border);
position: sticky;
bottom: 0;
}
.screen-content {
order: 1;
flex: 1;
overflow-y: auto;
padding-bottom: 8px;
}
.nav-btn {
flex: 1;
background: none;
border: none;
color: var(--muted);
font-size: 12px;
padding: 14px 4px;
padding-bottom: calc(14px + var(--safe-area-bottom));
cursor: pointer;
font-family: inherit;
text-align: center;
-webkit-tap-highlight-color: rgba(187,134,252,0.15);
}
.nav-btn:active {
background: rgba(187,134,252,0.1);
}
.nav-btn.active {
color: var(--primary);
font-weight: 600;
border-top: 2px solid var(--primary);
}
/* Day navigation */
.day-header h2 {
font-size: 15px;
font-weight: 500;
text-align: center;
}
.day-nav {
background: none;
border: none;
color: var(--primary);
font-size: 20px;
padding: 4px 10px;
cursor: pointer;
}
/* Summary cards */
.summary-row {
display: flex;
gap: 8px;
padding: 10px 12px;
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.summary-card {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 6px 4px;
border-radius: var(--radius);
background: var(--bg);
}
.summary-val {
font-size: 18px;
font-weight: 700;
}
.summary-lbl {
font-size: 10px;
color: var(--muted);
text-align: center;
}
.summary-card.surplus .summary-val { color: #cf6679; }
.summary-card.deficit .summary-val { color: #03dac6; }
.summary-card.balanced .summary-val { color: var(--on-bg); }
/* Log sections */
.log-section {
padding: 0 0 4px;
border-bottom: 1px solid var(--border);
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 14px 4px;
}
.section-header h3 {
font-size: 13px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
.add-btn {
background: none;
border: 0.5px solid var(--border);
border-radius: 6px;
color: var(--primary);
font-size: 22px;
line-height: 1;
cursor: pointer;
padding: 4px 10px;
min-width: 40px;
min-height: 36px;
display: flex;
align-items: center;
justify-content: center;
}
.log-list {
list-style: none;
padding: 0;
margin: 0;
}
.log-item {
display: flex;
align-items: center;
gap: 8px;
padding: 7px 14px;
border-top: 1px solid rgba(255,255,255,0.04);
}
.log-time {
font-size: 12px;
color: var(--muted);
min-width: 38px;
}
.log-desc {
flex: 1;
font-size: 14px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.log-desc small {
color: var(--muted);
margin-left: 4px;
font-size: 11px;
}
.log-kcal {
font-size: 13px;
color: var(--muted);
min-width: 52px;
text-align: right;
}
.log-empty {
padding: 6px 14px;
font-size: 13px;
color: var(--muted);
font-style: italic;
}
.del-btn {
background: none;
border: none;
color: #555;
font-size: 14px;
cursor: pointer;
padding: 2px 4px;
flex-shrink: 0;
}
.del-btn:active { color: #cf6679; }
.food-edit-item { cursor: pointer; }
.food-edit-item:active { background: rgba(255,255,255,0.05); }
.food-edit-item .log-desc { white-space: normal; }
/* Sleep info */
.sleep-info {
padding: 6px 14px 8px;
display: flex;
flex-direction: column;
gap: 2px;
}
.sleep-line {
font-size: 13px;
color: var(--on-bg);
}
/* Modal */
.modal-overlay {
position: fixed;
inset: 0;
background: var(--bg);
z-index: 500;
display: flex;
align-items: stretch;
justify-content: center;
}
.modal {
background: var(--surface);
width: 100%;
border-radius: 0;
padding: 0;
height: 100%;
padding-top: var(--safe-area-top);
padding-bottom: var(--safe-area-bottom);
display: flex;
flex-direction: column;
}
.modal-header {
display: flex;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid var(--border);
flex-shrink: 0;
}
.modal-back {
background: none;
border: none;
color: var(--primary);
font-size: 20px;
padding: 4px 8px 4px 0;
cursor: pointer;
}
.modal-body {
flex: 1;
overflow-y: auto;
padding: 16px 16px 8px;
}
.modal h3 {
font-size: 17px;
margin-bottom: 16px;
}
.modal-btns {
display: flex;
gap: 10px;
padding: 12px 16px;
border-top: 1px solid var(--border);
flex-shrink: 0;
}
.btn-primary, .btn-secondary {
flex: 1;
padding: 12px;
border-radius: var(--radius);
border: none;
font-size: 16px;
font-family: inherit;
cursor: pointer;
}
.btn-primary {
background: var(--primary);
color: #000;
font-weight: 600;
}
.btn-secondary {
background: var(--border);
color: var(--on-bg);
}
.full-width { width: 100%; }
/* Products screen */
.products-screen, .stub-screen, .settings-screen {
padding: 16px;
padding-top: calc(16px + var(--safe-area-top));
}
.settings-section {
margin-bottom: 16px;
background: var(--surface);
border-radius: var(--radius);
overflow: hidden;
}
.settings-section-title {
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.5px;
padding: 8px 14px 4px;
}
.settings-item {
display: flex;
align-items: center;
justify-content: space-between;
padding: 12px 14px;
border-bottom: 1px solid var(--border);
cursor: pointer;
}
.settings-item:last-child { border-bottom: none; }
.settings-item:active { background: rgba(255,255,255,0.03); }
.settings-item-title {
font-size: 15px;
color: var(--on-surface);
}
.settings-item-desc {
font-size: 12px;
color: var(--muted);
margin-top: 2px;
}
.settings-arrow {
font-size: 20px;
color: var(--muted);
}
.settings-toggle {
width: 44px;
height: 24px;
border-radius: 12px;
border: none;
background: var(--border);
position: relative;
cursor: pointer;
flex-shrink: 0;
transition: background 0.2s;
}
.settings-toggle::after {
content: "";
position: absolute;
top: 2px;
left: 2px;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--on-bg);
transition: transform 0.2s;
}
.settings-toggle.on {
background: var(--primary);
}
.settings-toggle.on::after {
transform: translateX(20px);
}
.products-screen h2, .stub-screen h2, .settings-screen h2 {
font-size: 20px;
margin-bottom: 16px;
}
.stub-note {
color: var(--muted);
font-size: 14px;
margin-top: 8px;
}
.search-input {
width: 100%;
padding: 10px 14px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--on-surface);
font-size: 16px;
font-family: inherit;
margin-bottom: 8px;
}
.search-input:focus {
outline: none;
border-color: var(--primary);
}
/* Product count & hint */
.prod-count {
font-size: 12px;
color: var(--muted);
padding: 0 0 8px;
}
.prod-hint {
color: var(--muted);
font-size: 14px;
font-style: italic;
padding: 20px 0;
text-align: center;
}
.prod-list {
display: flex;
flex-direction: column;
gap: 6px;
max-height: calc(100vh - 209px - var(--safe-area-top) - var(--safe-area-bottom));
overflow-y: auto;
-webkit-overflow-scrolling: touch;
}
.prod-list .prod-card {
flex-shrink: 0;
}
/* Product card — collapsed */
.prod-card {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
transition: border-color 0.15s;
}
.prod-card.open {
border-color: var(--primary);
}
.prod-card-header {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
cursor: pointer;
gap: 8px;
}
.prod-card-header:active {
background: rgba(255,255,255,0.03);
}
.prod-card-name {
flex: 1;
font-size: 14px;
line-height: 1.3;
}
.prod-brand {
display: inline;
color: var(--muted);
font-size: 12px;
margin-left: 4px;
}
.prod-weight {
display: inline;
color: var(--muted);
font-size: 11px;
margin-left: 4px;
}
.prod-card-kcal {
font-size: 13px;
color: var(--muted);
white-space: nowrap;
flex-shrink: 0;
}
/* Product card — expanded */
.prod-details {
padding: 0 12px 12px;
border-top: 1px solid var(--border);
}
.prod-macros {
display: flex;
gap: 4px;
padding: 10px 0 8px;
}
.macro {
flex: 1;
display: flex;
flex-direction: column;
align-items: center;
padding: 6px 4px;
background: var(--bg);
border-radius: var(--radius);
}
.macro-val {
font-size: 15px;
font-weight: 600;
color: var(--on-surface);
}
.macro-lbl {
font-size: 10px;
color: var(--muted);
margin-top: 2px;
}
.prod-composition {
font-size: 12px;
color: var(--muted);
line-height: 1.4;
margin-bottom: 10px;
max-height: 80px;
overflow-y: auto;
}
.prod-add-btn {
width: 100%;
padding: 10px;
font-size: 14px;
}
/* Modal product search */
.search-dropdown {
position: absolute;
left: 0;
right: 0;
z-index: 100;
max-height: 60vh;
overflow-y: auto;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0,0,0,0.5);
-webkit-overflow-scrolling: touch;
}
.search-dropdown .sd-item,
.search-dropdown .modal-prod-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 10px 12px;
cursor: pointer;
border-bottom: 1px solid var(--border);
font-size: 14px;
}
.search-dropdown .sd-item:active,
.search-dropdown .modal-prod-item:active {
background: rgba(255,255,255,0.05);
}
.sd-item-kcal {
color: var(--muted);
font-size: 13px;
white-space: nowrap;
margin-left: 8px;
}
.search-dropdown .prod-card {
border: none;
border-bottom: 1px solid var(--border);
border-radius: 0;
background: transparent;
}
.modal-search-results {
position: absolute;
left: 0;
right: 0;
z-index: 100;
max-height: 50vh;
overflow-y: auto;
margin-top: 2px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
box-shadow: 0 4px 16px rgba(0,0,0,0.5);
-webkit-overflow-scrolling: touch;
}
.modal-prod-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 8px 10px;
border-bottom: 1px solid var(--border);
cursor: pointer;
font-size: 13px;
gap: 8px;
}
.modal-prod-item:active {
background: rgba(187, 134, 252, 0.1);
}
.modal-selected {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 10px;
margin-bottom: 10px;
background: rgba(187, 134, 252, 0.12);
border-radius: var(--radius);
font-size: 14px;
color: var(--primary);
cursor: pointer;
}
.modal-selected:active {
background: rgba(187, 134, 252, 0.2);
}
/* Micronutrient strip on Today */
.micro-strip {
display: flex;
gap: 6px;
padding: 8px 12px;
overflow-x: auto;
flex-shrink: 0;
scrollbar-width: none;
}
.micro-strip::-webkit-scrollbar { display: none; }
.micro-chip {
display: inline-block;
padding: 3px 8px;
border-radius: 12px;
font-size: 11px;
font-weight: 600;
flex-shrink: 0;
}
.micro-low { background: rgba(207,102,121,0.2); color: #cf6679; }
.micro-mid { background: rgba(255,183,77,0.2); color: #ffb74d; }
.micro-ok { background: rgba(3,218,198,0.2); color: #03dac6; }
/* GPS screen */
.gps-screen {
display: flex;
flex-direction: column;
height: 100%;
}
.gps-map {
height: 250px;
width: 100%;
background: #1a1a1a;
flex-shrink: 0;
z-index: 1;
}
/* Floating recording bar */
.rec-floating-bar {
display: flex;
align-items: center;
gap: 8px;
padding: 8px 16px;
padding-top: calc(8px + var(--safe-area-top));
background: rgba(187, 134, 252, 0.15);
border-bottom: 1px solid var(--primary);
cursor: pointer;
order: 0;
}
.rec-bar-dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: #f44336;
animation: pulse 1.5s infinite;
}
@keyframes pulse {
0%, 100% { opacity: 1; }
50% { opacity: 0.3; }
}
#rec-bar-text {
font-size: 13px;
color: var(--primary);
font-weight: 500;
}
/* Recording screen */
.rec-screen {
display: flex;
flex-direction: column;
height: 100%;
}
.rec-panel {
flex-shrink: 0;
}
.draw-screen {
display: flex;
flex-direction: column;
height: 100%;
}
.draw-map {
flex: 1;
min-height: 200px;
}
.rec-map-wrap {
flex: 1;
min-height: 200px;
position: relative;
}
.rec-map {
position: absolute;
inset: 0;
}
.rec-map-btn {
position: absolute;
z-index: 1000;
width: 32px;
height: 32px;
border-radius: 4px;
border: 1px solid var(--border);
background: rgba(30, 30, 30, 0.8);
color: var(--on-bg);
font-size: 14px;
font-weight: 700;
cursor: pointer;
}
.rec-map-btn.active {
background: rgba(187, 134, 252, 0.4);
border-color: var(--primary);
}
.rec-orient-lock { right: 8px; bottom: 48px; }
.rec-roads-toggle { right: 8px; bottom: 86px; }
.gps-placeholder {
position: absolute;
inset: 0;
z-index: 1000;
background: url('../img/gps-placeholder.png') center/cover;
display: flex;
align-items: center;
justify-content: center;
}
.gps-placeholder-text {
background: rgba(0,0,0,0.7);
color: var(--on-bg);
padding: 12px 24px;
border-radius: 8px;
font-size: 18px;
}
/* Landscape: CSS rotation compensates physical rotation.
User sees element coords directly — so flex-direction:row = panel on the RIGHT. */
.rec-screen.landscape-right,
.rec-screen.landscape-left {
position: fixed;
width: 100vh;
height: 100vw;
top: calc((100vh - 100vw) / 2);
left: calc((100vw - 100vh) / 2);
z-index: 9999;
flex-direction: row;
}
.rec-screen.landscape-right {
transform: rotate(-90deg);
}
.rec-screen.landscape-left {
transform: rotate(90deg);
}
.rec-screen.landscape-right .rec-map-wrap,
.rec-screen.landscape-left .rec-map-wrap {
min-height: 0;
}
.rec-screen.landscape-right .rec-panel,
.rec-screen.landscape-left .rec-panel {
width: 200px;
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: space-evenly;
border-left: 1px solid var(--border);
}
.rec-screen.landscape-right .rec-stats,
.rec-screen.landscape-left .rec-stats {
flex-direction: column;
border-top: none;
padding: 8px;
gap: 2px;
}
.rec-screen.landscape-right .rec-bottom .rec-status,
.rec-screen.landscape-left .rec-bottom .rec-status {
display: none;
}
.rec-screen.landscape-right .rec-hr-row,
.rec-screen.landscape-left .rec-hr-row {
display: none;
}
.rec-stats {
display: flex;
justify-content: space-around;
padding: 12px 8px;
background: var(--surface);
border-top: 1px solid var(--border);
}
.rec-stat {
display: flex;
flex-direction: column;
align-items: center;
}
.rec-val {
font-size: 22px;
font-weight: 700;
color: var(--on-surface);
}
.rec-lbl {
font-size: 10px;
color: var(--muted);
text-transform: uppercase;
}
.rec-hr-row { display:flex; align-items:baseline; justify-content:center; gap:8px; padding:6px 8px; background:var(--surface); border-top:1px solid var(--border); }
.rec-hr-val { font-size:42px; font-weight:700; }
.rec-hr-unit { font-size:14px; color:var(--muted); }
.rec-hr-extra { font-size:14px; color:var(--muted); margin-left:12px; }
.hr-zone-green { color:#81c784; }
.hr-zone-yellow { color:#ffb74d; }
.hr-zone-orange { color:#ff8a65; }
.hr-zone-red { color:#ef5350; }
.rec-bottom {
display: flex;
align-items: center;
gap: 12px;
padding: 8px 12px;
padding-bottom: calc(8px + var(--safe-area-bottom));
background: var(--surface);
}
.rec-bottom .rec-status {
font-size: 12px;
color: var(--muted);
flex: 1;
}
.rec-bottom .gps-rec {
padding: 12px 32px;
flex-shrink: 0;
}
.gps-header {
padding: 10px 12px;
padding-top: calc(10px + var(--safe-area-top));
background: var(--surface);
border-bottom: 1px solid var(--border);
}
.gps-btns {
display: flex;
gap: 8px;
align-items: center;
}
.gps-type-sel {
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--on-bg);
padding: 8px 10px;
font-size: 14px;
font-family: inherit;
color-scheme: dark;
}
.gps-type-sel option {
background: var(--surface);
color: var(--on-bg);
}
.gps-rec {
flex: 1;
padding: 10px;
font-size: 16px;
}
.gps-rec.recording {
background: #cf6679;
}
.gps-menu-wrap {
position: relative;
}
.gps-menu-btn {
background: var(--border);
border: none;
color: var(--on-bg);
font-size: 20px;
padding: 8px 12px;
border-radius: var(--radius);
cursor: pointer;
line-height: 1;
}
.gps-menu {
position: absolute;
right: 0;
top: 100%;
margin-top: 4px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
z-index: 50;
min-width: 200px;
box-shadow: 0 4px 12px rgba(0,0,0,0.4);
}
.gps-menu.hidden { display: none; }
.gps-menu-item {
padding: 10px 14px;
font-size: 14px;
cursor: pointer;
white-space: nowrap;
}
.gps-menu-item:active {
background: rgba(187, 134, 252, 0.1);
}
/* Session detail */
.sd-section {
margin-bottom: 16px;
}
.sd-title {
font-size: 13px;
color: var(--primary);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
font-weight: 600;
}
.sd-row {
display: flex;
gap: 8px;
margin-bottom: 6px;
}
.sd-stat {
flex: 1;
display: flex;
flex-direction: column;
}
.sd-val {
font-size: 18px;
font-weight: 700;
color: var(--on-surface);
}
.sd-lbl {
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
}
.sd-map {
height: 200px;
border-radius: var(--radius);
overflow: hidden;
cursor: pointer;
touch-action: pan-y pinch-zoom;
}
.sd-map-full {
position: fixed !important;
inset: 0 !important;
height: 100vh !important;
width: 100vw !important;
max-width: none !important;
z-index: 9999;
border-radius: 0;
}
.sd-map-full .leaflet-top {
top: var(--safe-area-top) !important;
}
.sd-map-full .leaflet-bottom {
bottom: var(--safe-area-bottom) !important;
}
.sd-map-fs-btn {
position: relative;
z-index: 10000;
}
.sd-map-fs-btn {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--on-bg);
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
font-size: 18px;
box-shadow: 0 2px 6px rgba(0,0,0,0.3);
}
.zoom-display {
background: rgba(0,0,0,0.6);
color: #fff;
font-size: 13px;
font-weight: 700;
padding: 2px 6px;
border-radius: 4px;
}
.chart-wrap {
position: relative;
cursor: pointer;
height: 200px;
width: 100%;
}
/* Block touch on canvas when chart is minimized — lets page scroll normally.
Clicks on the .chart-wrap div still work (pointer-events: auto on parent). */
.chart-wrap:not(.chart-full) canvas {
pointer-events: none;
}
.chart-full {
position: fixed;
left: 0;
top: var(--safe-area-top);
z-index: 9999;
background: var(--bg);
width: 100vw;
height: calc(100vh - var(--safe-area-top) - var(--safe-area-bottom));
}
.chart-close-btn {
position: absolute;
top: calc(12px + var(--safe-area-top));
right: calc(12px + var(--safe-area-right, 0px));
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
color: var(--on-bg);
font-size: 18px;
cursor: pointer;
z-index: 10000;
}
.point-popup .leaflet-popup-content-wrapper {
background: var(--surface);
color: var(--on-bg);
font-size: 12px;
border-radius: 6px;
padding: 4px 8px;
}
.point-popup .leaflet-popup-tip {
background: var(--surface);
}
.gps-download-status {
text-align: center;
font-size: 12px;
color: var(--primary);
padding: 6px 0;
}
.gps-sessions {
flex: 1;
overflow-y: auto;
padding: 8px 12px;
}
.gps-sessions h3 {
font-size: 13px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.5px;
margin-bottom: 8px;
}
.gps-session-item {
padding: 10px;
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 6px;
cursor: pointer;
}
.gps-session-item:active {
border-color: var(--primary);
}
.gps-session-head {
display: flex;
justify-content: space-between;
font-size: 13px;
margin-bottom: 4px;
}
.gps-session-type {
color: var(--primary);
font-size: 12px;
}
.gps-session-stats {
font-size: 12px;
color: var(--muted);
}
.supp-summary {
padding: 0 12px 8px;
font-size: 11px;
color: var(--muted);
line-height: 1.3;
}
/* Supplement pick list (in modal) */
.supp-pick-list {
display: flex;
flex-direction: column;
gap: 4px;
max-height: 200px;
overflow-y: auto;
}
.supp-pick-item {
padding: 8px 10px;
border: 1px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
font-size: 14px;
}
.supp-pick-item.selected {
border-color: var(--primary);
background: rgba(187, 134, 252, 0.12);
color: var(--primary);
}
.supp-pick-item:active {
background: rgba(187, 134, 252, 0.08);
}
.supp-pick-divider {
padding: 6px 10px 2px;
font-size: 11px;
color: var(--muted);
text-transform: uppercase;
letter-spacing: 0.5px;
}
/* Micronutrient grid in supplement edit modal */
.micro-grid {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px 12px;
}
.micro-field label {
font-size: 11px;
}
.micro-field input {
padding: 6px 8px;
font-size: 14px;
}
/* Per-food-item nutrient line */
.src-badge {
display: inline-block;
font-size: 10px;
padding: 1px 5px;
border-radius: 3px;
vertical-align: middle;
margin-left: 4px;
font-weight: 600;
}
.src-magnit { background: rgba(229, 57, 53, 0.25); color: #ef5350; }
.src-5ka { background: rgba(129, 199, 132, 0.2); color: #a5d6a7; }
.src-kdv { background: rgba(255, 167, 38, 0.25); color: #ffb74d; }
.src-ion { background: rgba(158, 158, 158, 0.2); color: #9e9e9e; }
.src-manual { background: rgba(255, 235, 59, 0.2); color: #fff176; }
.food-nutrients {
font-size: 11px;
color: var(--muted);
margin-top: 2px;
white-space: normal;
}
/* Daily nutrient totals strip — chips sorted by deficiency */
.nutrient-strip {
display: flex;
flex-wrap: wrap;
gap: 6px;
padding: 8px 14px;
cursor: pointer;
}
/* Collapsed: single scrollable row */
.nutrient-strip.nutrient-collapsed {
flex-wrap: nowrap;
overflow-x: auto;
-webkit-overflow-scrolling: touch;
scrollbar-width: none;
}
.nutrient-strip.nutrient-collapsed::-webkit-scrollbar { display: none; }
.nut-item {
font-size: 12px;
padding: 3px 8px;
border-radius: 12px;
background: var(--surface);
border: 1px solid var(--border);
}
.nut-low { color: #f44336; border-color: #f44336; }
.nut-mid { color: #ffb74d; border-color: #ffb74d; }
.nut-ok { color: #81c784; border-color: #81c784; }
Recipes, nutrients_guessed, seed import fixes, search improvements Recipes: - Cooking methods table + 6 presets (boiling, frying, steaming etc.) - Recipe editor with live KBJU calculation including cooking loss - Recipe detail view, log-to-diary, ingredient search - Split food log into "Products" and "Dishes" sections - Products screen sub-tabs: Продукты / Рецепты Nutrient enrichment: - 1620 store products enriched with ION micronutrients (guessed flag) - Purple tint on guessed food entries, * on daily kcal total - Seed DB v4 with nutrients_guessed column Seed import fixes: - Rust: always refresh seed DB from bundled assets on launch - Rust: check app_config_dir for main DB (plugin-sql stores there on desktop) - Rust: ATTACH-based UPDATE to sync nutrients_guessed for existing products - Rust: read all 27 columns including micronutrients - Rust: emit seed_progress events + indeterminate progress bar animation - JS: boot screen with status labels during migrations/import - JS: nutrients_guessed column created in JS migration as safety net Search: - Results sorted by starts-with-query first, then by name length - Default limit raised to 200 - Modal search results scroll without max-height cap Bug fixes: - product_id=0 for custom recipe ingredients (NOT NULL constraint) - getRecipe JOIN for cooking_method_name - calcTotals applies cooking loss from selected method - Ingredient modal targets last overlay (not parent) - select elements dark theme (color-scheme: dark) - renderToday wrapped in try/catch
2026-05-21 00:06:50 +03:00
/* Recipe components */
/* Guessed nutrients indicator — slight tint on food items with estimated micronutrients */
.log-item.guessed {
background: rgba(187, 134, 252, 0.2);
}
.seed-progress {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 60vh;
gap: 12px;
padding: 0 32px;
}
.seed-label {
color: var(--on-bg);
font-size: 16px;
}
.seed-bar {
width: 100%;
max-width: 300px;
height: 8px;
background: var(--surface);
border-radius: 4px;
overflow: hidden;
}
.seed-bar-fill {
height: 100%;
width: 30%;
background: var(--primary);
border-radius: 4px;
animation: seed-slide 1.2s ease-in-out infinite;
}
@keyframes seed-slide {
0% { margin-left: 0%; }
50% { margin-left: 70%; }
100% { margin-left: 0%; }
}
/* When progress is known, switch to determinate mode */
.seed-bar-fill.determinate {
animation: none;
margin-left: 0;
transition: width 0.3s;
}
.drink-bar-wrap { padding: 6px 14px; display: flex; align-items: center; gap: 8px; }
.drink-label { font-size: 12px; color: var(--muted); white-space: nowrap; }
.drink-bar { flex: 1; height: 6px; background: var(--surface); border-radius: 3px; overflow: hidden; }
.drink-bar-fill { height: 100%; border-radius: 3px; transition: width 0.3s; }
.drink-bar-fill.low { background: #cf6679; }
.drink-bar-fill.mid { background: #ffb74d; }
.drink-bar-fill.ok { background: #81c784; }
Recipes, nutrients_guessed, seed import fixes, search improvements Recipes: - Cooking methods table + 6 presets (boiling, frying, steaming etc.) - Recipe editor with live KBJU calculation including cooking loss - Recipe detail view, log-to-diary, ingredient search - Split food log into "Products" and "Dishes" sections - Products screen sub-tabs: Продукты / Рецепты Nutrient enrichment: - 1620 store products enriched with ION micronutrients (guessed flag) - Purple tint on guessed food entries, * on daily kcal total - Seed DB v4 with nutrients_guessed column Seed import fixes: - Rust: always refresh seed DB from bundled assets on launch - Rust: check app_config_dir for main DB (plugin-sql stores there on desktop) - Rust: ATTACH-based UPDATE to sync nutrients_guessed for existing products - Rust: read all 27 columns including micronutrients - Rust: emit seed_progress events + indeterminate progress bar animation - JS: boot screen with status labels during migrations/import - JS: nutrients_guessed column created in JS migration as safety net Search: - Results sorted by starts-with-query first, then by name length - Default limit raised to 200 - Modal search results scroll without max-height cap Bug fixes: - product_id=0 for custom recipe ingredients (NOT NULL constraint) - getRecipe JOIN for cooking_method_name - calcTotals applies cooking loss from selected method - Ingredient modal targets last overlay (not parent) - select elements dark theme (color-scheme: dark) - renderToday wrapped in try/catch
2026-05-21 00:06:50 +03:00
.seed-count {
color: var(--muted);
font-size: 13px;
}
.sub-tabs {
display: flex;
gap: 0;
margin-bottom: 12px;
border-bottom: 1px solid var(--border);
}
.sub-tab {
flex: 1;
background: none;
border: none;
color: var(--muted);
font-size: 14px;
padding: 8px 0;
cursor: pointer;
border-bottom: 2px solid transparent;
}
.sub-tab.active {
color: var(--primary);
border-bottom-color: var(--primary);
}
.recipe-macros-hint {
padding: 2px 12px 8px;
font-size: 12px;
color: var(--muted);
}
.recipe-ing-row {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px 0;
border-bottom: 1px solid rgba(255,255,255,0.04);
}
.recipe-ing-info {
flex: 1;
display: flex;
align-items: baseline;
gap: 8px;
overflow: hidden;
}
.recipe-ing-name {
font-size: 14px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.recipe-ing-weight {
font-size: 12px;
color: var(--muted);
flex-shrink: 0;
}
.recipe-ing-cm {
font-size: 11px;
color: var(--primary);
flex-shrink: 0;
}
.recipe-totals {
padding: 10px 0;
font-size: 15px;
font-weight: 600;
color: var(--primary);
text-align: center;
}
/* Unit chips (alternative food units) */
.unit-chips {
display: flex;
gap: 6px;
overflow-x: auto;
padding: 4px 0;
margin-bottom: 8px;
}
.unit-chip {
padding: 4px 12px;
border-radius: 16px;
font-size: 13px;
border: 1px solid var(--border);
background: transparent;
color: var(--on-bg);
cursor: pointer;
white-space: nowrap;
font-family: inherit;
}
.unit-chip.selected {
background: var(--primary);
color: #000;
border-color: var(--primary);
}
/* Stats charts */
.stats-chart-block {
margin-bottom: 16px;
padding: 0 14px;
}
.stats-period {
display: flex;
gap: 4px;
}
/* Loading */
.loading {
text-align: center;
padding: 40px;
color: var(--muted);
}
/* Climb segments (session detail) */
.climb-segments {
border: 1px solid var(--border);
border-radius: var(--radius);
overflow: hidden;
}
.segment-item {
display: flex;
gap: 8px;
padding: 8px;
border-bottom: 1px solid var(--border);
align-items: center;
}
.segment-item:last-child {
border-bottom: none;
}
.segment-item.climb .seg-icon {
color: #f44336;
font-weight: 700;
}
.segment-item.descent .seg-icon {
color: #4caf50;
font-weight: 700;
}
.segment-item.flat .seg-icon {
color: var(--muted);
}
.seg-icon {
width: 20px;
text-align: center;
font-size: 16px;
}
.seg-info {
flex: 1;
font-size: 13px;
}
.seg-stats {
font-size: 12px;
color: var(--muted);
white-space: nowrap;
}
/* Bike maintenance list */
.maint-item {
display: flex;
align-items: center;
padding: 8px;
border-bottom: 1px solid var(--border);
gap: 8px;
}
.maint-item:last-child {
border-bottom: none;
}
.maint-overdue {
background: rgba(244, 67, 54, 0.08);
}
.maint-info {
flex: 1;
font-size: 13px;
display: flex;
gap: 6px;
flex-wrap: wrap;
align-items: center;
}
.maint-date {
color: var(--muted);
font-size: 12px;
min-width: 40px;
}
.maint-desc {
font-weight: 500;
}
.maint-km {
font-size: 12px;
color: var(--muted);
}
.maint-warn {
color: #f44336;
font-weight: 700;
}
.maint-del {
background: none;
border: none;
color: var(--muted);
font-size: 18px;
cursor: pointer;
padding: 4px 8px;
font-family: inherit;
}
.maint-del:active {
color: #f44336;
}
/* Compare session list */
.compare-list {
max-height: 60vh;
overflow-y: auto;
}
.compare-item {
padding: 12px;
border: 1px solid var(--border);
border-radius: var(--radius);
margin-bottom: 8px;
cursor: pointer;
}
.compare-item:active {
border-color: var(--primary);
background: rgba(187, 134, 252, 0.05);
}
/* 7-day balance trend (IDEA 6) */
.balance-trend {
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
padding: 6px 12px;
background: var(--surface);
border-bottom: 1px solid var(--border);
min-height: 0;
}
.balance-trend:empty {
display: none;
}
.balance-trend-label {
font-size: 12px;
color: var(--muted);
}
.balance-trend-val {
font-size: 14px;
font-weight: 700;
}
.trend-deficit {
color: #03dac6;
}
.trend-surplus {
color: #cf6679;
}
/* Goal markers (IDEA 2) */
.goal-markers-list {
display: flex;
flex-direction: column;
gap: 6px;
max-height: 50vh;
overflow-y: auto;
}
.goal-marker-card {
display: flex;
align-items: center;
justify-content: space-between;
padding: 10px 12px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
}
.goal-marker-info {
flex: 1;
}
.goal-marker-name {
font-size: 14px;
font-weight: 600;
color: var(--on-surface);
}
.goal-marker-coords {
font-size: 11px;
color: var(--muted);
margin-top: 2px;
}
.goal-marker-notes {
font-size: 12px;
color: var(--muted);
margin-top: 2px;
font-style: italic;
}
/* Goal marker map (add marker modal) */
.gm-map-wrap {
margin-top: 8px;
}
.gm-map {
height: 250px;
border-radius: var(--radius);
overflow: hidden;
}
.gm-coords {
font-size: 12px;
color: var(--muted);
margin-top: 4px;
text-align: center;
}
/* Goal markers in session detail */
.goal-marker-item {
padding: 8px 0;
border-bottom: 1px solid rgba(255,255,255,0.04);
}
.goal-marker-item:last-child {
border-bottom: none;
}
.goal-marker-dist {
font-size: 12px;
color: var(--muted);
margin-top: 2px;
}
.goal-bar-wrap {
height: 6px;
background: var(--border);
border-radius: 3px;
margin-top: 6px;
overflow: hidden;
}
.goal-bar-fill {
height: 100%;
background: var(--primary);
border-radius: 3px;
transition: width 0.3s;
}
.goal-progress {
font-size: 11px;
color: var(--muted);
margin-top: 4px;
display: block;
}
.goal-better {
color: #03dac6;
}
.goal-worse {
color: #cf6679;
}
/* Food cost of workout (IDEA 8) */
.food-cost {
padding: 4px 0;
}
.food-cost-total {
font-size: 16px;
font-weight: 700;
color: var(--on-surface);
margin-bottom: 6px;
}
.food-cost-items {
display: flex;
flex-direction: column;
gap: 2px;
}
.food-cost-line {
font-size: 14px;
color: var(--on-bg);
padding: 2px 0;
}
.food-cost-or {
font-size: 12px;
color: var(--muted);
font-style: italic;
padding: 0 0 0 8px;
}