baldav/frontend/css/styles.css

2319 lines
46 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;
}
/* Gap-fill preview panel (track detail): floats above the bottom, dismissible. */
.gap-panel {
position: fixed;
left: 8px;
right: 8px;
bottom: calc(16px + var(--safe-area-bottom));
background: var(--surface);
color: var(--on-bg);
border: 1px solid var(--border);
border-radius: 10px;
padding: 10px 12px;
z-index: 100000;
box-shadow: 0 2px 14px rgba(0,0,0,0.55);
display: flex;
flex-direction: column;
gap: 8px;
font-size: 14px;
}
.gap-panel-hint { color: var(--muted); font-size: 12px; }
.gap-panel-btns { display: flex; gap: 8px; }
.gap-panel-btns button { flex: 1; padding: 8px 12px; }
.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;
}
/* In-app confirm / prompt dialog — centered card over a dimmed backdrop,
above full-screen modals (z-index 500) and fullscreen maps (9999). */
.confirm-overlay {
background: rgba(0, 0, 0, 0.6);
align-items: center;
padding: 0 24px;
z-index: 10000;
}
.confirm-box {
background: var(--surface);
border: 1px solid var(--border);
border-radius: var(--radius);
padding: 20px 18px 16px;
width: 100%;
max-width: 340px;
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5);
}
.confirm-msg {
font-size: 15px;
line-height: 1.4;
margin-bottom: 16px;
color: var(--on-bg);
}
.confirm-input {
width: 100%;
padding: 10px;
margin-bottom: 16px;
background: var(--bg);
border: 1px solid var(--border);
border-radius: 4px;
color: var(--on-bg);
font-size: 15px;
}
.confirm-btns {
display: flex;
gap: 10px;
}
.confirm-btns button {
flex: 1;
}
.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;
}
/* Route editor bottom bar: compact hint on top, buttons in a row, cancel below. */
.re-bottom {
display: flex;
flex-direction: column;
gap: 6px;
padding: 6px 12px;
padding-bottom: calc(6px + var(--safe-area-bottom));
background: var(--surface);
border-top: 1px solid var(--border);
}
.re-hint {
font-size: 11px;
line-height: 1.25;
color: var(--muted);
text-align: center;
}
.re-btns {
display: flex;
gap: 8px;
}
.re-btns button {
flex: 1;
padding: 9px 6px;
font-size: 13px;
}
.re-cancel {
color: #cf6679;
padding: 6px;
font-size: 13px;
}
/* Invisible fat hit target for route legs — clickable despite zero opacity. */
.re-hit { pointer-events: stroke; }
/* Share image ("Похвастаться") preview modal. */
.share-modal {
position: fixed;
inset: 0;
z-index: 100000;
background: rgba(0,0,0,0.88);
display: flex;
align-items: center;
justify-content: center;
padding: 16px;
padding-top: calc(16px + var(--safe-area-top));
padding-bottom: calc(16px + var(--safe-area-bottom));
}
.share-box {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
max-width: 100%;
max-height: 100%;
}
.share-preview {
max-width: 100%;
max-height: 54vh;
object-fit: contain;
border-radius: 10px;
/* checkerboard so a transparent PNG reads as transparent */
background-image:
linear-gradient(45deg, #3a3a3a 25%, transparent 25%, transparent 75%, #3a3a3a 75%),
linear-gradient(45deg, #3a3a3a 25%, #262626 25%, #262626 75%, #3a3a3a 75%);
background-size: 24px 24px;
background-position: 0 0, 12px 12px;
}
.share-toggle, .share-actions { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; }
.share-toggle button, .share-actions button { padding: 9px 14px; }
.share-fields {
display: flex; flex-wrap: wrap; gap: 6px 12px; justify-content: center;
max-width: 520px;
}
.share-field {
display: inline-flex; align-items: center; gap: 5px;
font-size: 13px; color: #ddd; white-space: nowrap; cursor: pointer;
}
.share-field input { width: 16px; height: 16px; flex: none; }
.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(255, 255, 255, 0.5);
color: #1a1a1a;
border-color: rgba(255, 255, 255, 0.6);
}
.rec-rotate-btn { right: 8px; bottom: 48px; font-size: 18px; transition: transform 0.15s; }
.rec-freeze-btn { right: 8px; bottom: 86px; }
.rec-roads-toggle { right: 8px; bottom: 124px; }
.rec-mode-btn { right: 8px; bottom: 162px; font-size: 11px; }
.rec-route-toggle { right: 8px; bottom: 200px; }
.rec-dark-toggle { right: 8px; bottom: 238px; }
/* Dark map: invert ONLY the tile layer (overlays — track, route, roads, puck — keep
their colours). User-tuned filter on OSM standard tiles. Applies to every Leaflet map. */
body.dark-map .leaflet-tile-pane { filter: invert(1) hue-rotate(180deg) brightness(1.5); }
/* Darken the map backdrop too, else the light Leaflet container (#ddd) flashes bright
between/behind tiles while zooming or where a tile is missing. */
body.dark-map .leaflet-container { background: #1b1f24; }
/* POI overlay: circular contrasting icon badges (readable on light & dark tiles). */
.poi-badge-wrap { background: none; border: none; }
.poi-badge {
width: 26px; height: 26px; line-height: 22px; text-align: center; font-size: 15px;
background: #fff; border: 2px solid #222; border-radius: 50%;
box-shadow: 0 1px 3px rgba(0,0,0,.5);
}
body.dark-map .poi-badge { background: #111; border-color: #eee; }
.poi-panel {
margin-top: 4px; max-height: 55vh; overflow: auto; min-width: 150px;
background: var(--surface); border: 1px solid var(--border); border-radius: 6px;
padding: 6px 8px; box-shadow: 0 2px 8px rgba(0,0,0,.45);
}
.poi-row { display: block; font-size: 13px; color: var(--on-bg); padding: 2px 0; cursor: pointer; white-space: nowrap; }
.poi-head { font-weight: 600; border-bottom: 1px solid var(--border); margin-bottom: 4px; padding-bottom: 4px; }
.poi-btn.active { background: rgba(255,255,255,0.5); color: #1a1a1a; }
#re-brouter.active { background: #4caf50; color: #fff; }
/* Always-on compass needle: red half = North, grey = South. Rotates to point to real north.
Top-RIGHT (zoom controls are top-left), below the status-bar safe area. */
/* Position/direction puck (SVG divIcon) — no default leaflet-div-icon box. */
.rec-puck { background: none; border: none; pointer-events: none; }
.rec-puck svg { display: block; overflow: visible; }
.rec-compass {
position: absolute; top: calc(8px + var(--safe-area-top, 0px)); right: 8px; z-index: 1001;
width: 40px; height: 40px; border-radius: 50%;
background: rgba(20,20,20,0.8); border: 1px solid var(--border);
cursor: pointer;
}
/* Keep Leaflet zoom controls below the status bar on the recording map. */
.rec-map-wrap .leaflet-top { margin-top: var(--safe-area-top, 0px); }
/* Status-bar backdrop. The recording map is edge-to-edge (draws behind the system
status bar), so the bar had no backing and its icons were unreadable over the map
tiles. Paint a solid strip the height of the top inset — like the header on every
other screen — so the status bar stays visible. Controls already start below it. */
.rec-map-wrap::before {
content: "";
position: absolute;
top: 0; left: 0; right: 0;
height: var(--safe-area-top, 0px);
background: var(--surface);
z-index: 1100;
pointer-events: none;
}
.rec-screen.rot-90 .rec-map-wrap::before,
.rec-screen.rot-270 .rec-map-wrap::before { display: none; }
.rec-compass-arrow { position: absolute; inset: 0; transition: transform 0.12s linear; }
.rec-compass-arrow::before {
content: "N"; position: absolute; top: 1px; left: 50%; transform: translateX(-50%);
color: #f44336; font: 700 11px sans-serif;
}
.rec-compass-arrow::after {
content: ""; position: absolute; left: 50%; top: 14px; bottom: 6px; width: 3px; margin-left: -1.5px;
background: linear-gradient(to bottom, #f44336 0 55%, #9e9e9e 55% 100%);
border-radius: 2px;
}
.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;
}
/* Manual screen rotation (rotate button). 90/270 = landscape (swap dims, panel to side).
180 is intentionally not used — it would flip the panel text upside down.
Map compensates via setBearing(heading - rotOffset). */
.rec-screen.rot-90,
.rec-screen.rot-270 {
position: fixed;
width: 100vh;
height: 100vw;
top: calc((100vh - 100vw) / 2);
left: calc((100vw - 100vh) / 2);
z-index: 9999;
flex-direction: row;
}
.rec-screen.rot-90 { transform: rotate(90deg); }
.rec-screen.rot-270 { transform: rotate(270deg); }
.rec-screen.rot-90 .rec-map-wrap,
.rec-screen.rot-270 .rec-map-wrap { min-height: 0; }
.rec-screen.rot-90 .rec-panel,
.rec-screen.rot-270 .rec-panel {
width: 200px;
flex-shrink: 0;
display: flex;
flex-direction: column;
justify-content: space-evenly;
border-left: 1px solid var(--border);
}
.rec-screen.rot-90 .rec-stats,
.rec-screen.rot-270 .rec-stats {
flex-direction: column;
border-top: none;
padding: 8px;
gap: 2px;
}
.rec-screen.rot-90 .rec-bottom .rec-status,
.rec-screen.rot-270 .rec-bottom .rec-status { display: none; }
.rec-screen.rot-90 .rec-hr-row,
.rec-screen.rot-270 .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;
flex-wrap: wrap;
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;
}
/* Collapsible section (native <details>): header stays visible, body folds away
so the action buttons below aren't buried under a long segment list. */
.sd-acc-title {
cursor: pointer;
list-style: none;
display: flex;
align-items: center;
gap: 8px;
user-select: none;
padding: 6px 0;
}
.sd-acc-title::-webkit-details-marker { display: none; }
.sd-acc-title::before {
content: "\25B8"; /* ▸ */
display: inline-block;
transition: transform 0.15s;
font-size: 11px;
}
.sd-accordion[open] .sd-acc-title::before { transform: rotate(90deg); }
.sd-acc-count {
background: var(--border);
color: var(--on-bg);
border-radius: 10px;
padding: 1px 8px;
font-size: 11px;
font-weight: 600;
}
/* Repeat / interval analysis table + verdict */
.repeats-head { font-size: 12px; color: var(--on-bg); opacity: 0.8; margin-bottom: 8px; }
.repeats-tbl { width: 100%; border-collapse: collapse; font-size: 13px; }
.repeats-tbl th, .repeats-tbl td { padding: 5px 6px; text-align: right; border-bottom: 1px solid var(--border); }
.repeats-tbl th:first-child, .repeats-tbl td:first-child { text-align: left; }
.repeats-tbl th { color: var(--primary); font-weight: 600; }
.repeats-verdict { margin-top: 10px; padding: 10px; border-radius: var(--radius); text-align: center; line-height: 1.4; }
.repeats-verdict .rv-num { font-size: 22px; font-weight: 700; }
.repeats-verdict.ok { background: rgba(46, 160, 67, 0.18); color: #4caf50; }
.repeats-verdict.warn { background: rgba(255, 179, 0, 0.16); color: #ffb300; }
.repeats-verdict.bad { background: rgba(207, 102, 121, 0.18); color: #cf6679; }
.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);
}
.sd-map-fs-btn.active {
background: rgba(255, 255, 255, 0.5);
color: #1a1a1a;
border-color: rgba(255, 255, 255, 0.6);
}
.sd-smooth-panel {
position: fixed;
z-index: 10001;
left: 8px;
bottom: 8px;
width: 210px;
background: rgba(20, 20, 20, 0.93);
color: #eee;
font-size: 12px;
padding: 8px 10px;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
}
.sd-smooth-panel label { display: block; margin: 6px 0 2px; }
.sd-smooth-panel input[type=range] { width: 100%; }
.sd-smooth-panel .sm-stat { margin: 4px 0; color: #9be89b; }
.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-item.selected {
border-color: var(--primary);
background: color-mix(in srgb, var(--primary) 20%, var(--surface));
box-shadow: inset 0 0 0 1px var(--primary);
}
/* Floating action bar for multi-select merge (sits above the bottom tab bar) */
.gps-sel-bar {
position: fixed;
left: 0; right: 0;
bottom: calc(56px + var(--safe-area-bottom));
z-index: 90;
display: flex;
align-items: center;
gap: 10px;
padding: 10px 16px;
background: var(--surface);
border-top: 1px solid var(--border);
box-shadow: 0 -2px 8px rgba(0,0,0,0.3);
}
.gps-sel-bar[hidden] { display: none; }
.gps-sel-bar #gps-sel-count { flex: 1; font-size: 14px; }
.gps-sel-bar button { padding: 8px 14px; }
.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; }
.src-chzn { background: rgba(66, 165, 245, 0.22); color: #64b5f6; }
.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;
}