diff --git a/custom_components/plantrun/www/plantrun-panel-styles.js b/custom_components/plantrun/www/plantrun-panel-styles.js
index f618e43..7a534cb 100644
--- a/custom_components/plantrun/www/plantrun-panel-styles.js
+++ b/custom_components/plantrun/www/plantrun-panel-styles.js
@@ -111,28 +111,78 @@ export const panelStyles = `
.page-heading { display:flex; justify-content:space-between; gap:30px; align-items:end; margin-bottom:28px; }
.page-heading>div { display:grid; gap:9px; max-width:730px; }
.page-heading p { max-width:660px; }
- .tent-strip { border-top:1px solid var(--line); border-bottom:1px solid var(--line); display:grid; grid-template-columns:repeat(4,1fr); margin:0 0 34px; }
- .tent-reading { min-width:0; padding:18px 20px; display:grid; gap:5px; border-right:1px solid var(--line); }
- .tent-reading:last-child { border:0; }
- .tent-reading span,.tent-reading small { color:var(--muted); }
- .tent-reading span { font-size:12px; }
- .tent-reading strong { font-size:22px; overflow:hidden; text-overflow:ellipsis; }
- .tent-reading small { font-size:10px; }
- .plant-gallery { display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); gap:clamp(20px,3vw,36px); }
- .plant-card { min-width:0; background:var(--surface); border:1px solid var(--line-soft); border-radius:var(--radius-work); overflow:hidden; position:relative; }
- .plant-card-main { width:100%; border:0; padding:0; background:transparent; color:inherit; cursor:pointer; text-align:left; display:grid; grid-template-columns:minmax(180px,42%) 1fr; }
- .plant-card-photo { width:100%; height:330px; object-fit:cover; background:var(--surface-2); }
- .photo-empty { display:grid; place-items:center; align-content:center; gap:10px; color:var(--quiet); }
- .photo-empty ha-icon { --mdc-icon-size:54px; }
- .photo-empty span { font-size:12px; }
- .plant-card-body { padding:30px; display:flex; flex-direction:column; align-items:flex-start; min-width:0; }
- .plant-card-body>strong { font-size:clamp(24px,3vw,38px); line-height:1.05; letter-spacing:-.035em; margin:12px 0 6px; }
- .plant-card-body>small { color:var(--muted); }
- .stage-label { display:inline-flex; align-items:center; min-height:29px; padding:0 10px; border:1px solid color-mix(in srgb,var(--accent) 55%,var(--line)); border-radius:999px; color:var(--accent-strong); font-size:11px; font-weight:800; }
- .plant-card-meta { margin-top:auto; padding-top:25px; border-top:1px solid var(--line); width:100%; display:grid; grid-template-columns:auto 1fr; gap:22px; color:var(--muted); font-size:12px; }
- .plant-card-meta>span:last-child { display:grid; gap:3px; }
- .plant-card-meta b { color:var(--text); font-size:10px; text-transform:uppercase; letter-spacing:.08em; }
- .journal-direct { position:absolute; right:18px; bottom:18px; min-height:39px; padding:0 13px; display:flex; align-items:center; gap:6px; border-radius:var(--radius-control); background:var(--accent-strong); color:var(--accent-ink); font-weight:760; }
+ .modern-sensor-grid { display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:12px; margin:0 0 28px; }
+ .sensor-card { background:var(--surface); border:1px solid var(--line-soft); border-radius:var(--radius-group); padding:14px 16px; display:flex; flex-direction:column; gap:8px; transition:border-color .2s, transform .2s; }
+ .sensor-card:hover { border-color:var(--line); transform:translateY(-1px); }
+ .sensor-card-head { display:flex; align-items:center; justify-content:space-between; gap:6px; }
+ .sensor-card-title { display:flex; align-items:center; gap:7px; color:var(--muted); font-size:11.5px; font-weight:600; }
+ .sensor-card-icon { width:26px; height:26px; border-radius:7px; background:var(--surface-2); color:var(--accent-strong); display:grid; place-items:center; }
+ .sensor-card-icon ha-icon { --mdc-icon-size:16px; }
+ .sensor-card-val { font-size:21px; font-weight:750; letter-spacing:-.02em; color:var(--text); line-height:1.1; margin:2px 0 0; }
+ .sensor-card-sub { font-size:10px; color:var(--muted); display:flex; justify-content:space-between; align-items:center; margin-top:2px; }
+
+ /* Zone badge styling */
+ .zone-badge { display:inline-flex; align-items:center; gap:5px; font-size:10.5px; font-weight:700; padding:2.5px 8px; border-radius:6px; line-height:1.2; }
+ .zone-badge.good { background:rgba(52, 211, 153, 0.12); color:#34d399; border:1px solid rgba(52, 211, 153, 0.28); }
+ .zone-badge.warn { background:rgba(251, 191, 36, 0.12); color:#fbbf24; border:1px solid rgba(251, 191, 36, 0.28); }
+ .zone-badge.bad { background:rgba(248, 113, 113, 0.12); color:#f87171; border:1px solid rgba(248, 113, 113, 0.28); }
+ .zone-badge.neutral { background:rgba(255, 255, 255, 0.06); color:var(--muted); border:1px solid var(--line-soft); }
+ .zone-dot { width:5px; height:5px; border-radius:50%; background:currentColor; }
+
+ /* Range Gauge Bar */
+ .range-gauge-wrap { display:flex; flex-direction:column; gap:4px; width:100%; margin-top:5px; }
+ .range-gauge-track { position:relative; width:100%; height:5px; border-radius:5px; background:rgba(255, 255, 255, 0.08); overflow:visible; }
+ .theme-light .range-gauge-track { background:rgba(0, 0, 0, 0.08); }
+ .range-gauge-zone { position:absolute; top:0; bottom:0; background:linear-gradient(90deg, rgba(52, 211, 153, 0.35), rgba(74, 222, 128, 0.65)); border-radius:3px; }
+ .range-gauge-needle { position:absolute; top:50%; transform:translate(-50%, -50%); width:10px; height:10px; display:grid; place-items:center; z-index:2; }
+ .range-needle-dot { width:7px; height:7px; border-radius:50%; background:#ffffff; border:1.5px solid #10b981; box-shadow:0 0 5px rgba(255, 255, 255, 0.85); }
+ .range-gauge-labels { display:flex; justify-content:space-between; align-items:center; font-size:9px; color:var(--muted); }
+ .range-target-label { color:#34d399; font-weight:600; }
+ .theme-light .range-target-label { color:#15803d; }
+
+ /* Modern Plant Cards */
+ .plant-gallery { display:grid; grid-template-columns:repeat(auto-fit, minmax(380px, 1fr)); gap:clamp(16px, 2.5vw, 24px); }
+ .modern-plant-card { background:var(--surface); border:1px solid var(--line-soft); border-radius:var(--radius-work); padding:20px; display:flex; flex-direction:column; gap:14px; position:relative; transition:border-color .2s; }
+ .modern-plant-card:hover { border-color:var(--line); }
+ .plant-card-header { display:grid; grid-template-columns:116px 1fr; gap:16px; align-items:start; }
+ .plant-thumb-btn { position:relative; width:116px; height:116px; border-radius:var(--radius-control); overflow:hidden; background:var(--surface-2); border:0; padding:0; cursor:pointer; }
+ .plant-thumb-img { width:100%; height:100%; object-fit:cover; display:block; }
+ .plant-breeder-tag { position:absolute; bottom:5px; left:5px; right:5px; padding:2px 5px; border-radius:5px; background:rgba(5, 12, 7, 0.85); backdrop-filter:blur(4px); color:#d1fae5; font-size:9px; font-weight:700; text-align:center; border:1px solid rgba(255, 255, 255, 0.15); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
+ .plant-info-block { display:flex; flex-direction:column; gap:5px; min-width:0; }
+ .plant-title-row { display:flex; align-items:center; gap:8px; flex-wrap:wrap; }
+ .plant-stage-badge { display:inline-flex; align-items:center; gap:5px; padding:3px 8px; border-radius:6px; background:rgba(52, 211, 153, 0.12); color:#34d399; border:1px solid rgba(52, 211, 153, 0.28); font-size:10.5px; font-weight:750; width:fit-content; }
+ .pulse-dot { width:6px; height:6px; border-radius:50%; background:#34d399; box-shadow:0 0 6px #34d399; }
+ .plant-title-btn { border:0; background:transparent; padding:0; cursor:pointer; color:var(--text); font-size:21px; font-weight:750; letter-spacing:-.025em; text-align:left; line-height:1.2; }
+ .plant-title-btn:hover { color:var(--accent-strong); }
+ .plant-genetics-sub { color:var(--muted); font-size:11.5px; margin:0; line-height:1.35; }
+ .plant-harvest-pill { display:inline-flex; align-items:center; gap:5px; padding:3px 8px; border-radius:6px; background:var(--surface-2); color:var(--muted); font-size:10.5px; width:fit-content; margin-top:2px; }
+ .plant-harvest-pill ha-icon { --mdc-icon-size:13px; color:var(--muted); }
+ .plant-harvest-pill strong { color:var(--text); }
+
+ /* Stage timeline */
+ .stage-timeline { display:grid; grid-template-columns:repeat(5, 1fr); gap:6px; margin:4px 0 8px; }
+ .stage-step { display:flex; flex-direction:column; gap:4px; }
+ .stage-step-bar { height:4px; border-radius:3px; background:var(--line); }
+ .stage-step.done .stage-step-bar { background:rgba(74, 222, 128, 0.6); }
+ .stage-step.active .stage-step-bar { background:var(--accent-strong); box-shadow:0 0 8px rgba(74, 222, 128, 0.35); }
+ .stage-step small { font-size:9.5px; color:var(--muted); white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }
+ .stage-step.active small { color:var(--text); font-weight:700; }
+
+ /* Plant Telemetry 3-col Grid */
+ .plant-telemetry-grid { display:grid; grid-template-columns:repeat(3, 1fr); gap:8px; padding:12px; background:var(--surface-2); border-radius:var(--radius-control); border:1px solid var(--line-soft); }
+ .telemetry-cell { display:flex; flex-direction:column; gap:4px; min-width:0; }
+ .telemetry-cell-head { display:flex; align-items:center; justify-content:space-between; color:var(--muted); font-size:10.5px; font-weight:600; gap:4px; }
+ .telemetry-cell-title { display:inline-flex; align-items:center; gap:5px; }
+ .telemetry-cell-title ha-icon { --mdc-icon-size:14px; opacity:.85; color:var(--accent-strong); }
+ .telemetry-cell-val { font-size:17px; font-weight:750; color:var(--text); line-height:1.1; }
+
+ /* Card Bottom Action Bar */
+ .plant-card-foot { display:flex; align-items:center; justify-content:space-between; gap:12px; padding-top:6px; border-top:1px solid var(--line-soft); flex-wrap:wrap; }
+ .plant-care-status { display:flex; align-items:center; gap:6px; color:var(--muted); font-size:11px; }
+ .plant-care-status ha-icon { --mdc-icon-size:14px; color:var(--accent-strong); }
+ .plant-card-actions { display:flex; align-items:center; gap:8px; }
+ .plant-card-actions .primary { min-height:34px; padding:0 12px; font-size:12px; }
+ .plant-card-actions .secondary { min-height:34px; padding:0 10px; font-size:12px; }
.empty-garden,.system-state,.empty-journal { min-height:360px; border:1px dashed var(--line); border-radius:var(--radius-work); display:grid; align-content:center; justify-items:center; gap:12px; text-align:center; padding:34px; }
.empty-garden ha-icon,.system-state ha-icon { --mdc-icon-size:52px; color:var(--accent); }
.empty-garden p,.system-state p,.empty-journal p { max-width:500px; }
diff --git a/custom_components/plantrun/www/plantrun-panel-views.js b/custom_components/plantrun/www/plantrun-panel-views.js
index bf63dbd..5f02327 100644
--- a/custom_components/plantrun/www/plantrun-panel-views.js
+++ b/custom_components/plantrun/www/plantrun-panel-views.js
@@ -114,6 +114,44 @@ export function createPanelViewMethods() {
return this._renderOverview();
},
+ _renderRangeBar(value, min, max, targetLow, targetHigh, unit = "") {
+ const numVal = Number(value);
+ if (!Number.isFinite(numVal)) return "";
+ const targetLowPct = Math.max(0, Math.min(100, ((targetLow - min) / (max - min)) * 100));
+ const targetWidthPct = Math.max(2, Math.min(100 - targetLowPct, ((targetHigh - targetLow) / (max - min)) * 100));
+ const currentPct = Math.max(0, Math.min(100, ((numVal - min) / (max - min)) * 100));
+ return `
+
+
+ ${min}${unit}
+ Ziel ${targetLow}–${targetHigh}${unit}
+ ${max}${unit}
+
+
`;
+ },
+
+ _renderStatusBadge(value, targetLow, targetHigh, targetText = "") {
+ const numVal = Number(value);
+ if (!Number.isFinite(numVal)) {
+ return `Bereit`;
+ }
+ let status = "good";
+ let label = targetText || "Optimal";
+ if (numVal < targetLow) {
+ status = "warn";
+ label = "Niedrig";
+ } else if (numVal > targetHigh) {
+ status = "warn";
+ label = "Erhöht";
+ }
+ return `${e(label)}`;
+ },
+
_renderOverview() {
const tent = this._activeTent();
const runs = this._state.runs.filter((run) => !isArchived(run));
@@ -128,31 +166,157 @@ export function createPanelViewMethods() {
},
_renderTentStrip(tent) {
- const metrics = ["temperature", "humidity", "light", "energy"];
+ const metricConfigs = [
+ {
+ key: "temperature",
+ label: "Temperatur",
+ icon: "mdi:thermometer",
+ min: 18,
+ max: 34,
+ targetLow: 24,
+ targetHigh: 28,
+ unit: "°C",
+ fallbackSub: "Min 22,4 °C · Max 27,2 °C"
+ },
+ {
+ key: "humidity",
+ label: "Luftfeuchte",
+ icon: "mdi:water-percent",
+ min: 40,
+ max: 90,
+ targetLow: 65,
+ targetHigh: 75,
+ unit: "%",
+ fallbackSub: "Min 61,0 % · Max 72,0 %"
+ },
+ {
+ key: "light",
+ label: "Licht",
+ icon: "mdi:white-balance-sunny",
+ min: 0,
+ max: 80,
+ targetLow: 40,
+ targetHigh: 65,
+ unit: " klx",
+ fallbackSub: "18/6h Lichtplan"
+ },
+ {
+ key: "energy",
+ label: "Energie",
+ icon: "mdi:flash-outline",
+ min: 0,
+ max: 400,
+ targetLow: 200,
+ targetHigh: 320,
+ unit: " W",
+ fallbackSub: "Laufender Verbrauch"
+ }
+ ];
+
const bindings = Array.isArray(tent?.bindings) ? tent.bindings : [];
- const rows = metrics.map((metric) => {
- const binding = bindings.find((item) => item?.metric_type === metric && !item?.ended_at);
+ const cards = metricConfigs.map((cfg) => {
+ const binding = bindings.find((item) => item?.metric_type === cfg.key && !item?.ended_at);
const entityId = entityIdFor(binding);
const live = this._entityDisplay(entityId);
- const label = metric === "temperature" ? "Temperatur" : metric === "humidity" ? "Luftfeuchte" : metric === "light" ? "Licht" : "Energie";
- return `${label}${e(live.value)}${entityId ? e(live.status) : "Nicht zugeordnet"}
`;
+ const numeric = Number(live.raw);
+ const hasLive = Number.isFinite(numeric);
+ return `
+
+ ${cfg.label}
+ ${!entityId ? `Nicht zugeordnet` : ""}
+
+
${e(live.value)}
+ ${hasLive ? this._renderRangeBar(numeric, cfg.min, cfg.max, cfg.targetLow, cfg.targetHigh, cfg.unit) : ""}
+
${entityId ? e(live.status) : cfg.fallbackSub}
+
`;
});
- return ``;
+
+ return ``;
},
_renderPlantCard(run) {
const stage = currentStage(run);
- return `
-
-
+ const day = daysSince(runStart(run));
+ const stages = ["Keimung", "Sämling", "Vegetativ", "Blüte", "Ernte"];
+ const plan = stagePlan(run);
+ const recorded = recordedStages(run);
+ const breeder = breederName(run);
+ const cultivar = cultivarName(run);
+ const container = typeof run?.plant?.container === "string" ? run.plant.container : run?.plant?.container?.label || run?.container;
+ const substrate = run?.plant?.substrate || run?.substrate || "";
+
+ const tent = this._activeTent();
+ const moistureBinding = bindingForMetric(run, tent, "soil_moisture");
+ const tempBinding = bindingForMetric(run, tent, "temperature");
+ const ecBinding = bindingForMetric(run, tent, "conductivity");
+
+ const moistureLive = this._entityDisplay(entityIdFor(moistureBinding));
+ const tempLive = this._entityDisplay(entityIdFor(tempBinding));
+ const ecLive = this._entityDisplay(entityIdFor(ecBinding));
+
+ const moistureVal = Number(moistureLive.raw);
+ const tempVal = Number(tempLive.raw);
+ const ecVal = Number(ecLive.raw);
+
+ return `
+
+
+
+ ${(plan.length ? plan : stages).map((stg) => {
+ const isCurrent = stg.toLowerCase() === stage.toLowerCase();
+ const isPast = recorded.has(stg.toLowerCase());
+ const cls = isCurrent ? "active" : isPast ? "done" : "future";
+ return `
`;
+ }).join("")}
+
+
+
+
+
+ Bodenfeuchte
+
+
${e(moistureLive.value)}
+ ${Number.isFinite(moistureVal) ? this._renderRangeBar(moistureVal, 20, 80, 45, 65, "%") : `
${moistureLive.value !== "–" ? e(moistureLive.value) : "Kein Sensor zugeordnet"}
`}
+
+
+
+ Substrat-Temp
+
+
${e(tempLive.value)}
+ ${Number.isFinite(tempVal) ? this._renderRangeBar(tempVal, 15, 32, 20, 26, "°C") : `
${tempLive.value !== "–" ? e(tempLive.value) : "Kein Sensor zugeordnet"}
`}
+
+
+
+ Leitfähigkeit (EC)
+
+
${e(ecLive.value)}
+ ${Number.isFinite(ecVal) ? this._renderRangeBar(ecVal, 0.0, 2.4, 0.8, 1.4, " mS") : `
${ecLive.value !== "–" ? e(ecLive.value) : "Kein Sensor zugeordnet"}
`}
+
+
+
+
`;
},
diff --git a/custom_components/plantrun/www/prototype/plantrun-ui-prototype.html b/custom_components/plantrun/www/prototype/plantrun-ui-prototype.html
index a86a0fd..b5598c4 100644
--- a/custom_components/plantrun/www/prototype/plantrun-ui-prototype.html
+++ b/custom_components/plantrun/www/prototype/plantrun-ui-prototype.html
@@ -89,83 +89,126 @@
.fact dt { color: var(--muted); }
.fact dd { margin: 0; font-weight: 700; text-align: right; }
- /* A. Two equal, image-first plants. */
+ /* A. Two equal, image-first plants - Modernized */
.focus {
min-height: 100vh;
- --bg: #f1f7ef;
- --surface: #f8fbf6;
- --surface-2: #e5efdf;
- --surface-3: #dbe8d5;
- --text: #172018;
- --muted: #69746a;
- --line: rgba(31, 55, 34, .12);
- --line-strong: rgba(31, 55, 34, .21);
- --accent: #72b94f;
- --accent-soft: #4c8039;
- --accent-ink: #f8fff4;
- --shadow: none;
+ --bg: #070d09;
+ --surface: #0f1912;
+ --surface-2: #152219;
+ --surface-3: #1d3023;
+ --text: #f0f7f2;
+ --muted: #8fa596;
+ --line: rgba(144, 238, 144, 0.09);
+ --line-strong: rgba(144, 238, 144, 0.2);
+ --accent: #4ade80;
+ --accent-soft: #86efac;
+ --accent-ink: #07190d;
+ --accent-glow: rgba(74, 222, 128, 0.25);
+ --shadow: 0 16px 48px rgba(0, 0, 0, .45);
background: var(--bg);
color: var(--text);
}
- html[data-theme="dark"] .focus {
- --bg: #141713;
- --surface: #191e18;
- --surface-2: #232a21;
- --surface-3: #2b3429;
- --text: #eff2ea;
- --muted: #9ba397;
- --line: rgba(225, 235, 219, .12);
- --line-strong: rgba(225, 235, 219, .21);
- --accent: #7ea268;
- --accent-soft: #acc69d;
- --accent-ink: #11180e;
+ html[data-theme="light"] .focus {
+ --bg: #f2f7f2;
+ --surface: #ffffff;
+ --surface-2: #eaf2e9;
+ --surface-3: #ddeadd;
+ --text: #132217;
+ --muted: #5e7364;
+ --line: rgba(22, 54, 29, .1);
+ --line-strong: rgba(22, 54, 29, .22);
+ --accent: #2e9e50;
+ --accent-soft: #237b3e;
+ --accent-ink: #f0fbf2;
+ --accent-glow: rgba(46, 158, 80, 0.2);
}
.focus-shell { display: grid; grid-template-columns: 82px minmax(0, 1fr); min-height: 100vh; }
- .focus .side-nav { background: transparent; }
- .focus .brand { border: 1px solid var(--line-strong); border-radius: 5px; background: transparent; color: var(--accent-soft); }
- .focus .nav-link { border-radius: 0; background: transparent; }
- .focus .nav-link.active { box-shadow: inset 2px 0 var(--accent); background: transparent; color: var(--text); }
- .focus-main { padding: 26px clamp(22px, 3.6vw, 58px) 120px; }
- .focus .page-header h1 { font-size: clamp(27px, 2.5vw, 36px); font-weight: 620; }
- .focus .button, .focus .icon-button, .focus .language { border-radius: 6px; }
- .focus .language button { border-radius: 3px; }
- .focus .header-controls .button.primary { border: 1px solid var(--line-strong); background: transparent; color: var(--text); font-weight: 620; }
- .focus .sensor-ribbon { gap: 0; margin: 22px 0 30px; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
- .focus .sensor-pill { border: 0; border-right: 1px solid var(--line); border-radius: 0; background: transparent; }
- .focus .sensor-pill:last-child { border-right: 0; }
- .focus .sensor-icon { background: transparent; color: var(--muted); }
- .focus .sensor-pill strong { font-weight: 600; }
- .focus-layout { display: grid; grid-template-columns: minmax(0, 1fr) 290px; gap: 0; }
- .focus-plants { display: grid; grid-template-columns: 1fr 1fr; gap: 0; min-width: 0; }
- .focus-card { min-width: 0; padding: 0 clamp(18px, 2.5vw, 36px); border-right: 1px solid var(--line); background: transparent; }
- .focus-card:first-child { padding-left: 0; }
- .focus-card:last-child { border-right: 0; }
- .focus-image { position: relative; display: block; width: 100%; height: clamp(260px, 29vw, 390px); overflow: hidden; padding: 0; border-radius: 22px; background: transparent; text-align: left; }
- .focus-image img { width: 100%; height: 100%; object-fit: contain; transition: transform .25s ease; }
- html[data-theme="light"] .focus-image img { mix-blend-mode: multiply; }
- .focus-card:hover .focus-image img { transform: scale(1.015); }
- .focus-title { display: flex; align-items: flex-end; justify-content: space-between; gap: 12px; margin-bottom: 15px; }
- .focus-title h2 { margin: 6px 0 3px; font-size: clamp(20px, 2vw, 27px); letter-spacing: -.025em; }
- .focus-title h2, .focus-title strong { font-weight: 620; }
- .focus-title p { margin: 0; color: var(--muted); font-size: 12px; }
- .focus .status-chip { min-height: 0; padding: 0; background: transparent; color: var(--accent-soft); font-weight: 600; }
- .focus-body { padding: 16px 0 20px; }
- .focus-summary { display: grid; grid-template-columns: repeat(3, 1fr); gap: 0; border-top: 1px solid var(--line); border-bottom: 1px solid var(--line); }
- .focus-stat { padding: 11px 11px 11px 0; background: transparent; }
- .focus-stat + .focus-stat { padding-left: 11px; border-left: 1px solid var(--line); }
- .focus-stat small, .focus-stat strong { display: block; }
- .focus-stat small { color: var(--muted); font-size: 9px; }
- .focus-stat strong { margin-top: 4px; font-size: 12px; font-weight: 600; }
- .focus-card-actions { display: grid; grid-template-columns: 1fr auto; gap: 8px; margin-top: 15px; }
- .focus .focus-card-actions .button.primary { border: 1px solid var(--line-strong); background: transparent; color: var(--text); }
- .today-panel { align-self: start; padding: 0 0 0 26px; border: 0; border-left: 1px solid var(--line); border-radius: 0; background: transparent; }
- .today-panel h2 { margin: 0; font-size: 18px; }
- .today-item { display: grid; grid-template-columns: 39px 1fr; gap: 11px; padding: 15px 0; border-bottom: 1px solid var(--line); }
+ .focus .side-nav { background: var(--surface); border-right: 1px solid var(--line); }
+ .focus .brand { border: 1px solid var(--line-strong); border-radius: 12px; background: var(--accent); color: var(--accent-ink); }
+ .focus .nav-link { border-radius: 12px; }
+ .focus .nav-link.active { background: var(--surface-3); color: var(--accent-soft); }
+ .focus-main { padding: 26px clamp(24px, 3.8vw, 64px) 120px; }
+ .focus .page-header h1 { font-size: clamp(28px, 2.8vw, 40px); font-weight: 750; letter-spacing: -.035em; }
+ .focus .button, .focus .icon-button, .focus .language { border-radius: 10px; }
+ .focus .language button { border-radius: 6px; }
+ .focus .header-controls .button.primary { border: 0; background: var(--accent); color: var(--accent-ink); font-weight: 720; }
+
+ /* Zone badge styling */
+ .zone-badge { display: inline-flex; align-items: center; gap: 5px; font-size: 10.5px; font-weight: 700; padding: 2.5px 8px; border-radius: 6px; line-height: 1.2; }
+ .zone-badge.good { background: rgba(52, 211, 153, 0.12); color: #34d399; border: 1px solid rgba(52, 211, 153, 0.28); }
+ .zone-badge.warn { background: rgba(251, 191, 36, 0.12); color: #fbbf24; border: 1px solid rgba(251, 191, 36, 0.28); }
+ .zone-badge.bad { background: rgba(248, 113, 113, 0.12); color: #f87171; border: 1px solid rgba(248, 113, 113, 0.28); }
+ .zone-badge.neutral { background: rgba(255, 255, 255, 0.06); color: var(--muted); border: 1px solid var(--line); }
+ .zone-dot { width: 5px; height: 5px; border-radius: 50%; background: currentColor; }
+
+ /* Range Gauge Bar */
+ .range-gauge-wrap { display: flex; flex-direction: column; gap: 4px; width: 100%; margin-top: 5px; }
+ .range-gauge-track { position: relative; width: 100%; height: 5px; border-radius: 5px; background: rgba(255, 255, 255, 0.08); overflow: visible; }
+ html[data-theme="light"] .range-gauge-track { background: rgba(0, 0, 0, 0.08); }
+ .range-gauge-zone { position: absolute; top: 0; bottom: 0; background: linear-gradient(90deg, rgba(52, 211, 153, 0.35), rgba(74, 222, 128, 0.65)); border-radius: 3px; }
+ .range-gauge-needle { position: absolute; top: 50%; transform: translate(-50%, -50%); width: 10px; height: 10px; display: grid; place-items: center; z-index: 2; }
+ .range-needle-dot { width: 7px; height: 7px; border-radius: 50%; background: #ffffff; border: 1.5px solid #10b981; box-shadow: 0 0 5px rgba(255, 255, 255, 0.85); }
+ .range-gauge-labels { display: flex; justify-content: space-between; align-items: center; font-size: 9px; color: var(--muted); }
+ .range-target-label { color: #34d399; font-weight: 600; }
+ html[data-theme="light"] .range-target-label { color: #15803d; }
+
+ /* Stage lifecycle progress bar */
+ .stage-timeline { display: grid; grid-template-columns: repeat(5, 1fr); gap: 6px; margin: 12px 0; }
+ .stage-step { display: flex; flex-direction: column; gap: 4px; }
+ .stage-step-bar { height: 4px; border-radius: 3px; background: var(--line-strong); }
+ .stage-step.done .stage-step-bar { background: rgba(74, 222, 128, 0.6); }
+ .stage-step.active .stage-step-bar { background: var(--accent); box-shadow: 0 0 8px var(--accent-glow); }
+ .stage-step small { font-size: 9.5px; color: var(--muted); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+ .stage-step.active small { color: var(--text); font-weight: 700; }
+
+ /* Modern Tent Sensor Cards */
+ .modern-sensor-grid { display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); gap: 12px; margin: 20px 0 28px; }
+ .sensor-card { background: var(--surface); border: 1px solid var(--line); border-radius: 16px; padding: 14px 16px; display: flex; flex-direction: column; gap: 8px; transition: border-color .2s, transform .2s; }
+ .sensor-card:hover { border-color: var(--line-strong); transform: translateY(-1px); }
+ .sensor-card-head { display: flex; align-items: center; justify-content: space-between; }
+ .sensor-card-title { display: flex; align-items: center; gap: 7px; color: var(--muted); font-size: 11.5px; font-weight: 600; }
+ .sensor-card-icon { width: 26px; height: 26px; border-radius: 7px; background: var(--surface-2); color: var(--accent-soft); display: grid; place-items: center; }
+ .sensor-card-val { font-size: 21px; font-weight: 750; letter-spacing: -.02em; color: var(--text); line-height: 1.1; margin: 2px 0 0; }
+ .sensor-card-sub { font-size: 10px; color: var(--muted); display: flex; justify-content: space-between; align-items: center; margin-top: 2px; }
+
+ /* Modern Layout */
+ .focus-plants { display: grid; grid-template-columns: repeat(auto-fit, minmax(420px, 1fr)); gap: 20px; width: 100%; }
+
+ /* Modern Plant Cards */
+ .modern-plant-card { background: var(--surface); border: 1px solid var(--line); border-radius: 20px; padding: 20px; display: flex; flex-direction: column; gap: 14px; box-shadow: var(--shadow); position: relative; transition: border-color .2s; }
+ .modern-plant-card:hover { border-color: var(--line-strong); }
+ .plant-card-header { display: grid; grid-template-columns: 116px 1fr; gap: 16px; align-items: start; }
+ .plant-thumb-wrap { position: relative; width: 116px; height: 116px; border-radius: 14px; overflow: hidden; background: var(--surface-2); }
+ .plant-thumb-wrap img { width: 100%; height: 100%; object-fit: cover; }
+ .plant-breeder-tag { position: absolute; bottom: 5px; left: 5px; right: 5px; padding: 2px 5px; border-radius: 5px; background: rgba(5, 12, 7, 0.85); backdrop-filter: blur(4px); color: #d1fae5; font-size: 9px; font-weight: 700; text-align: center; border: 1px solid rgba(255, 255, 255, 0.15); white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
+ .plant-info-block { display: flex; flex-direction: column; gap: 5px; min-width: 0; }
+ .plant-stage-badge { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; border-radius: 6px; background: rgba(52, 211, 153, 0.12); color: #34d399; border: 1px solid rgba(52, 211, 153, 0.28); font-size: 10.5px; font-weight: 750; width: fit-content; }
+ .pulse-dot { width: 6px; height: 6px; border-radius: 50%; background: #34d399; box-shadow: 0 0 6px #34d399; }
+ .plant-info-block h2 { margin: 0; font-size: 21px; font-weight: 750; letter-spacing: -.025em; color: var(--text); }
+ .plant-genetics-sub { color: var(--muted); font-size: 11.5px; margin: 0; line-height: 1.35; }
+ .plant-harvest-pill { display: inline-flex; align-items: center; gap: 5px; padding: 3px 8px; border-radius: 6px; background: var(--surface-2); color: var(--muted); font-size: 10.5px; width: fit-content; margin-top: 2px; }
+ .plant-harvest-pill strong { color: var(--text); }
+
+ /* Plant Telemetry 3-col Grid */
+ .plant-telemetry-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; padding: 12px; background: var(--surface-2); border-radius: 12px; border: 1px solid var(--line); }
+ .telemetry-cell { display: flex; flex-direction: column; gap: 4px; }
+ .telemetry-cell-head { display: flex; align-items: center; justify-content: space-between; color: var(--muted); font-size: 10.5px; font-weight: 600; }
+ .telemetry-cell-val { font-size: 17px; font-weight: 750; color: var(--text); line-height: 1.1; }
+
+ /* Card Bottom Action Bar */
+ .plant-card-foot { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding-top: 6px; border-top: 1px solid var(--line); }
+ .plant-care-status { display: flex; align-items: center; gap: 6px; color: var(--muted); font-size: 11px; }
+ .plant-card-actions { display: flex; align-items: center; gap: 8px; }
+ .plant-card-actions .button.primary { min-height: 34px; padding: 0 12px; font-size: 12px; }
+ .plant-card-actions .button.outline { min-height: 34px; padding: 0 10px; font-size: 12px; }
+
+ .today-panel { align-self: start; padding: 18px; border: 1px solid var(--line); border-radius: 18px; background: var(--surface); }
+ .today-panel h2 { margin: 0 0 12px; font-size: 17px; font-weight: 700; }
+ .today-item { display: grid; grid-template-columns: 34px 1fr; gap: 10px; padding: 12px 0; border-bottom: 1px solid var(--line); }
.today-item:last-of-type { border-bottom: 0; }
- .today-icon { display: grid; width: 39px; height: 39px; place-items: center; border-radius: 0; background: transparent; color: var(--muted); }
- .focus .today-item strong, .focus .today-panel h2 { font-weight: 620; }
- .today-item strong, .today-item small { display: block; }
- .today-item small { margin-top: 4px; color: var(--muted); line-height: 1.35; }
+ .today-icon { display: grid; width: 34px; height: 34px; place-items: center; border-radius: 8px; background: var(--surface-2); color: var(--accent-soft); }
+ .today-item strong { font-size: 12px; font-weight: 650; display: block; }
+ .today-item small { margin-top: 2px; color: var(--muted); line-height: 1.3; display: block; font-size: 10.5px; }
/* B. One selected plant becomes the workspace. */
.workspace { min-height: 100vh; background: var(--bg); }
@@ -911,7 +954,48 @@
const createCopy={de:{createRuns:"Neue Pflanze anlegen",createIntro:"Eine Pflanze wird als eigener Lauf angelegt. Danach kannst du direkt die nächste Pflanze anlegen.",stepPlant:"Pflanze",stepSetup:"Phasen & Sensoren",stepReview:"Prüfen",chooseStrain:"Strain suchen",chooseStrainHint:"Suche wie in der bisherigen Version nach Breeder und Strain. Die Auswahl hilft beim ersten Erntekorridor und bleibt vollständig änderbar.",nickname:"Spitzname",nicknameOptional:"Optional",plantDate:"Pflanzdatum",breederDuration:"Breeder-Angabe",harvestEstimate:"Erster Erntekorridor",stages:"Phasen",customPhase:"Eigene Phase hinzufügen",setup:"Sensoren zuordnen",setupHint:"Nur Pflanzensensoren sind exklusiv. Zeltwerte dürfen gemeinsam genutzt werden.",soilSensor:"Bodensensor",sharedClimate:"Klima",lightSensor:"Helligkeit",doorSensor:"Zelttür",later:"Später zuordnen",sensorA:"Bodensensor A · Feuchte, Temperatur, EC",sensorB:"Bodensensor B · Feuchte, Temperatur, Luftfeuchte",averageSensors:"Mittelwert aus 3 Quellen",sharedTent:"Gemeinsamer Zeltwert",continue:"Weiter",back:"Zurück",review:"Lauf prüfen",createRun:"Pflanze anlegen",runCreated:"Pflanze im Prototyp angelegt",prototypeOnly:"Nur Prototyp · noch keine Daten werden gespeichert",plantSpecific:"Je Pflanze",selected:"Ausgewählt",notAssigned:"Noch nicht zugeordnet",createdNext:"Als Nächstes kannst du den Lauf öffnen und direkt den ersten Eintrag erfassen.",openPrototype:"Zur Pflanzenansicht",allEditable:"Jeder Wert bleibt auch nach dem Anlegen änderbar.",summary:"Zusammenfassung",runSingular:"1 Pflanze · 1 Lauf",strainQuery:"Strain",strainQueryPlaceholder:"z. B. Northern Lights Automatic",breederQuery:"Breeder",breederQueryPlaceholder:"z. B. Royal Queen Seeds",search:"Suchen",searchFirst:"Gib Breeder und Strain ein. Die spätere HA-Version verwendet dafür wieder die vorhandene SeedFinder-Suche.",sampleResults:"SeedFinder-Vorschau · Beispieldaten",noResults:"Keine passenden Beispieltreffer. Du kannst die Angaben trotzdem manuell erfassen.",manualEntry:"Strain manuell eingeben",manualActive:"Manuelle Angaben",manualName:"Strainname",manualBreeder:"Breeder",manualDuration:"Dauer ab Pflanzdatum",daysFrom:"von Tagen",daysTo:"bis Tage",imagePending:"Breeder-Bild wird bei der echten Suche geladen",providerContract:"Produktion: authentifizierte HA-Suche · Fallback blockiert das Anlegen nie",searchNeedsBoth:"Breeder und Strain werden für die Suche benötigt."},en:{createRuns:"Add new plant",createIntro:"Each plant is created as its own run. You can add the next plant immediately afterwards.",stepPlant:"Plant",stepSetup:"Stages & sensors",stepReview:"Review",chooseStrain:"Search strain",chooseStrainHint:"Search by breeder and strain as in the previous version. The selection helps set the initial harvest window and remains fully editable.",nickname:"Nickname",nicknameOptional:"Optional",plantDate:"Planting date",breederDuration:"Breeder estimate",harvestEstimate:"Initial harvest window",stages:"Stages",customPhase:"Add custom stage",setup:"Assign sensors",setupHint:"Only plant sensors are exclusive. Tent values may be shared.",soilSensor:"Soil sensor",sharedClimate:"Climate",lightSensor:"Illuminance",doorSensor:"Tent door",later:"Assign later",sensorA:"Soil sensor A · moisture, temperature, EC",sensorB:"Soil sensor B · moisture, temperature, humidity",averageSensors:"Average of 3 sources",sharedTent:"Shared tent value",continue:"Continue",back:"Back",review:"Review run",createRun:"Add plant",runCreated:"Plant created in the prototype",prototypeOnly:"Prototype only · no data is saved yet",plantSpecific:"Per plant",selected:"Selected",notAssigned:"Not assigned yet",createdNext:"Next, open the run and capture the first entry directly.",openPrototype:"Open plant view",allEditable:"Every value remains editable after creation.",summary:"Summary",runSingular:"1 plant · 1 run",strainQuery:"Strain",strainQueryPlaceholder:"e.g. Northern Lights Automatic",breederQuery:"Breeder",breederQueryPlaceholder:"e.g. Royal Queen Seeds",search:"Search",searchFirst:"Enter breeder and strain. The HA build will reuse the existing SeedFinder search.",sampleResults:"SeedFinder preview · sample data",noResults:"No matching sample results. You can still enter the details manually.",manualEntry:"Enter strain manually",manualActive:"Manual details",manualName:"Strain name",manualBreeder:"Breeder",manualDuration:"Duration from planting",daysFrom:"from days",daysTo:"to days",imagePending:"Breeder image loads through the real search",providerContract:"Production: authenticated HA search · fallback never blocks creation",searchNeedsBoth:"Breeder and strain are required for search."}};
const params=new URLSearchParams(location.search);
const state={variant:params.get("variant")||"A",detailVariant:params.get("detail")||"A",createVariant:"A",createStep:Math.min(3,Math.max(1,Number(params.get("step"))||1)),view:params.get("view")||"overview",lang:params.get("lang")||"de",theme:params.get("theme")||"dark",selectedPlant:params.get("plant")||"tangerine",selectedAction:"freeText",cleanMetric:params.get("metric")||"moisture",modal:null,entries:[],createdRuns:[],customPhases:[],strainQuery:"",breederQuery:"",strainSearchSubmitted:false,manualStrain:false,createDraft:{strainId:params.get("strain")||"",nickname:"",planted:"2026-08-21",schedule:"18/6",soilSensor:"later",climate:"average",light:"shared",door:"shared",manualName:"",manualBreeder:"",manualLow:"",manualHigh:""}};
- const plants={tangerine:{id:"tangerine",name:"Tangerine Dream Auto",breeder:"Zamnesia",stage:"seedling",day:1,image:"./assets/tangerine-dream-breeder.jpg",moisture:"46 %",conductivity:"—",harvestDe:"29. Okt. – 5. Nov.",harvestEn:"Oct 29 – Nov 5"},diesel:{id:"diesel",name:"Diesel Auto",breeder:"Royal Queen Seeds",stage:"seedling",day:1,image:"./assets/diesel-auto-breeder.jpg",moisture:"52 %",conductivity:"0 µS/cm",harvestDe:"18. – 23. Nov.",harvestEn:"Nov 18 – 23"}};
+ const plants={
+ tangerine:{
+ id:"tangerine",
+ name:"Tangerine Dream Auto",
+ breeder:"Zamnesia",
+ stage:"seedling",
+ day:5,
+ genetics:"Autoflower · Feminisiert",
+ container:"11L Stofftopf (BioBizz Light-Mix)",
+ image:"./assets/tangerine-dream-breeder.jpg",
+ moistureVal:58,
+ moisture:"58 %",
+ tempVal:23.8,
+ temp:"23,8 °C",
+ ecVal:1.2,
+ conductivity:"1,2 mS/cm",
+ harvestDe:"29. Okt. – 5. Nov.",
+ harvestEn:"Oct 29 – Nov 5",
+ harvestDaysLeft:65,
+ lastWatering:"Vor 18 Std. gegossen (350 ml)"
+ },
+ diesel:{
+ id:"diesel",
+ name:"Diesel Auto",
+ breeder:"Royal Queen Seeds",
+ stage:"seedling",
+ day:5,
+ genetics:"Autoflower · Feminisiert",
+ container:"11L Stofftopf (Plagron Promix)",
+ image:"./assets/diesel-auto-breeder.jpg",
+ moistureVal:52,
+ moisture:"52 %",
+ tempVal:24.1,
+ temp:"24,1 °C",
+ ecVal:1.1,
+ conductivity:"1,1 mS/cm",
+ harvestDe:"18. – 23. Nov.",
+ harvestEn:"Nov 18 – 23",
+ harvestDaysLeft:68,
+ lastWatering:"Vor 18 Std. gegossen (350 ml)"
+ }
+ };
const strainCatalog=[
{id:"tangerine",name:"Tangerine Dream Auto",breeder:"Zamnesia",duration:[70,77],image:"./assets/tangerine-dream-breeder.jpg"},
{id:"diesel",name:"Diesel Auto",breeder:"Royal Queen Seeds",duration:[90,95],image:"./assets/diesel-auto-breeder.jpg"},
@@ -931,11 +1015,198 @@
function sideNav(){return ``}
function mobileNav(){return ``}
function pageHeader(){return ``}
- function sensorRibbon(){const s=[["temp",t("temperature"),"24,2 °C"],["drop",t("humidity"),"63,5 %"],["sun",t("light"),"138 lx"],["power",t("lamp"),t("on")],["door",t("door"),t("closed")],["chart",t("energy"),"489,03 kWh"]];return `${s.map(([i,l,v])=>`${icon(i,true)}${l}${v}
`).join("")}`}
+ function renderRangeBar(value, min, max, targetLow, targetHigh, unit = "") {
+ const targetLowPct = Math.max(0, Math.min(100, ((targetLow - min) / (max - min)) * 100));
+ const targetWidthPct = Math.max(2, Math.min(100 - targetLowPct, ((targetHigh - targetLow) / (max - min)) * 100));
+ const currentPct = Math.max(0, Math.min(100, ((value - min) / (max - min)) * 100));
+ return `
+
+
+ ${min}${unit}
+ Ziel ${targetLow}–${targetHigh}${unit}
+ ${max}${unit}
+
+
`;
+ }
+
+ function renderStatusBadge(value, targetLow, targetHigh, targetText) {
+ let status = "good";
+ let label = targetText || "Optimal";
+ if (value < targetLow) {
+ status = "warn";
+ label = "Niedrig";
+ } else if (value > targetHigh) {
+ status = "warn";
+ label = "Erhöht";
+ }
+ return `${label}`;
+ }
+
+ function sensorRibbon() {
+ const cards = [
+ {
+ icon: "temp",
+ label: t("temperature"),
+ val: "26,9 °C",
+ numeric: 26.9,
+ min: 18,
+ max: 34,
+ targetLow: 24,
+ targetHigh: 28,
+ unit: "°C",
+ badge: "Optimal",
+ sub: "Min 22,4 °C · Max 27,2 °C"
+ },
+ {
+ icon: "drop",
+ label: t("humidity"),
+ val: "69,5 %",
+ numeric: 69.5,
+ min: 40,
+ max: 90,
+ targetLow: 65,
+ targetHigh: 75,
+ unit: "%",
+ badge: "Sämling · Ideal",
+ sub: "Min 61,0 % · Max 72,0 %"
+ },
+ {
+ icon: "eye",
+ label: "VPD (Dampfdruck)",
+ val: "0,94 kPa",
+ numeric: 0.94,
+ min: 0.4,
+ max: 1.8,
+ targetLow: 0.8,
+ targetHigh: 1.1,
+ unit: " kPa",
+ badge: "Ideal",
+ sub: "Ideale Transpiration"
+ },
+ {
+ icon: "sun",
+ label: "Licht & DLI",
+ val: "56,5 klx",
+ numeric: 56.5,
+ min: 0,
+ max: 80,
+ targetLow: 40,
+ targetHigh: 65,
+ unit: " klx",
+ badge: "18/6h An",
+ sub: "DLI 37,6 mol/m²d · 78%"
+ },
+ {
+ icon: "chart",
+ label: t("energy") + " & Last",
+ val: "497,8 kWh",
+ numeric: 280,
+ min: 0,
+ max: 400,
+ targetLow: 200,
+ targetHigh: 320,
+ unit: " W",
+ badge: "Live",
+ sub: "280 W Gesamtlast (~0,84 €/d)"
+ }
+ ];
+
+ return `
+ ${cards.map(c => `
+
+
+ ${icon(c.icon, true)}${c.label}
+
+
${c.val}
+ ${renderRangeBar(c.numeric, c.min, c.max, c.targetLow, c.targetHigh, c.unit)}
+
${c.sub}
+
+ `).join("")}
+ `;
+ }
+
function stageTrack(){return `${["seedling","vegetative","flowering","harvested"].map((k,i)=>`${t(k)}`).join("")}
`}
- function focusCard(p){return `${t(p.stage)} · ${t("day")} ${p.day}${p.name}
${p.breeder}
${t("moisture")}${p.moisture}${t("harvestWindow")}${harvestWindow(p)}${t("next")}${t("plantToday")}
${stageTrack()}
`}
+
+ function focusCard(p) {
+ const stage = p.stage || "seedling";
+ const day = p.day || 5;
+ const stageLabel = t(stage);
+ const moistureVal = p.moistureVal || 52;
+ const tempVal = p.tempVal || 24.1;
+ const ecVal = p.ecVal || 1.1;
+ const stages = [
+ { key: "germination", label: "Keimung", state: "done" },
+ { key: "seedling", label: "Sämling", state: "active" },
+ { key: "vegetative", label: "Vegetativ", state: "future" },
+ { key: "flowering", label: "Blüte", state: "future" },
+ { key: "harvested", label: "Ernte", state: "future" }
+ ];
+
+ return `
+
+
+
+ ${stages.map(s => `
+
+ `).join("")}
+
+
+
+
+
+ ${t("moisture")}
+
+
${moistureVal} %
+ ${renderRangeBar(moistureVal, 20, 80, 45, 65, "%")}
+
+
+
+ Substrat-Temp
+
+
${tempVal.toFixed(1).replace(".", ",")} °C
+ ${renderRangeBar(tempVal, 15, 32, 20, 26, "°C")}
+
+
+
+ Leitfähigkeit (EC)
+
+
${ecVal.toFixed(1).replace(".", ",")} mS/cm
+ ${renderRangeBar(ecVal, 0.0, 2.4, 0.8, 1.4, " mS")}
+
+
+
+
+ `;
+ }
function todayPanel(){return ``}
- function renderA(){return `${sideNav()}
${pageHeader()}${sensorRibbon()}${Object.values(plants).map(focusCard).join("")}${todayPanel()}
${mobileNav()}`}
+ function renderA(){return `${sideNav()}${pageHeader()}${sensorRibbon()}${Object.values(plants).map(focusCard).join("")}
${mobileNav()}`}
function facts(p){return `- ${t("stage")}
- ${t(p.stage)} · ${t("day")} ${p.day}
- ${t("planted")}
- 20.08.2026
- ${t("harvestWindow")}
- ${harvestWindow(p)}
- ${t("schedule")}
- 18/6
- ${t("moisture")}
- ${p.moisture}
- ${t("conductivity")}
- ${p.conductivity}
`}
function workspaceTop(){return `${icon("leaf")}PlantRun
${headerControls()}`}
function renderB(){const p=selected(),o=other();return `${workspaceTop()}
${icon("leaf",true)}${t("breederImage")}${t(p.stage)} · ${t("day")} ${p.day}${p.name}
${t("harvestWindow")}: ${harvestWindow(p)}
${stageTrack()}
${sensorRibbon()}${mobileNav()}`}
diff --git a/docs/screenshots/ha_panel_overview_dark.png b/docs/screenshots/ha_panel_overview_dark.png
new file mode 100644
index 0000000..5397180
Binary files /dev/null and b/docs/screenshots/ha_panel_overview_dark.png differ
diff --git a/docs/screenshots/ha_panel_overview_light.png b/docs/screenshots/ha_panel_overview_light.png
new file mode 100644
index 0000000..99b5a79
Binary files /dev/null and b/docs/screenshots/ha_panel_overview_light.png differ
diff --git a/tests/frontend_harness.html b/tests/frontend_harness.html
index 2b36932..a30772e 100644
--- a/tests/frontend_harness.html
+++ b/tests/frontend_harness.html
@@ -9,8 +9,42 @@