@hbslovely/baby-feeding 2026.8.2 → 2026.8.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,7 +2,7 @@ import * as i0 from '@angular/core';
2
2
  import { InjectionToken, inject, Injectable, signal, input, output, computed, ChangeDetectionStrategy, Component, DestroyRef, HostListener, effect, ViewChild, EventEmitter, Output, ChangeDetectorRef, Input, ViewChildren } from '@angular/core';
3
3
  import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
4
4
  import * as i1$1 from '@angular/common';
5
- import { CommonModule, DecimalPipe } from '@angular/common';
5
+ import { CommonModule, DecimalPipe, NgClass } from '@angular/common';
6
6
  import { ActivatedRoute } from '@angular/router';
7
7
  import { map, catchError, tap, switchMap, mergeMap, scan, takeLast, startWith, finalize, concatMap } from 'rxjs/operators';
8
8
  import { throwError, of, from, Subject, interval, firstValueFrom, finalize as finalize$1, catchError as catchError$1, switchMap as switchMap$1, forkJoin } from 'rxjs';
@@ -12498,13 +12498,50 @@ function formatGrowthZScore(zScore) {
12498
12498
 
12499
12499
  class WeightChartsComponent {
12500
12500
  constructor() {
12501
+ this.minZoom = 1;
12502
+ this.maxZoom = 3;
12503
+ this.zoomStep = 0.25;
12501
12504
  this.spanPreset = signal('90d', ...(ngDevMode ? [{ debugName: "spanPreset" }] : /* istanbul ignore next */ []));
12502
12505
  this.pinLatestPoint = signal(true, ...(ngDevMode ? [{ debugName: "pinLatestPoint" }] : /* istanbul ignore next */ []));
12506
+ this.velocityZoom = signal(1, ...(ngDevMode ? [{ debugName: "velocityZoom" }] : /* istanbul ignore next */ []));
12507
+ this.compareZoom = signal(1, ...(ngDevMode ? [{ debugName: "compareZoom" }] : /* istanbul ignore next */ []));
12508
+ this.dashShowValues = signal(true, ...(ngDevMode ? [{ debugName: "dashShowValues" }] : /* istanbul ignore next */ []));
12509
+ this.activePointKey = signal(null, ...(ngDevMode ? [{ debugName: "activePointKey" }] : /* istanbul ignore next */ []));
12510
+ this.panEl = null;
12511
+ this.panStartX = 0;
12512
+ this.panScrollLeft = 0;
12503
12513
  this.weightVsStandardChart = input(null, ...(ngDevMode ? [{ debugName: "weightVsStandardChart" }] : /* istanbul ignore next */ []));
12504
12514
  this.heightVsStandardChart = input(null, ...(ngDevMode ? [{ debugName: "heightVsStandardChart" }] : /* istanbul ignore next */ []));
12505
12515
  this.weightTrendChart = input(null, ...(ngDevMode ? [{ debugName: "weightTrendChart" }] : /* istanbul ignore next */ []));
12506
12516
  this.simpleTrendMode = input(false, ...(ngDevMode ? [{ debugName: "simpleTrendMode" }] : /* istanbul ignore next */ []));
12507
12517
  this.weightVelocityLineChart = input(null, ...(ngDevMode ? [{ debugName: "weightVelocityLineChart" }] : /* istanbul ignore next */ []));
12518
+ this.heightVelocityLineChart = input(null, ...(ngDevMode ? [{ debugName: "heightVelocityLineChart" }] : /* istanbul ignore next */ []));
12519
+ this.dashboardMode = input(false, ...(ngDevMode ? [{ debugName: "dashboardMode" }] : /* istanbul ignore next */ []));
12520
+ this.comparePeriod = input('month', ...(ngDevMode ? [{ debugName: "comparePeriod" }] : /* istanbul ignore next */ []));
12521
+ this.growthMetric = input('weight', ...(ngDevMode ? [{ debugName: "growthMetric" }] : /* istanbul ignore next */ []));
12522
+ this.showControls = computed(() => !this.dashboardMode(), ...(ngDevMode ? [{ debugName: "showControls" }] : /* istanbul ignore next */ []));
12523
+ this.showWeightCompare = computed(() => !this.dashboardMode() || this.growthMetric() === 'weight', ...(ngDevMode ? [{ debugName: "showWeightCompare" }] : /* istanbul ignore next */ []));
12524
+ this.showHeightCompare = computed(() => !this.dashboardMode() || this.growthMetric() === 'height', ...(ngDevMode ? [{ debugName: "showHeightCompare" }] : /* istanbul ignore next */ []));
12525
+ this.showSimpleTrend = computed(() => !this.dashboardMode() && this.simpleTrendMode(), ...(ngDevMode ? [{ debugName: "showSimpleTrend" }] : /* istanbul ignore next */ []));
12526
+ this.activeVelocityChart = computed(() => this.growthMetric() === 'weight'
12527
+ ? this.weightVelocityLineChart()
12528
+ : this.heightVelocityLineChart(), ...(ngDevMode ? [{ debugName: "activeVelocityChart" }] : /* istanbul ignore next */ []));
12529
+ this.activeWeightCompare = computed(() => this.growthMetric() === 'weight' ? this.weightVsStandardChart() : null, ...(ngDevMode ? [{ debugName: "activeWeightCompare" }] : /* istanbul ignore next */ []));
12530
+ this.activeHeightCompare = computed(() => this.growthMetric() === 'height' ? this.heightVsStandardChart() : null, ...(ngDevMode ? [{ debugName: "activeHeightCompare" }] : /* istanbul ignore next */ []));
12531
+ this.velocitySectionTitle = computed(() => {
12532
+ const metric = this.growthMetric() === 'weight' ? 'cân nặng' : 'chiều cao';
12533
+ const period = this.comparePeriod() === 'month' ? 'tháng' : 'tuần';
12534
+ return `Tốc độ phát triển · ${metric} · ${period}`;
12535
+ }, ...(ngDevMode ? [{ debugName: "velocitySectionTitle" }] : /* istanbul ignore next */ []));
12536
+ this.compareSectionTitle = computed(() => {
12537
+ const metric = this.growthMetric() === 'weight' ? 'cân nặng' : 'chiều cao';
12538
+ return `So với chuẩn · ${metric}`;
12539
+ }, ...(ngDevMode ? [{ debugName: "compareSectionTitle" }] : /* istanbul ignore next */ []));
12540
+ this.compareCaption = computed(() => {
12541
+ const unit = this.growthMetric() === 'weight' ? 'kg' : 'cm';
12542
+ return `Trục X: tuổi · trục Y: ${unit} · cuộn ngang để pan · +/− để zoom`;
12543
+ }, ...(ngDevMode ? [{ debugName: "compareCaption" }] : /* istanbul ignore next */ []));
12544
+ this.velocityCaption = computed(() => 'Cuộn ngang để pan · +/− để zoom · chạm điểm để xem số liệu', ...(ngDevMode ? [{ debugName: "velocityCaption" }] : /* istanbul ignore next */ []));
12508
12545
  this.spanDays = computed(() => {
12509
12546
  const preset = this.spanPreset();
12510
12547
  if (preset === '30d')
@@ -12528,12 +12565,135 @@ class WeightChartsComponent {
12528
12565
  formatGPerDay(n) {
12529
12566
  return formatWeightGPerDay(n);
12530
12567
  }
12568
+ formatVelocityValue(vel, n) {
12569
+ if (vel.seriesLabel.includes('g/ngày')) {
12570
+ return `${n} g/ngày`;
12571
+ }
12572
+ return `${n}${vel.refSuffix}`;
12573
+ }
12574
+ formatSummaryValue(summary) {
12575
+ return summary.unit === 'kg'
12576
+ ? this.formatKg(summary.baby)
12577
+ : this.formatCm(summary.baby);
12578
+ }
12579
+ formatSummaryRef(summary) {
12580
+ return summary.unit === 'kg' ? this.formatKg(summary.ref) : this.formatCm(summary.ref);
12581
+ }
12582
+ chartScaledWidth(w, zoom) {
12583
+ return Math.round(w * zoom);
12584
+ }
12585
+ chartScaledHeight(h, zoom) {
12586
+ return Math.round(h * zoom);
12587
+ }
12588
+ zoomPercent(zoom) {
12589
+ return Math.round(zoom * 100);
12590
+ }
12531
12591
  setSpanPreset(preset) {
12532
12592
  this.spanPreset.set(preset);
12533
12593
  }
12534
12594
  togglePinLatest() {
12535
12595
  this.pinLatestPoint.update((v) => !v);
12536
12596
  }
12597
+ toggleDashShowValues() {
12598
+ this.dashShowValues.update((v) => !v);
12599
+ }
12600
+ zoomIn(target) {
12601
+ const signalRef = target === 'velocity' ? this.velocityZoom : this.compareZoom;
12602
+ signalRef.update((z) => Math.min(this.maxZoom, Math.round((z + this.zoomStep) * 100) / 100));
12603
+ }
12604
+ zoomOut(target) {
12605
+ const signalRef = target === 'velocity' ? this.velocityZoom : this.compareZoom;
12606
+ signalRef.update((z) => Math.max(this.minZoom, Math.round((z - this.zoomStep) * 100) / 100));
12607
+ }
12608
+ resetZoom(target) {
12609
+ const signalRef = target === 'velocity' ? this.velocityZoom : this.compareZoom;
12610
+ signalRef.set(1);
12611
+ }
12612
+ onChartWheel(event, target) {
12613
+ if (!event.ctrlKey && !event.metaKey)
12614
+ return;
12615
+ event.preventDefault();
12616
+ if (event.deltaY < 0) {
12617
+ this.zoomIn(target);
12618
+ }
12619
+ else {
12620
+ this.zoomOut(target);
12621
+ }
12622
+ }
12623
+ onPanPointerDown(event, el) {
12624
+ if (event.target.closest('.wg-node-hit'))
12625
+ return;
12626
+ this.panEl = el;
12627
+ this.panStartX = event.clientX;
12628
+ this.panScrollLeft = el.scrollLeft;
12629
+ el.setPointerCapture(event.pointerId);
12630
+ el.classList.add('is-panning');
12631
+ }
12632
+ onPanPointerMove(event) {
12633
+ if (!this.panEl)
12634
+ return;
12635
+ this.panEl.scrollLeft = this.panScrollLeft - (event.clientX - this.panStartX);
12636
+ }
12637
+ onPanPointerUp(event) {
12638
+ if (!this.panEl)
12639
+ return;
12640
+ try {
12641
+ this.panEl.releasePointerCapture(event.pointerId);
12642
+ }
12643
+ catch {
12644
+ /* already released */
12645
+ }
12646
+ this.panEl.classList.remove('is-panning');
12647
+ this.panEl = null;
12648
+ }
12649
+ selectPoint(key) {
12650
+ this.activePointKey.update((current) => (current === key ? null : key));
12651
+ }
12652
+ showVelocityPointValue(vel, show, label) {
12653
+ if (!this.dashboardMode())
12654
+ return show;
12655
+ if (this.dashShowValues())
12656
+ return true;
12657
+ if (this.activePointKey() === label)
12658
+ return true;
12659
+ const pts = vel.pts;
12660
+ return pts.length > 0 && label === pts[pts.length - 1].label;
12661
+ }
12662
+ showDashWeightLabel(p, list) {
12663
+ if (!this.dashboardMode())
12664
+ return false;
12665
+ if (this.dashShowValues())
12666
+ return true;
12667
+ if (this.activePointKey() === p.log.date)
12668
+ return true;
12669
+ return list.length > 0 && p === list[list.length - 1];
12670
+ }
12671
+ showDashHeightLabel(p, list) {
12672
+ if (!this.dashboardMode())
12673
+ return false;
12674
+ if (this.dashShowValues())
12675
+ return true;
12676
+ if (this.activePointKey() === p.log.date)
12677
+ return true;
12678
+ return list.length > 0 && p === list[list.length - 1];
12679
+ }
12680
+ showWeightPointLabel(p, list) {
12681
+ if (this.dashboardMode())
12682
+ return this.showDashWeightLabel(p, list);
12683
+ if (!this.pinLatestPoint())
12684
+ return p.showKgLabel;
12685
+ return list.length > 0 && p === list[list.length - 1];
12686
+ }
12687
+ showHeightPointLabel(p, list) {
12688
+ if (this.dashboardMode())
12689
+ return this.showDashHeightLabel(p, list);
12690
+ if (!this.pinLatestPoint())
12691
+ return p.showLabel;
12692
+ return list.length > 0 && p === list[list.length - 1];
12693
+ }
12694
+ showTrendPointLabel(show) {
12695
+ return !this.dashboardMode() && show;
12696
+ }
12537
12697
  spanStartX(maxDays, width, left, right) {
12538
12698
  const span = this.spanDays();
12539
12699
  if (span === null || maxDays <= 0)
@@ -12550,23 +12710,15 @@ class WeightChartsComponent {
12550
12710
  const maxDays = cmp.pts.length > 0 ? Math.max(...cmp.pts.map((p) => p.days)) : 0;
12551
12711
  return this.spanStartX(maxDays, cmp.W, cmp.PAD_L, cmp.PAD_R);
12552
12712
  }
12553
- showWeightPointLabel(p, list) {
12554
- if (!this.pinLatestPoint())
12555
- return p.showKgLabel;
12556
- return list.length > 0 && p === list[list.length - 1];
12557
- }
12558
- showHeightPointLabel(p, list) {
12559
- if (!this.pinLatestPoint())
12560
- return p.showLabel;
12561
- return list.length > 0 && p === list[list.length - 1];
12562
- }
12563
12713
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightChartsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12564
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightChartsComponent, isStandalone: true, selector: "app-weight-charts", inputs: { weightVsStandardChart: { classPropertyName: "weightVsStandardChart", publicName: "weightVsStandardChart", isSignal: true, isRequired: false, transformFunction: null }, heightVsStandardChart: { classPropertyName: "heightVsStandardChart", publicName: "heightVsStandardChart", isSignal: true, isRequired: false, transformFunction: null }, weightTrendChart: { classPropertyName: "weightTrendChart", publicName: "weightTrendChart", isSignal: true, isRequired: false, transformFunction: null }, simpleTrendMode: { classPropertyName: "simpleTrendMode", publicName: "simpleTrendMode", isSignal: true, isRequired: false, transformFunction: null }, weightVelocityLineChart: { classPropertyName: "weightVelocityLineChart", publicName: "weightVelocityLineChart", isSignal: true, isRequired: false, transformFunction: null } }, ngImport: i0, template: "<section class=\"wg-controls\">\n <div class=\"wg-controls__group\">\n <span class=\"wg-controls__label\">Span</span>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '30d'\"\n (click)=\"setSpanPreset('30d')\"\n >\n 30 ng\u00E0y\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '90d'\"\n (click)=\"setSpanPreset('90d')\"\n >\n 90 ng\u00E0y\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '180d'\"\n (click)=\"setSpanPreset('180d')\"\n >\n 180 ng\u00E0y\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === 'all'\"\n (click)=\"setSpanPreset('all')\"\n >\n To\u00E0n b\u1ED9\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-pin\"\n [class.active]=\"pinLatestPoint()\"\n (click)=\"togglePinLatest()\"\n >\n <i class=\"pi pi-map-marker\"></i>\n {{ pinLatestPoint() ? 'Pin \u0111i\u1EC3m m\u1EDBi nh\u1EA5t' : 'Hi\u1EC7n nhi\u1EC1u nh\u00E3n' }}\n </button>\n</section>\n\n<!-- C\u00E2n n\u1EB7ng vs WHO -->\n@if (weightVsStandardChart(); as cmp) {\n <section class=\"wg-card wg-card--compare\">\n <header class=\"wg-card__head\">\n <h3><i class=\"pi pi-chart-line\"></i> C\u00E2n n\u1EB7ng so v\u1EDBi chu\u1EA9n</h3>\n <p class=\"wg-meta\">\n <span class=\"wg-pill\">\n <i class=\"pi pi-globe\"></i> {{ cmp.regionCode }} \u00B7 WHO \u00B7 {{ cmp.sexLabelVi }}\n </span>\n <span class=\"wg-pill wg-pill--accent\" [title]=\"cmp.referenceCaption\">\n <i class=\"pi pi-database\"></i>\n <strong>{{ cmp.birthRecordLine }}</strong>\n </span>\n </p>\n </header>\n <div class=\"wg-legend\" aria-hidden=\"false\">\n <span><i class=\"wg-dot wg-dot--violet\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> Trung v\u1ECB</span>\n <span><i class=\"wg-band\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"cmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + cmp.W + ' ' + cmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgWClip\">\n <rect\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n </clipPath>\n <linearGradient id=\"wCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.2\" />\n <stop offset=\"100%\" stop-color=\"#22c55e\" stop-opacity=\"0.05\" />\n </linearGradient>\n <linearGradient id=\"wCmpActualGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#a5b4fc\" />\n <stop offset=\"100%\" stop-color=\"#4f46e5\" />\n </linearGradient>\n <filter id=\"wgWGlow\" x=\"-8%\" y=\"-8%\" width=\"116%\" height=\"116%\">\n <feGaussianBlur stdDeviation=\"1.4\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g clip-path=\"url(#wgWClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n <g class=\"wg-grid\">\n @for (g of cmp.gridLines; track g) {\n <line\n [attr.x1]=\"cmp.PAD_L\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of cmp.gridLines; track g) {\n <text\n [attr.x]=\"cmp.PAD_L - 10\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band\" [attr.d]=\"cmp.bandPath\" fill=\"url(#wCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"cmp.pathMedian\" fill=\"none\" />\n <path\n class=\"wg-actual\"\n [attr.d]=\"cmp.pathActual\"\n stroke=\"url(#wCmpActualGrad)\"\n filter=\"url(#wgWGlow)\"\n />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"cmp.PAD_L - 2\"\n [attr.y1]=\"cmp.bottomY\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 6\"\n [attr.y2]=\"cmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of cmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"cmp.H - cmp.PAD_B\"\n [attr.y2]=\"cmp.H - cmp.PAD_B + 5\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 22\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n @if (t.subLabel) {\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 8\" text-anchor=\"middle\" class=\"wg-x-sub\">\n {{ t.subLabel }}\n </text>\n }\n </g>\n }\n </g>\n @for (p of cmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"7\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"3.5\" class=\"wg-node-core\" />\n @if (showWeightPointLabel(p, cmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 14\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"cmp.H - 48\"\n [attr.transform]=\"'rotate(-34 ' + p.x + ',' + (cmp.H - 48) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ p.ageLabel }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n\n<!-- Chi\u1EC1u cao vs WHO -->\n@if (heightVsStandardChart(); as hCmp) {\n <section class=\"wg-card wg-card--compare\">\n <header class=\"wg-card__head\">\n <h3><i class=\"pi pi-arrows-v\"></i> Chi\u1EC1u cao so v\u1EDBi chu\u1EA9n</h3>\n <p class=\"wg-meta\">\n <span class=\"wg-pill\">\n <i class=\"pi pi-globe\"></i> {{ hCmp.regionCode }} \u00B7 WHO \u00B7 {{ hCmp.sexLabelVi }}\n </span>\n </p>\n </header>\n <div class=\"wg-legend\" aria-hidden=\"false\">\n <span><i class=\"wg-dot wg-dot--cyan\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> Trung v\u1ECB</span>\n <span><i class=\"wg-band wg-band--cyan\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"hCmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + hCmp.W + ' ' + hCmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgHClip\">\n <rect\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n </clipPath>\n <linearGradient id=\"hCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#06b6d4\" stop-opacity=\"0.22\" />\n <stop offset=\"100%\" stop-color=\"#06b6d4\" stop-opacity=\"0.06\" />\n </linearGradient>\n <linearGradient id=\"hCmpActualGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#67e8f9\" />\n <stop offset=\"100%\" stop-color=\"#0284c7\" />\n </linearGradient>\n <filter id=\"wgHGlow\" x=\"-8%\" y=\"-8%\" width=\"116%\" height=\"116%\">\n <feGaussianBlur stdDeviation=\"1.4\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g clip-path=\"url(#wgHClip)\">\n <rect\n class=\"wg-plot-fill wg-plot-fill--cyan\"\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n <g class=\"wg-grid\">\n @for (g of hCmp.gridLines; track g) {\n <line\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of hCmp.gridLines; track g) {\n <text\n [attr.x]=\"hCmp.PAD_L - 10\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band wg-band--cyan\" [attr.d]=\"hCmp.bandPath\" fill=\"url(#hCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"hCmp.pathMedian\" fill=\"none\" />\n <path\n class=\"wg-actual wg-actual--height\"\n [attr.d]=\"hCmp.pathActual\"\n stroke=\"url(#hCmpActualGrad)\"\n filter=\"url(#wgHGlow)\"\n />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"hCmp.PAD_L - 2\"\n [attr.y1]=\"hCmp.bottomY\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 6\"\n [attr.y2]=\"hCmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of hCmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"hCmp.H - hCmp.PAD_B\"\n [attr.y2]=\"hCmp.H - hCmp.PAD_B + 5\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"hCmp.H - 20\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n </g>\n }\n </g>\n @for (p of hCmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"7\" class=\"wg-node wg-node--cyan\" />\n <circle\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"3.5\"\n class=\"wg-node-core wg-node-core--cyan\"\n />\n @if (showHeightPointLabel(p, hCmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 14\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatCm(p.heightCm) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"hCmp.H - 48\"\n [attr.transform]=\"'rotate(-34 ' + p.x + ',' + (hCmp.H - 48) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ p.ageLabel }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n\n<!-- Xu h\u01B0\u1EDBng \u0111\u01A1n (kh\u00F4ng c\u00F3 birth + WHO compare) -->\n@if (simpleTrendMode() && weightTrendChart(); as ch) {\n <section class=\"wg-card\">\n <header class=\"wg-card__head wg-card__head--solo\">\n <h3><i class=\"pi pi-chart-line\"></i> Xu h\u01B0\u1EDBng c\u00E2n n\u1EB7ng</h3>\n <p class=\"wg-card__hint\">Theo t\u1EEBng l\u1EA7n c\u00E2n tr\u00EAn sheet</p>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ch.W\">\n <svg\n class=\"wg-svg\"\n [attr.viewBox]=\"'0 0 ' + ch.W + ' ' + ch.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgTrendClip\">\n <rect\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n </clipPath>\n <linearGradient id=\"wAreaGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#6366f1\" stop-opacity=\"0.28\" />\n <stop offset=\"100%\" stop-color=\"#6366f1\" stop-opacity=\"0.04\" />\n </linearGradient>\n <linearGradient id=\"wLineGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#a5b4fc\" />\n <stop offset=\"100%\" stop-color=\"#4338ca\" />\n </linearGradient>\n <filter id=\"wgTrendGlow\" x=\"-8%\" y=\"-8%\" width=\"116%\" height=\"116%\">\n <feGaussianBlur stdDeviation=\"1.2\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g clip-path=\"url(#wgTrendClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n <g class=\"wg-grid\">\n @for (g of ch.gridLines; track g) {\n <line\n [attr.x1]=\"ch.PAD_L\"\n [attr.x2]=\"ch.W - ch.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of ch.gridLines; track g) {\n <text\n [attr.x]=\"ch.PAD_L - 10\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-area\" [attr.d]=\"ch.areaPath\" fill=\"url(#wAreaGrad)\" />\n <path\n class=\"wg-actual\"\n [attr.d]=\"ch.pathActual\"\n stroke=\"url(#wLineGrad)\"\n filter=\"url(#wgTrendGlow)\"\n />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ch.PAD_L - 2\"\n [attr.y1]=\"ch.bottomY\"\n [attr.x2]=\"ch.W - ch.PAD_R + 6\"\n [attr.y2]=\"ch.bottomY\"\n />\n @for (p of ch.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"7\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"3.5\" class=\"wg-node-core\" />\n @if (p.showKgLabel) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 14\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"ch.H - 40\"\n [attr.transform]=\"'rotate(-36 ' + p.x + ',' + (ch.H - 40) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ formatDateShort(p.log.date) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n\n<!-- T\u1ED1c \u0111\u1ED9 -->\n@if (weightVelocityLineChart(); as ln) {\n <section class=\"wg-suite\">\n <header class=\"wg-suite__head\">\n <h3><i class=\"pi pi-bolt\"></i> T\u1ED1c \u0111\u1ED9 t\u0103ng c\u00E2n</h3>\n <p class=\"wg-suite__sub\">\u01AF\u1EDBc l\u01B0\u1EE3ng g/ng\u00E0y gi\u1EEFa hai l\u1EA7n c\u00E2n li\u00EAn ti\u1EBFp</p>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ln.W\">\n <svg\n class=\"wg-svg wg-svg--velocity\"\n [attr.viewBox]=\"'0 0 ' + ln.W + ' ' + ln.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <linearGradient id=\"wVelLineGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#34d399\" />\n <stop offset=\"100%\" stop-color=\"#0d9488\" />\n </linearGradient>\n <filter id=\"wgVelGlow\" x=\"-6%\" y=\"-6%\" width=\"112%\" height=\"112%\">\n <feGaussianBlur stdDeviation=\"1\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g class=\"wg-grid\">\n @for (gl of ln.gridLines; track gl) {\n <line\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y1]=\"gl.y\"\n [attr.y2]=\"gl.y\"\n />\n }\n @for (gl of ln.gridLines; track gl) {\n <text\n [attr.x]=\"ln.PAD_L - 6\"\n [attr.y]=\"gl.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ gl.label }}\n </text>\n }\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ln.PAD_L - 2\"\n [attr.y1]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y2]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n />\n @if (ln.showZeroLine) {\n <line\n class=\"wg-zero\"\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW\"\n [attr.y1]=\"ln.zeroY\"\n [attr.y2]=\"ln.zeroY\"\n />\n }\n <path\n class=\"wg-velocity\"\n [attr.d]=\"ln.pathLine\"\n fill=\"none\"\n stroke=\"url(#wVelLineGrad)\"\n filter=\"url(#wgVelGlow)\"\n />\n @for (p of ln.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"6\" class=\"wg-node wg-node--vel\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"3\" class=\"wg-node-core wg-node-core--vel\" />\n @if (p.showValue) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 12\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatGPerDay(p.gPerDay) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"ln.H - 36\"\n [attr.transform]=\"'rotate(-36 ' + p.x + ',' + (ln.H - 36) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ p.label }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:10px}.wg-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:8px 10px}.wg-controls__group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px}.wg-controls__label{font-size:11px;font-weight:800;color:#64748b;text-transform:uppercase;letter-spacing:.04em;margin-right:2px}.wg-chip{border:1px solid #cbd5e1;background:#f8fafc;color:#334155;border-radius:999px;padding:4px 10px;font-size:11.5px;font-weight:700;cursor:pointer;transition:all .12s ease}.wg-chip.active{background:#4f46e5;border-color:#4f46e5;color:#fff}.wg-pin{display:inline-flex;align-items:center;gap:5px;border:1px solid #cbd5e1;background:#fff;color:#475569;border-radius:999px;padding:5px 11px;font-size:11.5px;font-weight:700;cursor:pointer}.wg-pin i{font-size:10px}.wg-pin.active{border-color:#10b981;color:#047857;background:#ecfdf5}.wg-card,.wg-suite{background:var(--surface, #fff);border:1px solid color-mix(in srgb,var(--border, #e6e8ef) 88%,var(--primary, #5b6cff));border-radius:14px;padding:12px 14px 10px;box-shadow:0 1px 10px -6px #0f172a1a}.wg-card__head{margin-bottom:8px}.wg-card__head h3{margin:0 0 6px;font-size:15px;font-weight:800;letter-spacing:-.02em;display:flex;align-items:center;gap:8px;color:var(--text, #0f172a)}.wg-card__head h3 i{color:var(--primary, #5b6cff)}.wg-card__head--solo .wg-card__hint{margin:0;font-size:13px;color:var(--muted, #64748b)}.wg-meta{display:flex;flex-wrap:wrap;gap:8px;margin:0}.wg-pill{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:600;background:var(--surface-2, #f1f5f9);color:var(--text)}.wg-pill i{opacity:.85;font-size:12px}.wg-pill--accent{flex:1 1 200px;min-width:0;background:linear-gradient(120deg,color-mix(in srgb,var(--primary, #5b6cff) 10%,#fff),#f8fafc);border:1px solid color-mix(in srgb,var(--primary, #5b6cff) 22%,var(--border, #e6e8ef))}.wg-pill--accent strong{font-weight:700;word-break:break-word}.wg-legend{display:flex;flex-wrap:wrap;gap:10px 18px;margin-bottom:10px;font-size:12px;font-weight:600;color:var(--muted, #64748b)}.wg-legend span{display:inline-flex;align-items:center;gap:8px}.wg-dot{width:11px;height:11px;border-radius:50%;flex-shrink:0;box-shadow:0 0 0 2px #fff}.wg-dot--violet{background:linear-gradient(145deg,#818cf8,#4f46e5)}.wg-dot--cyan{background:linear-gradient(145deg,#22d3ee,#0284c7)}.wg-line{width:22px;height:3px;border-radius:2px;flex-shrink:0}.wg-line--median{background:#16a34a}.wg-band{width:22px;height:12px;border-radius:4px;background:linear-gradient(180deg,#22c55e59,#22c55e14);border:1px solid rgba(34,197,94,.45)}.wg-band--cyan{background:linear-gradient(180deg,#06b6d461,#06b6d41a);border-color:#06b6d473}.wg-scroll{--wg-min-x: 0px;overflow-x:auto;overflow-y:hidden;margin:0 -4px;padding:4px 4px 8px;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}.wg-scroll::-webkit-scrollbar{height:7px}.wg-scroll::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:6px}.wg-svg{display:block;width:max(100%,var(--wg-min-x, 0px));max-width:none;height:auto;flex-shrink:0}.wg-svg .wg-plot-fill{fill:#f8faff}.wg-svg .wg-plot-fill--cyan{fill:#f0fdfa}.wg-svg .wg-grid line{stroke:#e8edf5;stroke-width:1}.wg-svg .wg-grid-label{font-size:11px;font-weight:600;fill:var(--muted, #64748b)}.wg-svg .wg-axis{stroke:#cbd5e1;stroke-width:1.35}.wg-svg .wg-x-tick{stroke:#cbd5e1;stroke-width:1}.wg-svg .wg-x-main{font-size:10.5px;font-weight:700;fill:var(--text, #0f172a)}.wg-svg .wg-x-sub{font-size:8.5px;fill:var(--muted-2, #94a3b8)}.wg-svg .wg-band{stroke:none}.wg-svg .wg-ref{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 4;opacity:.95}.wg-svg .wg-median{stroke:#16a34a;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;opacity:.95}.wg-svg .wg-actual{fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-actual--height{stroke-width:3.1}.wg-svg .wg-area{stroke:none}.wg-svg .wg-node{fill:#fff;stroke:#6366f1;stroke-width:2.2}.wg-svg .wg-node--cyan{stroke:#0ea5e9}.wg-svg .wg-node--vel{stroke:#0f766e;stroke-width:2}.wg-svg .wg-node-core{fill:#4f46e5;stroke:none}.wg-svg .wg-node-core--cyan{fill:#0284c7}.wg-svg .wg-node-core--vel{fill:#0d9488}.wg-svg .wg-value-label{font-size:10.5px;font-weight:800;fill:var(--text, #0f172a)}.wg-svg .wg-age-label{font-size:9.5px;font-weight:600;fill:#475569}.wg-svg .wg-zero{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 3}.wg-svg .wg-velocity{stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}.wg-suite__head{margin-bottom:10px}.wg-suite__head h3{margin:0 0 4px;font-size:15px;font-weight:800;display:flex;align-items:center;gap:8px;color:var(--text)}.wg-suite__head h3 i{color:#0d9488}.wg-suite__sub{margin:0;font-size:12.5px;color:var(--muted);line-height:1.45}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12714
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightChartsComponent, isStandalone: true, selector: "app-weight-charts", inputs: { weightVsStandardChart: { classPropertyName: "weightVsStandardChart", publicName: "weightVsStandardChart", isSignal: true, isRequired: false, transformFunction: null }, heightVsStandardChart: { classPropertyName: "heightVsStandardChart", publicName: "heightVsStandardChart", isSignal: true, isRequired: false, transformFunction: null }, weightTrendChart: { classPropertyName: "weightTrendChart", publicName: "weightTrendChart", isSignal: true, isRequired: false, transformFunction: null }, simpleTrendMode: { classPropertyName: "simpleTrendMode", publicName: "simpleTrendMode", isSignal: true, isRequired: false, transformFunction: null }, weightVelocityLineChart: { classPropertyName: "weightVelocityLineChart", publicName: "weightVelocityLineChart", isSignal: true, isRequired: false, transformFunction: null }, heightVelocityLineChart: { classPropertyName: "heightVelocityLineChart", publicName: "heightVelocityLineChart", isSignal: true, isRequired: false, transformFunction: null }, dashboardMode: { classPropertyName: "dashboardMode", publicName: "dashboardMode", isSignal: true, isRequired: false, transformFunction: null }, comparePeriod: { classPropertyName: "comparePeriod", publicName: "comparePeriod", isSignal: true, isRequired: false, transformFunction: null }, growthMetric: { classPropertyName: "growthMetric", publicName: "growthMetric", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class.wg-dashboard": "dashboardMode()" } }, ngImport: i0, template: "@if (showControls()) {\n <section class=\"wg-controls\">\n <div class=\"wg-controls__group\" role=\"group\" aria-label=\"Kho\u1EA3ng th\u1EDDi gian\">\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '30d'\"\n (click)=\"setSpanPreset('30d')\"\n >\n 30d\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '90d'\"\n (click)=\"setSpanPreset('90d')\"\n >\n 90d\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '180d'\"\n (click)=\"setSpanPreset('180d')\"\n >\n 180d\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === 'all'\"\n (click)=\"setSpanPreset('all')\"\n >\n All\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-pin\"\n [class.active]=\"pinLatestPoint()\"\n (click)=\"togglePinLatest()\"\n [attr.aria-label]=\"pinLatestPoint() ? 'Ch\u1EC9 hi\u1EC7n \u0111i\u1EC3m m\u1EDBi nh\u1EA5t' : 'Hi\u1EC7n nhi\u1EC1u nh\u00E3n'\"\n [title]=\"pinLatestPoint() ? 'Ch\u1EC9 \u0111i\u1EC3m m\u1EDBi nh\u1EA5t' : 'Nhi\u1EC1u nh\u00E3n'\"\n >\n <i class=\"pi\" [ngClass]=\"pinLatestPoint() ? 'pi-map-marker' : 'pi-tags'\"></i>\n </button>\n </section>\n}\n\n@if (dashboardMode()) {\n @if (activeVelocityChart(); as vel) {\n <section class=\"wg-section\" aria-label=\"Bi\u1EC3u \u0111\u1ED3 t\u1ED1c \u0111\u1ED9\">\n <header class=\"wg-section__head\">\n <h3 class=\"wg-section__title\">{{ velocitySectionTitle() }}</h3>\n <div class=\"wg-chart-toolbar\">\n <div class=\"wg-chart-toolbar__zoom\" role=\"group\" aria-label=\"Thu ph\u00F3ng\">\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomOut('velocity')\"\n [disabled]=\"velocityZoom() <= minZoom\"\n aria-label=\"Thu nh\u1ECF\"\n >\n \u2212\n </button>\n <span class=\"wg-zoom-level\">{{ zoomPercent(velocityZoom()) }}%</span>\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomIn('velocity')\"\n [disabled]=\"velocityZoom() >= maxZoom\"\n aria-label=\"Ph\u00F3ng to\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"wg-zoom-btn wg-zoom-btn--reset\"\n (click)=\"resetZoom('velocity')\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i thu ph\u00F3ng\"\n >\n \u21BA\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-values-btn\"\n [class.active]=\"dashShowValues()\"\n (click)=\"toggleDashShowValues()\"\n [attr.aria-label]=\"dashShowValues() ? '\u1EA8n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3' : 'Hi\u1EC7n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3'\"\n [title]=\"dashShowValues() ? '\u1EA8n s\u1ED1' : 'Hi\u1EC7n s\u1ED1'\"\n >\n <i class=\"pi\" [ngClass]=\"dashShowValues() ? 'pi-eye' : 'pi-eye-slash'\"></i>\n </button>\n </div>\n </header>\n <div class=\"wg-panel\">\n <div class=\"wg-panel__legend\">\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--vel\" aria-hidden=\"true\"></i>\n {{ vel.seriesLabel }}\n </span>\n <span class=\"wg-panel__ref\">TB {{ vel.refValue }}{{ vel.refSuffix }}</span>\n </div>\n <div\n class=\"wg-chart-stage wg-chart-stage--vel\"\n (wheel)=\"onChartWheel($event, 'velocity')\"\n >\n <div\n class=\"wg-scroll wg-scroll--interactive\"\n (pointerdown)=\"onPanPointerDown($event, $any($event.currentTarget))\"\n (pointermove)=\"onPanPointerMove($event)\"\n (pointerup)=\"onPanPointerUp($event)\"\n (pointercancel)=\"onPanPointerUp($event)\"\n >\n <svg\n class=\"wg-svg wg-svg--velocity-line\"\n [attr.viewBox]=\"'0 0 ' + vel.W + ' ' + vel.H\"\n preserveAspectRatio=\"xMinYMid meet\"\n [style.width.px]=\"chartScaledWidth(vel.W, velocityZoom())\"\n [style.height.px]=\"chartScaledHeight(vel.H, velocityZoom())\"\n role=\"img\"\n [attr.aria-label]=\"vel.caption\"\n >\n <g class=\"wg-grid\">\n @for (gl of vel.gridLines; track gl) {\n <line\n [attr.x1]=\"vel.PAD_L\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y1]=\"gl.y\"\n [attr.y2]=\"gl.y\"\n />\n }\n @for (gl of vel.gridLines; track gl) {\n <text\n [attr.x]=\"vel.PAD_L - 5\"\n [attr.y]=\"gl.y + 3\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ gl.label }}\n </text>\n }\n </g>\n <line\n class=\"wg-ref-line\"\n [attr.x1]=\"vel.PAD_L\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y1]=\"vel.refY\"\n [attr.y2]=\"vel.refY\"\n />\n @if (vel.showZeroLine) {\n <line\n class=\"wg-zero\"\n [attr.x1]=\"vel.PAD_L\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y1]=\"vel.zeroY\"\n [attr.y2]=\"vel.zeroY\"\n />\n }\n <path class=\"wg-velocity\" [attr.d]=\"vel.pathLine\" fill=\"none\" />\n @for (p of vel.pts; track p.label) {\n <g class=\"wg-point\" (click)=\"selectPoint(p.label)\">\n <circle\n class=\"wg-node-hit\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"14\"\n />\n <circle\n class=\"wg-node wg-node--vel\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n [class.wg-node--active]=\"activePointKey() === p.label\"\n />\n <circle\n class=\"wg-node-core wg-node-core--vel\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"2.5\"\n />\n @if (showVelocityPointValue(vel, p.showValue, p.label)) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"p.y - 10\"\n text-anchor=\"middle\"\n class=\"wg-value-label\"\n >\n {{ formatVelocityValue(vel, p.gPerDay) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"vel.H - 10\"\n text-anchor=\"middle\"\n class=\"wg-x-label\"\n >\n {{ p.label }}\n </text>\n }\n </g>\n }\n <line\n class=\"wg-axis\"\n [attr.x1]=\"vel.PAD_L\"\n [attr.y1]=\"vel.bottomY\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y2]=\"vel.bottomY\"\n />\n </svg>\n </div>\n </div>\n </div>\n <p class=\"wg-caption\">{{ velocityCaption() }}</p>\n </section>\n }\n\n @if (activeWeightCompare(); as cmp) {\n <section class=\"wg-section\" aria-label=\"Bi\u1EC3u \u0111\u1ED3 so chu\u1EA9n c\u00E2n n\u1EB7ng\">\n <header class=\"wg-section__head\">\n <h3 class=\"wg-section__title\">{{ compareSectionTitle() }}</h3>\n <div class=\"wg-chart-toolbar\">\n <div class=\"wg-chart-toolbar__zoom\" role=\"group\" aria-label=\"Thu ph\u00F3ng\">\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomOut('compare')\"\n [disabled]=\"compareZoom() <= minZoom\"\n aria-label=\"Thu nh\u1ECF\"\n >\n \u2212\n </button>\n <span class=\"wg-zoom-level\">{{ zoomPercent(compareZoom()) }}%</span>\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomIn('compare')\"\n [disabled]=\"compareZoom() >= maxZoom\"\n aria-label=\"Ph\u00F3ng to\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"wg-zoom-btn wg-zoom-btn--reset\"\n (click)=\"resetZoom('compare')\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i thu ph\u00F3ng\"\n >\n \u21BA\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-values-btn\"\n [class.active]=\"dashShowValues()\"\n (click)=\"toggleDashShowValues()\"\n [attr.aria-label]=\"dashShowValues() ? '\u1EA8n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3' : 'Hi\u1EC7n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3'\"\n [title]=\"dashShowValues() ? '\u1EA8n s\u1ED1' : 'Hi\u1EC7n s\u1ED1'\"\n >\n <i class=\"pi\" [ngClass]=\"dashShowValues() ? 'pi-eye' : 'pi-eye-slash'\"></i>\n </button>\n </div>\n </header>\n <div class=\"wg-panel\">\n <div class=\"wg-panel__legend\">\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--baby\" aria-hidden=\"true\"></i>\n B\u00E9\n </span>\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--ref\" aria-hidden=\"true\"></i>\n Chu\u1EA9n (trung v\u1ECB)\n </span>\n </div>\n <div class=\"wg-chart-stage wg-chart-stage--compare\" (wheel)=\"onChartWheel($event, 'compare')\">\n <div\n class=\"wg-scroll wg-scroll--interactive\"\n (pointerdown)=\"onPanPointerDown($event, $any($event.currentTarget))\"\n (pointermove)=\"onPanPointerMove($event)\"\n (pointerup)=\"onPanPointerUp($event)\"\n (pointercancel)=\"onPanPointerUp($event)\"\n >\n <svg\n class=\"wg-svg wg-svg--compare-dash\"\n [attr.viewBox]=\"'0 0 ' + cmp.W + ' ' + cmp.H\"\n preserveAspectRatio=\"xMinYMid meet\"\n [style.width.px]=\"chartScaledWidth(cmp.W, compareZoom())\"\n [style.height.px]=\"chartScaledHeight(cmp.H, compareZoom())\"\n role=\"img\"\n [attr.aria-label]=\"compareCaption()\"\n >\n <g class=\"wg-grid\">\n @for (g of cmp.gridLines; track g) {\n <line\n [attr.x1]=\"cmp.PAD_L\"\n [attr.x2]=\"cmp.W - cmp.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of cmp.gridLines; track g) {\n <text\n [attr.x]=\"cmp.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-median wg-median--dash\" [attr.d]=\"cmp.pathMedian\" fill=\"none\" />\n <path class=\"wg-actual wg-actual--dash\" [attr.d]=\"cmp.pathActual\" />\n @for (p of cmp.pts; track p.log.date) {\n <g class=\"wg-point\" (click)=\"selectPoint(p.log.date)\">\n <circle class=\"wg-node-hit\" [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"14\" />\n <circle\n class=\"wg-node wg-node--dash\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n [class.wg-node--active]=\"activePointKey() === p.log.date\"\n />\n @if (showDashWeightLabel(p, cmp.pts)) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"p.y - 10\"\n text-anchor=\"middle\"\n class=\"wg-value-label\"\n >\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n </g>\n }\n <line\n class=\"wg-axis\"\n [attr.x1]=\"cmp.PAD_L\"\n [attr.y1]=\"cmp.bottomY\"\n [attr.x2]=\"cmp.W - cmp.PAD_R\"\n [attr.y2]=\"cmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of cmp.xTicks; track t.label) {\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 12\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n }\n </g>\n </svg>\n </div>\n </div>\n @if (cmp.latestSummary; as sum) {\n <div class=\"wg-latest\">\n <span class=\"wg-latest__label\">M\u1EDBi nh\u1EA5t \u00B7 {{ sum.ageLabel }}</span>\n <span class=\"wg-latest__values\">\n <span class=\"wg-latest__baby\">B\u00E9 {{ formatSummaryValue(sum) }} {{ sum.unit }}</span>\n <span class=\"wg-latest__ref\">Chu\u1EA9n {{ formatSummaryRef(sum) }}</span>\n </span>\n </div>\n }\n </div>\n <p class=\"wg-caption\">{{ compareCaption() }}</p>\n </section>\n }\n\n @if (activeHeightCompare(); as hCmp) {\n <section class=\"wg-section\" aria-label=\"Bi\u1EC3u \u0111\u1ED3 so chu\u1EA9n chi\u1EC1u cao\">\n <header class=\"wg-section__head\">\n <h3 class=\"wg-section__title\">{{ compareSectionTitle() }}</h3>\n <div class=\"wg-chart-toolbar\">\n <div class=\"wg-chart-toolbar__zoom\" role=\"group\" aria-label=\"Thu ph\u00F3ng\">\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomOut('compare')\"\n [disabled]=\"compareZoom() <= minZoom\"\n aria-label=\"Thu nh\u1ECF\"\n >\n \u2212\n </button>\n <span class=\"wg-zoom-level\">{{ zoomPercent(compareZoom()) }}%</span>\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomIn('compare')\"\n [disabled]=\"compareZoom() >= maxZoom\"\n aria-label=\"Ph\u00F3ng to\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"wg-zoom-btn wg-zoom-btn--reset\"\n (click)=\"resetZoom('compare')\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i thu ph\u00F3ng\"\n >\n \u21BA\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-values-btn\"\n [class.active]=\"dashShowValues()\"\n (click)=\"toggleDashShowValues()\"\n [attr.aria-label]=\"dashShowValues() ? '\u1EA8n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3' : 'Hi\u1EC7n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3'\"\n [title]=\"dashShowValues() ? '\u1EA8n s\u1ED1' : 'Hi\u1EC7n s\u1ED1'\"\n >\n <i class=\"pi\" [ngClass]=\"dashShowValues() ? 'pi-eye' : 'pi-eye-slash'\"></i>\n </button>\n </div>\n </header>\n <div class=\"wg-panel\">\n <div class=\"wg-panel__legend\">\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--baby\" aria-hidden=\"true\"></i>\n B\u00E9\n </span>\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--ref\" aria-hidden=\"true\"></i>\n Chu\u1EA9n (trung v\u1ECB)\n </span>\n </div>\n <div class=\"wg-chart-stage wg-chart-stage--compare\" (wheel)=\"onChartWheel($event, 'compare')\">\n <div\n class=\"wg-scroll wg-scroll--interactive\"\n (pointerdown)=\"onPanPointerDown($event, $any($event.currentTarget))\"\n (pointermove)=\"onPanPointerMove($event)\"\n (pointerup)=\"onPanPointerUp($event)\"\n (pointercancel)=\"onPanPointerUp($event)\"\n >\n <svg\n class=\"wg-svg wg-svg--compare-dash\"\n [attr.viewBox]=\"'0 0 ' + hCmp.W + ' ' + hCmp.H\"\n preserveAspectRatio=\"xMinYMid meet\"\n [style.width.px]=\"chartScaledWidth(hCmp.W, compareZoom())\"\n [style.height.px]=\"chartScaledHeight(hCmp.H, compareZoom())\"\n role=\"img\"\n [attr.aria-label]=\"compareCaption()\"\n >\n <g class=\"wg-grid\">\n @for (g of hCmp.gridLines; track g) {\n <line\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of hCmp.gridLines; track g) {\n <text\n [attr.x]=\"hCmp.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-median wg-median--dash\" [attr.d]=\"hCmp.pathMedian\" fill=\"none\" />\n <path\n class=\"wg-actual wg-actual--dash wg-actual--height\"\n [attr.d]=\"hCmp.pathActual\"\n />\n @for (p of hCmp.pts; track p.log.date) {\n <g class=\"wg-point\" (click)=\"selectPoint(p.log.date)\">\n <circle class=\"wg-node-hit\" [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"14\" />\n <circle\n class=\"wg-node wg-node--dash wg-node--cyan\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n [class.wg-node--active]=\"activePointKey() === p.log.date\"\n />\n @if (showDashHeightLabel(p, hCmp.pts)) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"p.y - 10\"\n text-anchor=\"middle\"\n class=\"wg-value-label\"\n >\n {{ formatCm(p.heightCm) }}\n </text>\n }\n </g>\n }\n <line\n class=\"wg-axis\"\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.y1]=\"hCmp.bottomY\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R\"\n [attr.y2]=\"hCmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of hCmp.xTicks; track t.label) {\n <text [attr.x]=\"t.x\" [attr.y]=\"hCmp.H - 12\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n }\n </g>\n </svg>\n </div>\n </div>\n @if (hCmp.latestSummary; as sum) {\n <div class=\"wg-latest\">\n <span class=\"wg-latest__label\">M\u1EDBi nh\u1EA5t \u00B7 {{ sum.ageLabel }}</span>\n <span class=\"wg-latest__values\">\n <span class=\"wg-latest__baby\">B\u00E9 {{ formatSummaryValue(sum) }} {{ sum.unit }}</span>\n <span class=\"wg-latest__ref\">Chu\u1EA9n {{ formatSummaryRef(sum) }}</span>\n </span>\n </div>\n }\n </div>\n <p class=\"wg-caption\">{{ compareCaption() }}</p>\n </section>\n }\n} @else {\n @if (weightVelocityLineChart(); as ln) {\n <section class=\"wg-block wg-block--vel\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n T\u1ED1c \u0111\u1ED9 t\u0103ng\n </h3>\n <span class=\"wg-block__unit\">g/ng\u00E0y</span>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ln.W\">\n <svg\n class=\"wg-svg wg-svg--velocity\"\n [attr.viewBox]=\"'0 0 ' + ln.W + ' ' + ln.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <g class=\"wg-grid\">\n @for (gl of ln.gridLines; track gl) {\n <line\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y1]=\"gl.y\"\n [attr.y2]=\"gl.y\"\n />\n }\n @for (gl of ln.gridLines; track gl) {\n <text\n [attr.x]=\"ln.PAD_L - 6\"\n [attr.y]=\"gl.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ gl.label }}\n </text>\n }\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ln.PAD_L - 2\"\n [attr.y1]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y2]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n />\n @if (ln.showZeroLine) {\n <line\n class=\"wg-zero\"\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW\"\n [attr.y1]=\"ln.zeroY\"\n [attr.y2]=\"ln.zeroY\"\n />\n }\n <path class=\"wg-velocity\" [attr.d]=\"ln.pathLine\" fill=\"none\" />\n @for (p of ln.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node wg-node--vel\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core wg-node-core--vel\" />\n @if (showVelocityPointValue(ln, p.showValue, p.label)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatGPerDay(p.gPerDay) }}\n </text>\n }\n @if (p.showXLabel) {\n <text [attr.x]=\"p.x\" [attr.y]=\"ln.H - 28\" text-anchor=\"middle\" class=\"wg-age-label\">\n {{ p.label }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n\n @if (showWeightCompare() && weightVsStandardChart(); as cmp) {\n <section class=\"wg-block wg-block--compare\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-chart-bar\" aria-hidden=\"true\"></i>\n C\u00E2n \u00B7 chu\u1EA9n\n </h3>\n <span class=\"wg-block__unit\">kg</span>\n </header>\n <div class=\"wg-legend wg-legend--mini\" aria-hidden=\"true\">\n <span><i class=\"wg-dot wg-dot--violet\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> P50</span>\n <span><i class=\"wg-band\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"cmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + cmp.W + ' ' + cmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgWClip\">\n <rect\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n </clipPath>\n <linearGradient id=\"wCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.18\" />\n <stop offset=\"100%\" stop-color=\"#22c55e\" stop-opacity=\"0.04\" />\n </linearGradient>\n </defs>\n <g clip-path=\"url(#wgWClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n <g class=\"wg-grid\">\n @for (g of cmp.gridLines; track g) {\n <line\n [attr.x1]=\"cmp.PAD_L\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of cmp.gridLines; track g) {\n <text\n [attr.x]=\"cmp.PAD_L - 8\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band\" [attr.d]=\"cmp.bandPath\" fill=\"url(#wCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"cmp.pathMedian\" fill=\"none\" />\n <path class=\"wg-actual\" [attr.d]=\"cmp.pathActual\" />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"cmp.PAD_L - 2\"\n [attr.y1]=\"cmp.bottomY\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 6\"\n [attr.y2]=\"cmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of cmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"cmp.H - cmp.PAD_B\"\n [attr.y2]=\"cmp.H - cmp.PAD_B + 4\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 10\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n </g>\n }\n </g>\n @for (p of cmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core\" />\n @if (showWeightPointLabel(p, cmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n\n @if (showHeightCompare() && heightVsStandardChart(); as hCmp) {\n <section class=\"wg-block wg-block--compare\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n Cao \u00B7 chu\u1EA9n\n </h3>\n <span class=\"wg-block__unit\">cm</span>\n </header>\n <div class=\"wg-legend wg-legend--mini\" aria-hidden=\"true\">\n <span><i class=\"wg-dot wg-dot--cyan\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> P50</span>\n <span><i class=\"wg-band wg-band--cyan\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"hCmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + hCmp.W + ' ' + hCmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgHClip\">\n <rect\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n </clipPath>\n <linearGradient id=\"hCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#06b6d4\" stop-opacity=\"0.18\" />\n <stop offset=\"100%\" stop-color=\"#06b6d4\" stop-opacity=\"0.04\" />\n </linearGradient>\n </defs>\n <g clip-path=\"url(#wgHClip)\">\n <rect\n class=\"wg-plot-fill wg-plot-fill--cyan\"\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n <g class=\"wg-grid\">\n @for (g of hCmp.gridLines; track g) {\n <line\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of hCmp.gridLines; track g) {\n <text\n [attr.x]=\"hCmp.PAD_L - 8\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band wg-band--cyan\" [attr.d]=\"hCmp.bandPath\" fill=\"url(#hCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"hCmp.pathMedian\" fill=\"none\" />\n <path class=\"wg-actual wg-actual--height\" [attr.d]=\"hCmp.pathActual\" />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"hCmp.PAD_L - 2\"\n [attr.y1]=\"hCmp.bottomY\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 6\"\n [attr.y2]=\"hCmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of hCmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"hCmp.H - hCmp.PAD_B\"\n [attr.y2]=\"hCmp.H - hCmp.PAD_B + 4\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"hCmp.H - 10\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n </g>\n }\n </g>\n @for (p of hCmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node wg-node--cyan\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core wg-node-core--cyan\" />\n @if (showHeightPointLabel(p, hCmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatCm(p.heightCm) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n\n @if (showSimpleTrend() && weightTrendChart(); as ch) {\n <section class=\"wg-block\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-chart-line\" aria-hidden=\"true\"></i>\n Xu h\u01B0\u1EDBng\n </h3>\n <span class=\"wg-block__unit\">kg</span>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ch.W\">\n <svg\n class=\"wg-svg\"\n [attr.viewBox]=\"'0 0 ' + ch.W + ' ' + ch.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgTrendClip\">\n <rect\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n </clipPath>\n <linearGradient id=\"wAreaGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#6366f1\" stop-opacity=\"0.2\" />\n <stop offset=\"100%\" stop-color=\"#6366f1\" stop-opacity=\"0.03\" />\n </linearGradient>\n </defs>\n <g clip-path=\"url(#wgTrendClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n <g class=\"wg-grid\">\n @for (g of ch.gridLines; track g) {\n <line\n [attr.x1]=\"ch.PAD_L\"\n [attr.x2]=\"ch.W - ch.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of ch.gridLines; track g) {\n <text\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-area\" [attr.d]=\"ch.areaPath\" fill=\"url(#wAreaGrad)\" />\n <path class=\"wg-actual\" [attr.d]=\"ch.pathActual\" />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ch.PAD_L - 2\"\n [attr.y1]=\"ch.bottomY\"\n [attr.x2]=\"ch.W - ch.PAD_R + 6\"\n [attr.y2]=\"ch.bottomY\"\n />\n @for (p of ch.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core\" />\n @if (showTrendPointLabel(p.showKgLabel)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n @if (p.showXLabel) {\n <text [attr.x]=\"p.x\" [attr.y]=\"ch.H - 10\" text-anchor=\"middle\" class=\"wg-age-label\">\n {{ formatDateShort(p.log.date) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:8px}.wg-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;padding:2px 0 4px}.wg-controls__group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:5px}.wg-chip{border:1px solid #e2e8f0;background:#f8fafc;color:#475569;border-radius:999px;padding:4px 9px;font-size:11px;font-weight:700;cursor:pointer;transition:background .12s ease,border-color .12s ease,color .12s ease}.wg-chip.active{background:#4f46e5;border-color:#4f46e5;color:#fff}.wg-pin{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid #e2e8f0;background:#fff;color:#64748b;border-radius:999px;cursor:pointer}.wg-pin i{font-size:12px}.wg-pin.active{border-color:#10b981;color:#047857;background:#ecfdf5}.wg-section{margin-top:6px}.wg-section:first-child{margin-top:0}.wg-section__head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;padding-bottom:6px;border-bottom:1px solid var(--border, #e6e8ef);flex-wrap:wrap}.wg-chart-toolbar{display:inline-flex;align-items:center;gap:6px;flex-shrink:0}.wg-chart-toolbar__zoom{display:inline-flex;align-items:center;gap:3px;padding:2px;border-radius:8px;border:1px solid var(--border, #e6e8ef);background:var(--surface, #fff)}.wg-zoom-btn{width:26px;height:26px;border:none;border-radius:6px;background:transparent;color:var(--text, #0f172a);font-size:14px;font-weight:700;font-family:inherit;cursor:pointer;line-height:1}.wg-zoom-btn:disabled{opacity:.35;cursor:not-allowed}.wg-zoom-btn:hover:not(:disabled){background:var(--bg, #f8fafc)}.wg-zoom-btn--reset{font-size:12px}.wg-zoom-level{min-width:38px;text-align:center;font-size:10px;font-weight:700;color:var(--muted, #64748b);font-variant-numeric:tabular-nums}.wg-values-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid var(--border, #e6e8ef);border-radius:8px;padding:0;font-family:inherit;cursor:pointer;background:var(--surface, #fff);color:var(--muted, #64748b)}.wg-values-btn i{font-size:13px}.wg-values-btn.active{border-color:var(--primary, #5b6cff);color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff)}.wg-chart-stage{border-radius:6px;overflow:hidden;background:var(--surface, #fff)}.wg-chart-stage--vel{min-height:200px}.wg-chart-stage--compare{min-height:260px}.wg-section__title{margin:0;font-size:12px;font-weight:700;color:var(--text, #0f172a);white-space:nowrap}.wg-panel{padding:8px 8px 4px;background:var(--bg, #f8fafc);border:1px solid var(--border, #e6e8ef);border-radius:8px}.wg-panel__legend{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px;padding:0 2px}.wg-panel__ref{font-size:9px;font-weight:600;color:var(--muted-2, #94a3b8);white-space:nowrap}.wg-legend-item{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.wg-swatch{width:8px;height:8px;border-radius:999px;flex-shrink:0}.wg-swatch--vel{background:#10b981}.wg-line{display:inline-block;width:14px;height:0;flex-shrink:0}.wg-line--baby{border-top:2px solid var(--primary, #5b6cff)}.wg-line--ref{border-top:2px solid #94a3b8}.wg-line--vel{border-top:2px solid #10b981}.wg-line--median{background:#16a34a;height:2px;border:none}.wg-caption{margin:6px 0 0;font-size:9px;line-height:1.4;color:var(--muted-2, #94a3b8)}.wg-latest{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:8px;padding:6px 8px;background:var(--surface, #fff);border:1px solid var(--border, #e6e8ef);border-radius:6px}.wg-latest__label{font-size:9.5px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.wg-latest__values{display:inline-flex;align-items:center;gap:10px;font-variant-numeric:tabular-nums}.wg-latest__baby{font-size:10px;font-weight:700;color:var(--primary, #5b6cff)}.wg-latest__ref{font-size:10px;font-weight:600;color:var(--muted-2, #94a3b8)}.wg-block{padding:10px 0 2px;border-top:1px solid #f1f5f9}.wg-block:first-of-type{border-top:none;padding-top:0}.wg-block__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:6px}.wg-block__title{margin:0;font-size:13px;font-weight:800;letter-spacing:-.02em;display:inline-flex;align-items:center;gap:6px;color:var(--text, #0f172a)}.wg-block__title i{font-size:12px;color:var(--primary, #5b6cff)}.wg-block--vel .wg-block__title i{color:#0d9488}.wg-block__unit{font-size:11px;font-weight:700;color:#94a3b8;flex-shrink:0}.wg-legend{display:flex;flex-wrap:wrap;gap:8px 14px;margin-bottom:6px;font-size:11px;font-weight:600;color:#94a3b8}.wg-legend span{display:inline-flex;align-items:center;gap:6px}.wg-legend--mini{margin-bottom:4px}.wg-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.wg-dot--violet{background:#6366f1}.wg-dot--cyan{background:#0ea5e9}.wg-band{width:16px;height:8px;border-radius:3px;background:#22c55e40;border:1px solid rgba(34,197,94,.4)}.wg-band--cyan{background:#06b6d440;border-color:#06b6d466}.wg-scroll{--wg-min-x: 0px;overflow-x:auto;overflow-y:hidden;margin:0 -2px;padding:2px 2px 6px;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}.wg-scroll--fit{overflow-x:hidden;padding-bottom:2px}.wg-scroll--interactive{overflow-x:auto;overflow-y:hidden;height:100%;min-height:inherit;cursor:grab;touch-action:pan-x}.wg-scroll--interactive.is-panning{cursor:grabbing;-webkit-user-select:none;user-select:none}.wg-scroll::-webkit-scrollbar{height:5px}.wg-scroll::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:6px}.wg-svg{display:block;width:max(100%,var(--wg-min-x, 0px));max-width:none;height:auto;flex-shrink:0}.wg-svg .wg-plot-fill{fill:#f8faff}.wg-svg .wg-plot-fill--cyan{fill:#f0fdfa}.wg-svg .wg-grid line{stroke:#eef2f7;stroke-width:1}.wg-svg .wg-grid-label{font-size:9px;font-weight:600;fill:#94a3b8}.wg-svg .wg-axis{stroke:#e2e8f0;stroke-width:1.2}.wg-svg .wg-x-tick{stroke:#e2e8f0;stroke-width:1}.wg-svg .wg-x-main,.wg-svg .wg-x-label{font-size:9px;font-weight:600;fill:var(--text, #0f172a)}.wg-svg .wg-x-label{fill:#64748b}.wg-svg .wg-band{stroke:none}.wg-svg .wg-ref{stroke:#cbd5e1;stroke-width:1;stroke-dasharray:4 3;opacity:.9}.wg-svg .wg-median{stroke:#94a3b8;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-median--dash{stroke:#94a3b8;stroke-width:2}.wg-svg .wg-actual{fill:none;stroke:#6366f1;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-actual--dash{stroke:var(--primary, #5b6cff);stroke-width:2.4}.wg-svg .wg-actual--height{stroke:#0ea5e9;stroke-width:2.6}.wg-svg .wg-area{stroke:none}.wg-svg .wg-node{fill:#fff;stroke:#6366f1;stroke-width:2}.wg-svg .wg-node--dash{fill:var(--primary, #5b6cff);stroke:#fff;stroke-width:1.5}.wg-svg .wg-node--dash.wg-node--active{stroke:var(--primary, #5b6cff);stroke-width:2.5}.wg-svg .wg-node--cyan{stroke:#0ea5e9;fill:#0ea5e9}.wg-svg .wg-node--vel{stroke:#0f766e}.wg-svg .wg-node-core{fill:#4f46e5;stroke:none}.wg-svg .wg-node-core--cyan{fill:#0284c7}.wg-svg .wg-node-core--vel{fill:#0d9488}.wg-svg .wg-value-label{font-size:10px;font-weight:800;fill:var(--text, #0f172a);pointer-events:none}.wg-svg .wg-age-label{font-size:9px;font-weight:600;fill:#64748b}.wg-svg .wg-zero{stroke:#cbd5e1;stroke-width:1;stroke-dasharray:4 3}.wg-svg .wg-velocity{stroke:#0d9488;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-bar{fill:#10b981}.wg-svg .wg-ref-line{stroke:#94a3b8;stroke-width:1.2;stroke-dasharray:4 3}.wg-svg .wg-node-hit{fill:transparent;stroke:none;cursor:pointer}.wg-svg .wg-point{cursor:pointer}:host.wg-dashboard{gap:12px}:host.wg-dashboard .wg-svg--compare-dash,:host.wg-dashboard .wg-svg--velocity-line{display:block;max-width:none;flex-shrink:0}:host.wg-dashboard .wg-grid-label{font-size:10px}:host.wg-dashboard .wg-value-label{font-size:10px}:host.wg-dashboard .wg-x-main,:host.wg-dashboard .wg-x-label{font-size:10px}\n"], dependencies: [{ kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12565
12715
  }
12566
12716
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightChartsComponent, decorators: [{
12567
12717
  type: Component,
12568
- args: [{ selector: 'app-weight-charts', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"wg-controls\">\n <div class=\"wg-controls__group\">\n <span class=\"wg-controls__label\">Span</span>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '30d'\"\n (click)=\"setSpanPreset('30d')\"\n >\n 30 ng\u00E0y\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '90d'\"\n (click)=\"setSpanPreset('90d')\"\n >\n 90 ng\u00E0y\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '180d'\"\n (click)=\"setSpanPreset('180d')\"\n >\n 180 ng\u00E0y\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === 'all'\"\n (click)=\"setSpanPreset('all')\"\n >\n To\u00E0n b\u1ED9\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-pin\"\n [class.active]=\"pinLatestPoint()\"\n (click)=\"togglePinLatest()\"\n >\n <i class=\"pi pi-map-marker\"></i>\n {{ pinLatestPoint() ? 'Pin \u0111i\u1EC3m m\u1EDBi nh\u1EA5t' : 'Hi\u1EC7n nhi\u1EC1u nh\u00E3n' }}\n </button>\n</section>\n\n<!-- C\u00E2n n\u1EB7ng vs WHO -->\n@if (weightVsStandardChart(); as cmp) {\n <section class=\"wg-card wg-card--compare\">\n <header class=\"wg-card__head\">\n <h3><i class=\"pi pi-chart-line\"></i> C\u00E2n n\u1EB7ng so v\u1EDBi chu\u1EA9n</h3>\n <p class=\"wg-meta\">\n <span class=\"wg-pill\">\n <i class=\"pi pi-globe\"></i> {{ cmp.regionCode }} \u00B7 WHO \u00B7 {{ cmp.sexLabelVi }}\n </span>\n <span class=\"wg-pill wg-pill--accent\" [title]=\"cmp.referenceCaption\">\n <i class=\"pi pi-database\"></i>\n <strong>{{ cmp.birthRecordLine }}</strong>\n </span>\n </p>\n </header>\n <div class=\"wg-legend\" aria-hidden=\"false\">\n <span><i class=\"wg-dot wg-dot--violet\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> Trung v\u1ECB</span>\n <span><i class=\"wg-band\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"cmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + cmp.W + ' ' + cmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgWClip\">\n <rect\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n </clipPath>\n <linearGradient id=\"wCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.2\" />\n <stop offset=\"100%\" stop-color=\"#22c55e\" stop-opacity=\"0.05\" />\n </linearGradient>\n <linearGradient id=\"wCmpActualGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#a5b4fc\" />\n <stop offset=\"100%\" stop-color=\"#4f46e5\" />\n </linearGradient>\n <filter id=\"wgWGlow\" x=\"-8%\" y=\"-8%\" width=\"116%\" height=\"116%\">\n <feGaussianBlur stdDeviation=\"1.4\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g clip-path=\"url(#wgWClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n <g class=\"wg-grid\">\n @for (g of cmp.gridLines; track g) {\n <line\n [attr.x1]=\"cmp.PAD_L\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of cmp.gridLines; track g) {\n <text\n [attr.x]=\"cmp.PAD_L - 10\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band\" [attr.d]=\"cmp.bandPath\" fill=\"url(#wCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"cmp.pathMedian\" fill=\"none\" />\n <path\n class=\"wg-actual\"\n [attr.d]=\"cmp.pathActual\"\n stroke=\"url(#wCmpActualGrad)\"\n filter=\"url(#wgWGlow)\"\n />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"cmp.PAD_L - 2\"\n [attr.y1]=\"cmp.bottomY\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 6\"\n [attr.y2]=\"cmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of cmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"cmp.H - cmp.PAD_B\"\n [attr.y2]=\"cmp.H - cmp.PAD_B + 5\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 22\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n @if (t.subLabel) {\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 8\" text-anchor=\"middle\" class=\"wg-x-sub\">\n {{ t.subLabel }}\n </text>\n }\n </g>\n }\n </g>\n @for (p of cmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"7\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"3.5\" class=\"wg-node-core\" />\n @if (showWeightPointLabel(p, cmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 14\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"cmp.H - 48\"\n [attr.transform]=\"'rotate(-34 ' + p.x + ',' + (cmp.H - 48) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ p.ageLabel }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n\n<!-- Chi\u1EC1u cao vs WHO -->\n@if (heightVsStandardChart(); as hCmp) {\n <section class=\"wg-card wg-card--compare\">\n <header class=\"wg-card__head\">\n <h3><i class=\"pi pi-arrows-v\"></i> Chi\u1EC1u cao so v\u1EDBi chu\u1EA9n</h3>\n <p class=\"wg-meta\">\n <span class=\"wg-pill\">\n <i class=\"pi pi-globe\"></i> {{ hCmp.regionCode }} \u00B7 WHO \u00B7 {{ hCmp.sexLabelVi }}\n </span>\n </p>\n </header>\n <div class=\"wg-legend\" aria-hidden=\"false\">\n <span><i class=\"wg-dot wg-dot--cyan\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> Trung v\u1ECB</span>\n <span><i class=\"wg-band wg-band--cyan\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"hCmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + hCmp.W + ' ' + hCmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgHClip\">\n <rect\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n </clipPath>\n <linearGradient id=\"hCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#06b6d4\" stop-opacity=\"0.22\" />\n <stop offset=\"100%\" stop-color=\"#06b6d4\" stop-opacity=\"0.06\" />\n </linearGradient>\n <linearGradient id=\"hCmpActualGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#67e8f9\" />\n <stop offset=\"100%\" stop-color=\"#0284c7\" />\n </linearGradient>\n <filter id=\"wgHGlow\" x=\"-8%\" y=\"-8%\" width=\"116%\" height=\"116%\">\n <feGaussianBlur stdDeviation=\"1.4\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g clip-path=\"url(#wgHClip)\">\n <rect\n class=\"wg-plot-fill wg-plot-fill--cyan\"\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n <g class=\"wg-grid\">\n @for (g of hCmp.gridLines; track g) {\n <line\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of hCmp.gridLines; track g) {\n <text\n [attr.x]=\"hCmp.PAD_L - 10\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band wg-band--cyan\" [attr.d]=\"hCmp.bandPath\" fill=\"url(#hCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"hCmp.pathMedian\" fill=\"none\" />\n <path\n class=\"wg-actual wg-actual--height\"\n [attr.d]=\"hCmp.pathActual\"\n stroke=\"url(#hCmpActualGrad)\"\n filter=\"url(#wgHGlow)\"\n />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"hCmp.PAD_L - 2\"\n [attr.y1]=\"hCmp.bottomY\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 6\"\n [attr.y2]=\"hCmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of hCmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"hCmp.H - hCmp.PAD_B\"\n [attr.y2]=\"hCmp.H - hCmp.PAD_B + 5\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"hCmp.H - 20\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n </g>\n }\n </g>\n @for (p of hCmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"7\" class=\"wg-node wg-node--cyan\" />\n <circle\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"3.5\"\n class=\"wg-node-core wg-node-core--cyan\"\n />\n @if (showHeightPointLabel(p, hCmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 14\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatCm(p.heightCm) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"hCmp.H - 48\"\n [attr.transform]=\"'rotate(-34 ' + p.x + ',' + (hCmp.H - 48) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ p.ageLabel }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n\n<!-- Xu h\u01B0\u1EDBng \u0111\u01A1n (kh\u00F4ng c\u00F3 birth + WHO compare) -->\n@if (simpleTrendMode() && weightTrendChart(); as ch) {\n <section class=\"wg-card\">\n <header class=\"wg-card__head wg-card__head--solo\">\n <h3><i class=\"pi pi-chart-line\"></i> Xu h\u01B0\u1EDBng c\u00E2n n\u1EB7ng</h3>\n <p class=\"wg-card__hint\">Theo t\u1EEBng l\u1EA7n c\u00E2n tr\u00EAn sheet</p>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ch.W\">\n <svg\n class=\"wg-svg\"\n [attr.viewBox]=\"'0 0 ' + ch.W + ' ' + ch.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgTrendClip\">\n <rect\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n </clipPath>\n <linearGradient id=\"wAreaGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#6366f1\" stop-opacity=\"0.28\" />\n <stop offset=\"100%\" stop-color=\"#6366f1\" stop-opacity=\"0.04\" />\n </linearGradient>\n <linearGradient id=\"wLineGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#a5b4fc\" />\n <stop offset=\"100%\" stop-color=\"#4338ca\" />\n </linearGradient>\n <filter id=\"wgTrendGlow\" x=\"-8%\" y=\"-8%\" width=\"116%\" height=\"116%\">\n <feGaussianBlur stdDeviation=\"1.2\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g clip-path=\"url(#wgTrendClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"12\"\n ry=\"12\"\n />\n <g class=\"wg-grid\">\n @for (g of ch.gridLines; track g) {\n <line\n [attr.x1]=\"ch.PAD_L\"\n [attr.x2]=\"ch.W - ch.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of ch.gridLines; track g) {\n <text\n [attr.x]=\"ch.PAD_L - 10\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-area\" [attr.d]=\"ch.areaPath\" fill=\"url(#wAreaGrad)\" />\n <path\n class=\"wg-actual\"\n [attr.d]=\"ch.pathActual\"\n stroke=\"url(#wLineGrad)\"\n filter=\"url(#wgTrendGlow)\"\n />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ch.PAD_L - 2\"\n [attr.y1]=\"ch.bottomY\"\n [attr.x2]=\"ch.W - ch.PAD_R + 6\"\n [attr.y2]=\"ch.bottomY\"\n />\n @for (p of ch.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"7\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"3.5\" class=\"wg-node-core\" />\n @if (p.showKgLabel) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 14\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"ch.H - 40\"\n [attr.transform]=\"'rotate(-36 ' + p.x + ',' + (ch.H - 40) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ formatDateShort(p.log.date) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n\n<!-- T\u1ED1c \u0111\u1ED9 -->\n@if (weightVelocityLineChart(); as ln) {\n <section class=\"wg-suite\">\n <header class=\"wg-suite__head\">\n <h3><i class=\"pi pi-bolt\"></i> T\u1ED1c \u0111\u1ED9 t\u0103ng c\u00E2n</h3>\n <p class=\"wg-suite__sub\">\u01AF\u1EDBc l\u01B0\u1EE3ng g/ng\u00E0y gi\u1EEFa hai l\u1EA7n c\u00E2n li\u00EAn ti\u1EBFp</p>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ln.W\">\n <svg\n class=\"wg-svg wg-svg--velocity\"\n [attr.viewBox]=\"'0 0 ' + ln.W + ' ' + ln.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <linearGradient id=\"wVelLineGrad\" x1=\"0%\" y1=\"0%\" x2=\"100%\" y2=\"0%\">\n <stop offset=\"0%\" stop-color=\"#34d399\" />\n <stop offset=\"100%\" stop-color=\"#0d9488\" />\n </linearGradient>\n <filter id=\"wgVelGlow\" x=\"-6%\" y=\"-6%\" width=\"112%\" height=\"112%\">\n <feGaussianBlur stdDeviation=\"1\" result=\"b\" />\n <feMerge>\n <feMergeNode in=\"b\" />\n <feMergeNode in=\"SourceGraphic\" />\n </feMerge>\n </filter>\n </defs>\n <g class=\"wg-grid\">\n @for (gl of ln.gridLines; track gl) {\n <line\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y1]=\"gl.y\"\n [attr.y2]=\"gl.y\"\n />\n }\n @for (gl of ln.gridLines; track gl) {\n <text\n [attr.x]=\"ln.PAD_L - 6\"\n [attr.y]=\"gl.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ gl.label }}\n </text>\n }\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ln.PAD_L - 2\"\n [attr.y1]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y2]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n />\n @if (ln.showZeroLine) {\n <line\n class=\"wg-zero\"\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW\"\n [attr.y1]=\"ln.zeroY\"\n [attr.y2]=\"ln.zeroY\"\n />\n }\n <path\n class=\"wg-velocity\"\n [attr.d]=\"ln.pathLine\"\n fill=\"none\"\n stroke=\"url(#wVelLineGrad)\"\n filter=\"url(#wgVelGlow)\"\n />\n @for (p of ln.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"6\" class=\"wg-node wg-node--vel\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"3\" class=\"wg-node-core wg-node-core--vel\" />\n @if (p.showValue) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 12\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatGPerDay(p.gPerDay) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"ln.H - 36\"\n [attr.transform]=\"'rotate(-36 ' + p.x + ',' + (ln.H - 36) + ')'\"\n text-anchor=\"end\"\n class=\"wg-age-label\"\n >\n {{ p.label }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n}\n", styles: ["@charset \"UTF-8\";:host{display:flex;flex-direction:column;gap:10px}.wg-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px 10px;background:#fff;border:1px solid #e2e8f0;border-radius:12px;padding:8px 10px}.wg-controls__group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:6px}.wg-controls__label{font-size:11px;font-weight:800;color:#64748b;text-transform:uppercase;letter-spacing:.04em;margin-right:2px}.wg-chip{border:1px solid #cbd5e1;background:#f8fafc;color:#334155;border-radius:999px;padding:4px 10px;font-size:11.5px;font-weight:700;cursor:pointer;transition:all .12s ease}.wg-chip.active{background:#4f46e5;border-color:#4f46e5;color:#fff}.wg-pin{display:inline-flex;align-items:center;gap:5px;border:1px solid #cbd5e1;background:#fff;color:#475569;border-radius:999px;padding:5px 11px;font-size:11.5px;font-weight:700;cursor:pointer}.wg-pin i{font-size:10px}.wg-pin.active{border-color:#10b981;color:#047857;background:#ecfdf5}.wg-card,.wg-suite{background:var(--surface, #fff);border:1px solid color-mix(in srgb,var(--border, #e6e8ef) 88%,var(--primary, #5b6cff));border-radius:14px;padding:12px 14px 10px;box-shadow:0 1px 10px -6px #0f172a1a}.wg-card__head{margin-bottom:8px}.wg-card__head h3{margin:0 0 6px;font-size:15px;font-weight:800;letter-spacing:-.02em;display:flex;align-items:center;gap:8px;color:var(--text, #0f172a)}.wg-card__head h3 i{color:var(--primary, #5b6cff)}.wg-card__head--solo .wg-card__hint{margin:0;font-size:13px;color:var(--muted, #64748b)}.wg-meta{display:flex;flex-wrap:wrap;gap:8px;margin:0}.wg-pill{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:600;background:var(--surface-2, #f1f5f9);color:var(--text)}.wg-pill i{opacity:.85;font-size:12px}.wg-pill--accent{flex:1 1 200px;min-width:0;background:linear-gradient(120deg,color-mix(in srgb,var(--primary, #5b6cff) 10%,#fff),#f8fafc);border:1px solid color-mix(in srgb,var(--primary, #5b6cff) 22%,var(--border, #e6e8ef))}.wg-pill--accent strong{font-weight:700;word-break:break-word}.wg-legend{display:flex;flex-wrap:wrap;gap:10px 18px;margin-bottom:10px;font-size:12px;font-weight:600;color:var(--muted, #64748b)}.wg-legend span{display:inline-flex;align-items:center;gap:8px}.wg-dot{width:11px;height:11px;border-radius:50%;flex-shrink:0;box-shadow:0 0 0 2px #fff}.wg-dot--violet{background:linear-gradient(145deg,#818cf8,#4f46e5)}.wg-dot--cyan{background:linear-gradient(145deg,#22d3ee,#0284c7)}.wg-line{width:22px;height:3px;border-radius:2px;flex-shrink:0}.wg-line--median{background:#16a34a}.wg-band{width:22px;height:12px;border-radius:4px;background:linear-gradient(180deg,#22c55e59,#22c55e14);border:1px solid rgba(34,197,94,.45)}.wg-band--cyan{background:linear-gradient(180deg,#06b6d461,#06b6d41a);border-color:#06b6d473}.wg-scroll{--wg-min-x: 0px;overflow-x:auto;overflow-y:hidden;margin:0 -4px;padding:4px 4px 8px;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}.wg-scroll::-webkit-scrollbar{height:7px}.wg-scroll::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:6px}.wg-svg{display:block;width:max(100%,var(--wg-min-x, 0px));max-width:none;height:auto;flex-shrink:0}.wg-svg .wg-plot-fill{fill:#f8faff}.wg-svg .wg-plot-fill--cyan{fill:#f0fdfa}.wg-svg .wg-grid line{stroke:#e8edf5;stroke-width:1}.wg-svg .wg-grid-label{font-size:11px;font-weight:600;fill:var(--muted, #64748b)}.wg-svg .wg-axis{stroke:#cbd5e1;stroke-width:1.35}.wg-svg .wg-x-tick{stroke:#cbd5e1;stroke-width:1}.wg-svg .wg-x-main{font-size:10.5px;font-weight:700;fill:var(--text, #0f172a)}.wg-svg .wg-x-sub{font-size:8.5px;fill:var(--muted-2, #94a3b8)}.wg-svg .wg-band{stroke:none}.wg-svg .wg-ref{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 4;opacity:.95}.wg-svg .wg-median{stroke:#16a34a;stroke-width:2.2;stroke-linecap:round;stroke-linejoin:round;opacity:.95}.wg-svg .wg-actual{fill:none;stroke-width:3;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-actual--height{stroke-width:3.1}.wg-svg .wg-area{stroke:none}.wg-svg .wg-node{fill:#fff;stroke:#6366f1;stroke-width:2.2}.wg-svg .wg-node--cyan{stroke:#0ea5e9}.wg-svg .wg-node--vel{stroke:#0f766e;stroke-width:2}.wg-svg .wg-node-core{fill:#4f46e5;stroke:none}.wg-svg .wg-node-core--cyan{fill:#0284c7}.wg-svg .wg-node-core--vel{fill:#0d9488}.wg-svg .wg-value-label{font-size:10.5px;font-weight:800;fill:var(--text, #0f172a)}.wg-svg .wg-age-label{font-size:9.5px;font-weight:600;fill:#475569}.wg-svg .wg-zero{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 3}.wg-svg .wg-velocity{stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}.wg-suite__head{margin-bottom:10px}.wg-suite__head h3{margin:0 0 4px;font-size:15px;font-weight:800;display:flex;align-items:center;gap:8px;color:var(--text)}.wg-suite__head h3 i{color:#0d9488}.wg-suite__sub{margin:0;font-size:12.5px;color:var(--muted);line-height:1.45}\n"] }]
12569
- }], propDecorators: { weightVsStandardChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightVsStandardChart", required: false }] }], heightVsStandardChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "heightVsStandardChart", required: false }] }], weightTrendChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightTrendChart", required: false }] }], simpleTrendMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "simpleTrendMode", required: false }] }], weightVelocityLineChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightVelocityLineChart", required: false }] }] } });
12718
+ args: [{ selector: 'app-weight-charts', standalone: true, imports: [NgClass], changeDetection: ChangeDetectionStrategy.OnPush, host: {
12719
+ '[class.wg-dashboard]': 'dashboardMode()',
12720
+ }, template: "@if (showControls()) {\n <section class=\"wg-controls\">\n <div class=\"wg-controls__group\" role=\"group\" aria-label=\"Kho\u1EA3ng th\u1EDDi gian\">\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '30d'\"\n (click)=\"setSpanPreset('30d')\"\n >\n 30d\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '90d'\"\n (click)=\"setSpanPreset('90d')\"\n >\n 90d\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === '180d'\"\n (click)=\"setSpanPreset('180d')\"\n >\n 180d\n </button>\n <button\n type=\"button\"\n class=\"wg-chip\"\n [class.active]=\"spanPreset() === 'all'\"\n (click)=\"setSpanPreset('all')\"\n >\n All\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-pin\"\n [class.active]=\"pinLatestPoint()\"\n (click)=\"togglePinLatest()\"\n [attr.aria-label]=\"pinLatestPoint() ? 'Ch\u1EC9 hi\u1EC7n \u0111i\u1EC3m m\u1EDBi nh\u1EA5t' : 'Hi\u1EC7n nhi\u1EC1u nh\u00E3n'\"\n [title]=\"pinLatestPoint() ? 'Ch\u1EC9 \u0111i\u1EC3m m\u1EDBi nh\u1EA5t' : 'Nhi\u1EC1u nh\u00E3n'\"\n >\n <i class=\"pi\" [ngClass]=\"pinLatestPoint() ? 'pi-map-marker' : 'pi-tags'\"></i>\n </button>\n </section>\n}\n\n@if (dashboardMode()) {\n @if (activeVelocityChart(); as vel) {\n <section class=\"wg-section\" aria-label=\"Bi\u1EC3u \u0111\u1ED3 t\u1ED1c \u0111\u1ED9\">\n <header class=\"wg-section__head\">\n <h3 class=\"wg-section__title\">{{ velocitySectionTitle() }}</h3>\n <div class=\"wg-chart-toolbar\">\n <div class=\"wg-chart-toolbar__zoom\" role=\"group\" aria-label=\"Thu ph\u00F3ng\">\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomOut('velocity')\"\n [disabled]=\"velocityZoom() <= minZoom\"\n aria-label=\"Thu nh\u1ECF\"\n >\n \u2212\n </button>\n <span class=\"wg-zoom-level\">{{ zoomPercent(velocityZoom()) }}%</span>\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomIn('velocity')\"\n [disabled]=\"velocityZoom() >= maxZoom\"\n aria-label=\"Ph\u00F3ng to\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"wg-zoom-btn wg-zoom-btn--reset\"\n (click)=\"resetZoom('velocity')\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i thu ph\u00F3ng\"\n >\n \u21BA\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-values-btn\"\n [class.active]=\"dashShowValues()\"\n (click)=\"toggleDashShowValues()\"\n [attr.aria-label]=\"dashShowValues() ? '\u1EA8n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3' : 'Hi\u1EC7n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3'\"\n [title]=\"dashShowValues() ? '\u1EA8n s\u1ED1' : 'Hi\u1EC7n s\u1ED1'\"\n >\n <i class=\"pi\" [ngClass]=\"dashShowValues() ? 'pi-eye' : 'pi-eye-slash'\"></i>\n </button>\n </div>\n </header>\n <div class=\"wg-panel\">\n <div class=\"wg-panel__legend\">\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--vel\" aria-hidden=\"true\"></i>\n {{ vel.seriesLabel }}\n </span>\n <span class=\"wg-panel__ref\">TB {{ vel.refValue }}{{ vel.refSuffix }}</span>\n </div>\n <div\n class=\"wg-chart-stage wg-chart-stage--vel\"\n (wheel)=\"onChartWheel($event, 'velocity')\"\n >\n <div\n class=\"wg-scroll wg-scroll--interactive\"\n (pointerdown)=\"onPanPointerDown($event, $any($event.currentTarget))\"\n (pointermove)=\"onPanPointerMove($event)\"\n (pointerup)=\"onPanPointerUp($event)\"\n (pointercancel)=\"onPanPointerUp($event)\"\n >\n <svg\n class=\"wg-svg wg-svg--velocity-line\"\n [attr.viewBox]=\"'0 0 ' + vel.W + ' ' + vel.H\"\n preserveAspectRatio=\"xMinYMid meet\"\n [style.width.px]=\"chartScaledWidth(vel.W, velocityZoom())\"\n [style.height.px]=\"chartScaledHeight(vel.H, velocityZoom())\"\n role=\"img\"\n [attr.aria-label]=\"vel.caption\"\n >\n <g class=\"wg-grid\">\n @for (gl of vel.gridLines; track gl) {\n <line\n [attr.x1]=\"vel.PAD_L\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y1]=\"gl.y\"\n [attr.y2]=\"gl.y\"\n />\n }\n @for (gl of vel.gridLines; track gl) {\n <text\n [attr.x]=\"vel.PAD_L - 5\"\n [attr.y]=\"gl.y + 3\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ gl.label }}\n </text>\n }\n </g>\n <line\n class=\"wg-ref-line\"\n [attr.x1]=\"vel.PAD_L\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y1]=\"vel.refY\"\n [attr.y2]=\"vel.refY\"\n />\n @if (vel.showZeroLine) {\n <line\n class=\"wg-zero\"\n [attr.x1]=\"vel.PAD_L\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y1]=\"vel.zeroY\"\n [attr.y2]=\"vel.zeroY\"\n />\n }\n <path class=\"wg-velocity\" [attr.d]=\"vel.pathLine\" fill=\"none\" />\n @for (p of vel.pts; track p.label) {\n <g class=\"wg-point\" (click)=\"selectPoint(p.label)\">\n <circle\n class=\"wg-node-hit\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"14\"\n />\n <circle\n class=\"wg-node wg-node--vel\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n [class.wg-node--active]=\"activePointKey() === p.label\"\n />\n <circle\n class=\"wg-node-core wg-node-core--vel\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"2.5\"\n />\n @if (showVelocityPointValue(vel, p.showValue, p.label)) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"p.y - 10\"\n text-anchor=\"middle\"\n class=\"wg-value-label\"\n >\n {{ formatVelocityValue(vel, p.gPerDay) }}\n </text>\n }\n @if (p.showXLabel) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"vel.H - 10\"\n text-anchor=\"middle\"\n class=\"wg-x-label\"\n >\n {{ p.label }}\n </text>\n }\n </g>\n }\n <line\n class=\"wg-axis\"\n [attr.x1]=\"vel.PAD_L\"\n [attr.y1]=\"vel.bottomY\"\n [attr.x2]=\"vel.PAD_L + vel.innerW\"\n [attr.y2]=\"vel.bottomY\"\n />\n </svg>\n </div>\n </div>\n </div>\n <p class=\"wg-caption\">{{ velocityCaption() }}</p>\n </section>\n }\n\n @if (activeWeightCompare(); as cmp) {\n <section class=\"wg-section\" aria-label=\"Bi\u1EC3u \u0111\u1ED3 so chu\u1EA9n c\u00E2n n\u1EB7ng\">\n <header class=\"wg-section__head\">\n <h3 class=\"wg-section__title\">{{ compareSectionTitle() }}</h3>\n <div class=\"wg-chart-toolbar\">\n <div class=\"wg-chart-toolbar__zoom\" role=\"group\" aria-label=\"Thu ph\u00F3ng\">\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomOut('compare')\"\n [disabled]=\"compareZoom() <= minZoom\"\n aria-label=\"Thu nh\u1ECF\"\n >\n \u2212\n </button>\n <span class=\"wg-zoom-level\">{{ zoomPercent(compareZoom()) }}%</span>\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomIn('compare')\"\n [disabled]=\"compareZoom() >= maxZoom\"\n aria-label=\"Ph\u00F3ng to\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"wg-zoom-btn wg-zoom-btn--reset\"\n (click)=\"resetZoom('compare')\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i thu ph\u00F3ng\"\n >\n \u21BA\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-values-btn\"\n [class.active]=\"dashShowValues()\"\n (click)=\"toggleDashShowValues()\"\n [attr.aria-label]=\"dashShowValues() ? '\u1EA8n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3' : 'Hi\u1EC7n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3'\"\n [title]=\"dashShowValues() ? '\u1EA8n s\u1ED1' : 'Hi\u1EC7n s\u1ED1'\"\n >\n <i class=\"pi\" [ngClass]=\"dashShowValues() ? 'pi-eye' : 'pi-eye-slash'\"></i>\n </button>\n </div>\n </header>\n <div class=\"wg-panel\">\n <div class=\"wg-panel__legend\">\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--baby\" aria-hidden=\"true\"></i>\n B\u00E9\n </span>\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--ref\" aria-hidden=\"true\"></i>\n Chu\u1EA9n (trung v\u1ECB)\n </span>\n </div>\n <div class=\"wg-chart-stage wg-chart-stage--compare\" (wheel)=\"onChartWheel($event, 'compare')\">\n <div\n class=\"wg-scroll wg-scroll--interactive\"\n (pointerdown)=\"onPanPointerDown($event, $any($event.currentTarget))\"\n (pointermove)=\"onPanPointerMove($event)\"\n (pointerup)=\"onPanPointerUp($event)\"\n (pointercancel)=\"onPanPointerUp($event)\"\n >\n <svg\n class=\"wg-svg wg-svg--compare-dash\"\n [attr.viewBox]=\"'0 0 ' + cmp.W + ' ' + cmp.H\"\n preserveAspectRatio=\"xMinYMid meet\"\n [style.width.px]=\"chartScaledWidth(cmp.W, compareZoom())\"\n [style.height.px]=\"chartScaledHeight(cmp.H, compareZoom())\"\n role=\"img\"\n [attr.aria-label]=\"compareCaption()\"\n >\n <g class=\"wg-grid\">\n @for (g of cmp.gridLines; track g) {\n <line\n [attr.x1]=\"cmp.PAD_L\"\n [attr.x2]=\"cmp.W - cmp.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of cmp.gridLines; track g) {\n <text\n [attr.x]=\"cmp.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-median wg-median--dash\" [attr.d]=\"cmp.pathMedian\" fill=\"none\" />\n <path class=\"wg-actual wg-actual--dash\" [attr.d]=\"cmp.pathActual\" />\n @for (p of cmp.pts; track p.log.date) {\n <g class=\"wg-point\" (click)=\"selectPoint(p.log.date)\">\n <circle class=\"wg-node-hit\" [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"14\" />\n <circle\n class=\"wg-node wg-node--dash\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n [class.wg-node--active]=\"activePointKey() === p.log.date\"\n />\n @if (showDashWeightLabel(p, cmp.pts)) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"p.y - 10\"\n text-anchor=\"middle\"\n class=\"wg-value-label\"\n >\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n </g>\n }\n <line\n class=\"wg-axis\"\n [attr.x1]=\"cmp.PAD_L\"\n [attr.y1]=\"cmp.bottomY\"\n [attr.x2]=\"cmp.W - cmp.PAD_R\"\n [attr.y2]=\"cmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of cmp.xTicks; track t.label) {\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 12\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n }\n </g>\n </svg>\n </div>\n </div>\n @if (cmp.latestSummary; as sum) {\n <div class=\"wg-latest\">\n <span class=\"wg-latest__label\">M\u1EDBi nh\u1EA5t \u00B7 {{ sum.ageLabel }}</span>\n <span class=\"wg-latest__values\">\n <span class=\"wg-latest__baby\">B\u00E9 {{ formatSummaryValue(sum) }} {{ sum.unit }}</span>\n <span class=\"wg-latest__ref\">Chu\u1EA9n {{ formatSummaryRef(sum) }}</span>\n </span>\n </div>\n }\n </div>\n <p class=\"wg-caption\">{{ compareCaption() }}</p>\n </section>\n }\n\n @if (activeHeightCompare(); as hCmp) {\n <section class=\"wg-section\" aria-label=\"Bi\u1EC3u \u0111\u1ED3 so chu\u1EA9n chi\u1EC1u cao\">\n <header class=\"wg-section__head\">\n <h3 class=\"wg-section__title\">{{ compareSectionTitle() }}</h3>\n <div class=\"wg-chart-toolbar\">\n <div class=\"wg-chart-toolbar__zoom\" role=\"group\" aria-label=\"Thu ph\u00F3ng\">\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomOut('compare')\"\n [disabled]=\"compareZoom() <= minZoom\"\n aria-label=\"Thu nh\u1ECF\"\n >\n \u2212\n </button>\n <span class=\"wg-zoom-level\">{{ zoomPercent(compareZoom()) }}%</span>\n <button\n type=\"button\"\n class=\"wg-zoom-btn\"\n (click)=\"zoomIn('compare')\"\n [disabled]=\"compareZoom() >= maxZoom\"\n aria-label=\"Ph\u00F3ng to\"\n >\n +\n </button>\n <button\n type=\"button\"\n class=\"wg-zoom-btn wg-zoom-btn--reset\"\n (click)=\"resetZoom('compare')\"\n aria-label=\"\u0110\u1EB7t l\u1EA1i thu ph\u00F3ng\"\n >\n \u21BA\n </button>\n </div>\n <button\n type=\"button\"\n class=\"wg-values-btn\"\n [class.active]=\"dashShowValues()\"\n (click)=\"toggleDashShowValues()\"\n [attr.aria-label]=\"dashShowValues() ? '\u1EA8n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3' : 'Hi\u1EC7n s\u1ED1 li\u1EC7u tr\u00EAn bi\u1EC3u \u0111\u1ED3'\"\n [title]=\"dashShowValues() ? '\u1EA8n s\u1ED1' : 'Hi\u1EC7n s\u1ED1'\"\n >\n <i class=\"pi\" [ngClass]=\"dashShowValues() ? 'pi-eye' : 'pi-eye-slash'\"></i>\n </button>\n </div>\n </header>\n <div class=\"wg-panel\">\n <div class=\"wg-panel__legend\">\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--baby\" aria-hidden=\"true\"></i>\n B\u00E9\n </span>\n <span class=\"wg-legend-item\">\n <i class=\"wg-line wg-line--ref\" aria-hidden=\"true\"></i>\n Chu\u1EA9n (trung v\u1ECB)\n </span>\n </div>\n <div class=\"wg-chart-stage wg-chart-stage--compare\" (wheel)=\"onChartWheel($event, 'compare')\">\n <div\n class=\"wg-scroll wg-scroll--interactive\"\n (pointerdown)=\"onPanPointerDown($event, $any($event.currentTarget))\"\n (pointermove)=\"onPanPointerMove($event)\"\n (pointerup)=\"onPanPointerUp($event)\"\n (pointercancel)=\"onPanPointerUp($event)\"\n >\n <svg\n class=\"wg-svg wg-svg--compare-dash\"\n [attr.viewBox]=\"'0 0 ' + hCmp.W + ' ' + hCmp.H\"\n preserveAspectRatio=\"xMinYMid meet\"\n [style.width.px]=\"chartScaledWidth(hCmp.W, compareZoom())\"\n [style.height.px]=\"chartScaledHeight(hCmp.H, compareZoom())\"\n role=\"img\"\n [attr.aria-label]=\"compareCaption()\"\n >\n <g class=\"wg-grid\">\n @for (g of hCmp.gridLines; track g) {\n <line\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of hCmp.gridLines; track g) {\n <text\n [attr.x]=\"hCmp.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-median wg-median--dash\" [attr.d]=\"hCmp.pathMedian\" fill=\"none\" />\n <path\n class=\"wg-actual wg-actual--dash wg-actual--height\"\n [attr.d]=\"hCmp.pathActual\"\n />\n @for (p of hCmp.pts; track p.log.date) {\n <g class=\"wg-point\" (click)=\"selectPoint(p.log.date)\">\n <circle class=\"wg-node-hit\" [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"14\" />\n <circle\n class=\"wg-node wg-node--dash wg-node--cyan\"\n [attr.cx]=\"p.x\"\n [attr.cy]=\"p.y\"\n r=\"5\"\n [class.wg-node--active]=\"activePointKey() === p.log.date\"\n />\n @if (showDashHeightLabel(p, hCmp.pts)) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"p.y - 10\"\n text-anchor=\"middle\"\n class=\"wg-value-label\"\n >\n {{ formatCm(p.heightCm) }}\n </text>\n }\n </g>\n }\n <line\n class=\"wg-axis\"\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.y1]=\"hCmp.bottomY\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R\"\n [attr.y2]=\"hCmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of hCmp.xTicks; track t.label) {\n <text [attr.x]=\"t.x\" [attr.y]=\"hCmp.H - 12\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n }\n </g>\n </svg>\n </div>\n </div>\n @if (hCmp.latestSummary; as sum) {\n <div class=\"wg-latest\">\n <span class=\"wg-latest__label\">M\u1EDBi nh\u1EA5t \u00B7 {{ sum.ageLabel }}</span>\n <span class=\"wg-latest__values\">\n <span class=\"wg-latest__baby\">B\u00E9 {{ formatSummaryValue(sum) }} {{ sum.unit }}</span>\n <span class=\"wg-latest__ref\">Chu\u1EA9n {{ formatSummaryRef(sum) }}</span>\n </span>\n </div>\n }\n </div>\n <p class=\"wg-caption\">{{ compareCaption() }}</p>\n </section>\n }\n} @else {\n @if (weightVelocityLineChart(); as ln) {\n <section class=\"wg-block wg-block--vel\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-bolt\" aria-hidden=\"true\"></i>\n T\u1ED1c \u0111\u1ED9 t\u0103ng\n </h3>\n <span class=\"wg-block__unit\">g/ng\u00E0y</span>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ln.W\">\n <svg\n class=\"wg-svg wg-svg--velocity\"\n [attr.viewBox]=\"'0 0 ' + ln.W + ' ' + ln.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <g class=\"wg-grid\">\n @for (gl of ln.gridLines; track gl) {\n <line\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y1]=\"gl.y\"\n [attr.y2]=\"gl.y\"\n />\n }\n @for (gl of ln.gridLines; track gl) {\n <text\n [attr.x]=\"ln.PAD_L - 6\"\n [attr.y]=\"gl.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ gl.label }}\n </text>\n }\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ln.PAD_L - 2\"\n [attr.y1]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n [attr.x2]=\"ln.PAD_L + ln.innerW + 4\"\n [attr.y2]=\"ln.PAD_T + ln.H - ln.PAD_B\"\n />\n @if (ln.showZeroLine) {\n <line\n class=\"wg-zero\"\n [attr.x1]=\"ln.PAD_L\"\n [attr.x2]=\"ln.PAD_L + ln.innerW\"\n [attr.y1]=\"ln.zeroY\"\n [attr.y2]=\"ln.zeroY\"\n />\n }\n <path class=\"wg-velocity\" [attr.d]=\"ln.pathLine\" fill=\"none\" />\n @for (p of ln.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node wg-node--vel\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core wg-node-core--vel\" />\n @if (showVelocityPointValue(ln, p.showValue, p.label)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatGPerDay(p.gPerDay) }}\n </text>\n }\n @if (p.showXLabel) {\n <text [attr.x]=\"p.x\" [attr.y]=\"ln.H - 28\" text-anchor=\"middle\" class=\"wg-age-label\">\n {{ p.label }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n\n @if (showWeightCompare() && weightVsStandardChart(); as cmp) {\n <section class=\"wg-block wg-block--compare\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-chart-bar\" aria-hidden=\"true\"></i>\n C\u00E2n \u00B7 chu\u1EA9n\n </h3>\n <span class=\"wg-block__unit\">kg</span>\n </header>\n <div class=\"wg-legend wg-legend--mini\" aria-hidden=\"true\">\n <span><i class=\"wg-dot wg-dot--violet\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> P50</span>\n <span><i class=\"wg-band\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"cmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + cmp.W + ' ' + cmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgWClip\">\n <rect\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n </clipPath>\n <linearGradient id=\"wCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.18\" />\n <stop offset=\"100%\" stop-color=\"#22c55e\" stop-opacity=\"0.04\" />\n </linearGradient>\n </defs>\n <g clip-path=\"url(#wgWClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"weightSpanStartX(cmp)\"\n [attr.y]=\"cmp.PAD_T - 4\"\n [attr.width]=\"cmp.W - weightSpanStartX(cmp) - cmp.PAD_R + 20\"\n [attr.height]=\"cmp.bottomY - cmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n <g class=\"wg-grid\">\n @for (g of cmp.gridLines; track g) {\n <line\n [attr.x1]=\"cmp.PAD_L\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of cmp.gridLines; track g) {\n <text\n [attr.x]=\"cmp.PAD_L - 8\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band\" [attr.d]=\"cmp.bandPath\" fill=\"url(#wCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"cmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"cmp.pathMedian\" fill=\"none\" />\n <path class=\"wg-actual\" [attr.d]=\"cmp.pathActual\" />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"cmp.PAD_L - 2\"\n [attr.y1]=\"cmp.bottomY\"\n [attr.x2]=\"cmp.W - cmp.PAD_R + 6\"\n [attr.y2]=\"cmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of cmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"cmp.H - cmp.PAD_B\"\n [attr.y2]=\"cmp.H - cmp.PAD_B + 4\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"cmp.H - 10\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n </g>\n }\n </g>\n @for (p of cmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core\" />\n @if (showWeightPointLabel(p, cmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n\n @if (showHeightCompare() && heightVsStandardChart(); as hCmp) {\n <section class=\"wg-block wg-block--compare\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n Cao \u00B7 chu\u1EA9n\n </h3>\n <span class=\"wg-block__unit\">cm</span>\n </header>\n <div class=\"wg-legend wg-legend--mini\" aria-hidden=\"true\">\n <span><i class=\"wg-dot wg-dot--cyan\"></i> B\u00E9</span>\n <span><i class=\"wg-line wg-line--median\"></i> P50</span>\n <span><i class=\"wg-band wg-band--cyan\"></i> \u00B12SD</span>\n </div>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"hCmp.W\">\n <svg\n class=\"wg-svg wg-svg--compare\"\n [attr.viewBox]=\"'0 0 ' + hCmp.W + ' ' + hCmp.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgHClip\">\n <rect\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n </clipPath>\n <linearGradient id=\"hCmpBandGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#06b6d4\" stop-opacity=\"0.18\" />\n <stop offset=\"100%\" stop-color=\"#06b6d4\" stop-opacity=\"0.04\" />\n </linearGradient>\n </defs>\n <g clip-path=\"url(#wgHClip)\">\n <rect\n class=\"wg-plot-fill wg-plot-fill--cyan\"\n [attr.x]=\"heightSpanStartX(hCmp)\"\n [attr.y]=\"hCmp.PAD_T - 4\"\n [attr.width]=\"hCmp.W - heightSpanStartX(hCmp) - hCmp.PAD_R + 20\"\n [attr.height]=\"hCmp.bottomY - hCmp.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n <g class=\"wg-grid\">\n @for (g of hCmp.gridLines; track g) {\n <line\n [attr.x1]=\"hCmp.PAD_L\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of hCmp.gridLines; track g) {\n <text\n [attr.x]=\"hCmp.PAD_L - 8\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-band wg-band--cyan\" [attr.d]=\"hCmp.bandPath\" fill=\"url(#hCmpBandGrad)\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathLow\" fill=\"none\" />\n <path class=\"wg-ref\" [attr.d]=\"hCmp.pathHigh\" fill=\"none\" />\n <path class=\"wg-median\" [attr.d]=\"hCmp.pathMedian\" fill=\"none\" />\n <path class=\"wg-actual wg-actual--height\" [attr.d]=\"hCmp.pathActual\" />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"hCmp.PAD_L - 2\"\n [attr.y1]=\"hCmp.bottomY\"\n [attr.x2]=\"hCmp.W - hCmp.PAD_R + 6\"\n [attr.y2]=\"hCmp.bottomY\"\n />\n <g class=\"wg-x-ticks\">\n @for (t of hCmp.xTicks; track t) {\n <g>\n <line\n [attr.x1]=\"t.x\"\n [attr.x2]=\"t.x\"\n [attr.y1]=\"hCmp.H - hCmp.PAD_B\"\n [attr.y2]=\"hCmp.H - hCmp.PAD_B + 4\"\n class=\"wg-x-tick\"\n />\n <text [attr.x]=\"t.x\" [attr.y]=\"hCmp.H - 10\" text-anchor=\"middle\" class=\"wg-x-main\">\n {{ t.label }}\n </text>\n </g>\n }\n </g>\n @for (p of hCmp.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node wg-node--cyan\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core wg-node-core--cyan\" />\n @if (showHeightPointLabel(p, hCmp.pts)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatCm(p.heightCm) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n\n @if (showSimpleTrend() && weightTrendChart(); as ch) {\n <section class=\"wg-block\">\n <header class=\"wg-block__head\">\n <h3 class=\"wg-block__title\">\n <i class=\"pi pi-chart-line\" aria-hidden=\"true\"></i>\n Xu h\u01B0\u1EDBng\n </h3>\n <span class=\"wg-block__unit\">kg</span>\n </header>\n <div class=\"wg-scroll\" [style.--wg-min-x.px]=\"ch.W\">\n <svg\n class=\"wg-svg\"\n [attr.viewBox]=\"'0 0 ' + ch.W + ' ' + ch.H\"\n preserveAspectRatio=\"xMidYMid meet\"\n >\n <defs>\n <clipPath id=\"wgTrendClip\">\n <rect\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n </clipPath>\n <linearGradient id=\"wAreaGrad\" x1=\"0%\" y1=\"0%\" x2=\"0%\" y2=\"100%\">\n <stop offset=\"0%\" stop-color=\"#6366f1\" stop-opacity=\"0.2\" />\n <stop offset=\"100%\" stop-color=\"#6366f1\" stop-opacity=\"0.03\" />\n </linearGradient>\n </defs>\n <g clip-path=\"url(#wgTrendClip)\">\n <rect\n class=\"wg-plot-fill\"\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"ch.PAD_T - 4\"\n [attr.width]=\"ch.W - ch.PAD_L - ch.PAD_R + 20\"\n [attr.height]=\"ch.bottomY - ch.PAD_T + 8\"\n rx=\"8\"\n ry=\"8\"\n />\n <g class=\"wg-grid\">\n @for (g of ch.gridLines; track g) {\n <line\n [attr.x1]=\"ch.PAD_L\"\n [attr.x2]=\"ch.W - ch.PAD_R + 4\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n />\n }\n @for (g of ch.gridLines; track g) {\n <text\n [attr.x]=\"ch.PAD_L - 8\"\n [attr.y]=\"g.y + 4\"\n text-anchor=\"end\"\n class=\"wg-grid-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path class=\"wg-area\" [attr.d]=\"ch.areaPath\" fill=\"url(#wAreaGrad)\" />\n <path class=\"wg-actual\" [attr.d]=\"ch.pathActual\" />\n </g>\n <line\n class=\"wg-axis\"\n [attr.x1]=\"ch.PAD_L - 2\"\n [attr.y1]=\"ch.bottomY\"\n [attr.x2]=\"ch.W - ch.PAD_R + 6\"\n [attr.y2]=\"ch.bottomY\"\n />\n @for (p of ch.pts; track p) {\n <g>\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"5\" class=\"wg-node\" />\n <circle [attr.cx]=\"p.x\" [attr.cy]=\"p.y\" r=\"2.5\" class=\"wg-node-core\" />\n @if (showTrendPointLabel(p.showKgLabel)) {\n <text [attr.x]=\"p.x\" [attr.y]=\"p.y - 10\" text-anchor=\"middle\" class=\"wg-value-label\">\n {{ formatKg(p.log.weightKg) }}\n </text>\n }\n @if (p.showXLabel) {\n <text [attr.x]=\"p.x\" [attr.y]=\"ch.H - 10\" text-anchor=\"middle\" class=\"wg-age-label\">\n {{ formatDateShort(p.log.date) }}\n </text>\n }\n </g>\n }\n </svg>\n </div>\n </section>\n }\n}\n", styles: [":host{display:flex;flex-direction:column;gap:8px}.wg-controls{display:flex;flex-wrap:wrap;align-items:center;justify-content:space-between;gap:8px;padding:2px 0 4px}.wg-controls__group{display:inline-flex;align-items:center;flex-wrap:wrap;gap:5px}.wg-chip{border:1px solid #e2e8f0;background:#f8fafc;color:#475569;border-radius:999px;padding:4px 9px;font-size:11px;font-weight:700;cursor:pointer;transition:background .12s ease,border-color .12s ease,color .12s ease}.wg-chip.active{background:#4f46e5;border-color:#4f46e5;color:#fff}.wg-pin{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid #e2e8f0;background:#fff;color:#64748b;border-radius:999px;cursor:pointer}.wg-pin i{font-size:12px}.wg-pin.active{border-color:#10b981;color:#047857;background:#ecfdf5}.wg-section{margin-top:6px}.wg-section:first-child{margin-top:0}.wg-section__head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;padding-bottom:6px;border-bottom:1px solid var(--border, #e6e8ef);flex-wrap:wrap}.wg-chart-toolbar{display:inline-flex;align-items:center;gap:6px;flex-shrink:0}.wg-chart-toolbar__zoom{display:inline-flex;align-items:center;gap:3px;padding:2px;border-radius:8px;border:1px solid var(--border, #e6e8ef);background:var(--surface, #fff)}.wg-zoom-btn{width:26px;height:26px;border:none;border-radius:6px;background:transparent;color:var(--text, #0f172a);font-size:14px;font-weight:700;font-family:inherit;cursor:pointer;line-height:1}.wg-zoom-btn:disabled{opacity:.35;cursor:not-allowed}.wg-zoom-btn:hover:not(:disabled){background:var(--bg, #f8fafc)}.wg-zoom-btn--reset{font-size:12px}.wg-zoom-level{min-width:38px;text-align:center;font-size:10px;font-weight:700;color:var(--muted, #64748b);font-variant-numeric:tabular-nums}.wg-values-btn{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border:1px solid var(--border, #e6e8ef);border-radius:8px;padding:0;font-family:inherit;cursor:pointer;background:var(--surface, #fff);color:var(--muted, #64748b)}.wg-values-btn i{font-size:13px}.wg-values-btn.active{border-color:var(--primary, #5b6cff);color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff)}.wg-chart-stage{border-radius:6px;overflow:hidden;background:var(--surface, #fff)}.wg-chart-stage--vel{min-height:200px}.wg-chart-stage--compare{min-height:260px}.wg-section__title{margin:0;font-size:12px;font-weight:700;color:var(--text, #0f172a);white-space:nowrap}.wg-panel{padding:8px 8px 4px;background:var(--bg, #f8fafc);border:1px solid var(--border, #e6e8ef);border-radius:8px}.wg-panel__legend{display:flex;align-items:center;justify-content:space-between;gap:10px;margin-bottom:6px;padding:0 2px}.wg-panel__ref{font-size:9px;font-weight:600;color:var(--muted-2, #94a3b8);white-space:nowrap}.wg-legend-item{display:inline-flex;align-items:center;gap:5px;font-size:10px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.wg-swatch{width:8px;height:8px;border-radius:999px;flex-shrink:0}.wg-swatch--vel{background:#10b981}.wg-line{display:inline-block;width:14px;height:0;flex-shrink:0}.wg-line--baby{border-top:2px solid var(--primary, #5b6cff)}.wg-line--ref{border-top:2px solid #94a3b8}.wg-line--vel{border-top:2px solid #10b981}.wg-line--median{background:#16a34a;height:2px;border:none}.wg-caption{margin:6px 0 0;font-size:9px;line-height:1.4;color:var(--muted-2, #94a3b8)}.wg-latest{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-top:8px;padding:6px 8px;background:var(--surface, #fff);border:1px solid var(--border, #e6e8ef);border-radius:6px}.wg-latest__label{font-size:9.5px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.wg-latest__values{display:inline-flex;align-items:center;gap:10px;font-variant-numeric:tabular-nums}.wg-latest__baby{font-size:10px;font-weight:700;color:var(--primary, #5b6cff)}.wg-latest__ref{font-size:10px;font-weight:600;color:var(--muted-2, #94a3b8)}.wg-block{padding:10px 0 2px;border-top:1px solid #f1f5f9}.wg-block:first-of-type{border-top:none;padding-top:0}.wg-block__head{display:flex;align-items:baseline;justify-content:space-between;gap:8px;margin-bottom:6px}.wg-block__title{margin:0;font-size:13px;font-weight:800;letter-spacing:-.02em;display:inline-flex;align-items:center;gap:6px;color:var(--text, #0f172a)}.wg-block__title i{font-size:12px;color:var(--primary, #5b6cff)}.wg-block--vel .wg-block__title i{color:#0d9488}.wg-block__unit{font-size:11px;font-weight:700;color:#94a3b8;flex-shrink:0}.wg-legend{display:flex;flex-wrap:wrap;gap:8px 14px;margin-bottom:6px;font-size:11px;font-weight:600;color:#94a3b8}.wg-legend span{display:inline-flex;align-items:center;gap:6px}.wg-legend--mini{margin-bottom:4px}.wg-dot{width:8px;height:8px;border-radius:50%;flex-shrink:0}.wg-dot--violet{background:#6366f1}.wg-dot--cyan{background:#0ea5e9}.wg-band{width:16px;height:8px;border-radius:3px;background:#22c55e40;border:1px solid rgba(34,197,94,.4)}.wg-band--cyan{background:#06b6d440;border-color:#06b6d466}.wg-scroll{--wg-min-x: 0px;overflow-x:auto;overflow-y:hidden;margin:0 -2px;padding:2px 2px 6px;-webkit-overflow-scrolling:touch;overscroll-behavior-x:contain}.wg-scroll--fit{overflow-x:hidden;padding-bottom:2px}.wg-scroll--interactive{overflow-x:auto;overflow-y:hidden;height:100%;min-height:inherit;cursor:grab;touch-action:pan-x}.wg-scroll--interactive.is-panning{cursor:grabbing;-webkit-user-select:none;user-select:none}.wg-scroll::-webkit-scrollbar{height:5px}.wg-scroll::-webkit-scrollbar-thumb{background:#e2e8f0;border-radius:6px}.wg-svg{display:block;width:max(100%,var(--wg-min-x, 0px));max-width:none;height:auto;flex-shrink:0}.wg-svg .wg-plot-fill{fill:#f8faff}.wg-svg .wg-plot-fill--cyan{fill:#f0fdfa}.wg-svg .wg-grid line{stroke:#eef2f7;stroke-width:1}.wg-svg .wg-grid-label{font-size:9px;font-weight:600;fill:#94a3b8}.wg-svg .wg-axis{stroke:#e2e8f0;stroke-width:1.2}.wg-svg .wg-x-tick{stroke:#e2e8f0;stroke-width:1}.wg-svg .wg-x-main,.wg-svg .wg-x-label{font-size:9px;font-weight:600;fill:var(--text, #0f172a)}.wg-svg .wg-x-label{fill:#64748b}.wg-svg .wg-band{stroke:none}.wg-svg .wg-ref{stroke:#cbd5e1;stroke-width:1;stroke-dasharray:4 3;opacity:.9}.wg-svg .wg-median{stroke:#94a3b8;stroke-width:2;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-median--dash{stroke:#94a3b8;stroke-width:2}.wg-svg .wg-actual{fill:none;stroke:#6366f1;stroke-width:2.6;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-actual--dash{stroke:var(--primary, #5b6cff);stroke-width:2.4}.wg-svg .wg-actual--height{stroke:#0ea5e9;stroke-width:2.6}.wg-svg .wg-area{stroke:none}.wg-svg .wg-node{fill:#fff;stroke:#6366f1;stroke-width:2}.wg-svg .wg-node--dash{fill:var(--primary, #5b6cff);stroke:#fff;stroke-width:1.5}.wg-svg .wg-node--dash.wg-node--active{stroke:var(--primary, #5b6cff);stroke-width:2.5}.wg-svg .wg-node--cyan{stroke:#0ea5e9;fill:#0ea5e9}.wg-svg .wg-node--vel{stroke:#0f766e}.wg-svg .wg-node-core{fill:#4f46e5;stroke:none}.wg-svg .wg-node-core--cyan{fill:#0284c7}.wg-svg .wg-node-core--vel{fill:#0d9488}.wg-svg .wg-value-label{font-size:10px;font-weight:800;fill:var(--text, #0f172a);pointer-events:none}.wg-svg .wg-age-label{font-size:9px;font-weight:600;fill:#64748b}.wg-svg .wg-zero{stroke:#cbd5e1;stroke-width:1;stroke-dasharray:4 3}.wg-svg .wg-velocity{stroke:#0d9488;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}.wg-svg .wg-bar{fill:#10b981}.wg-svg .wg-ref-line{stroke:#94a3b8;stroke-width:1.2;stroke-dasharray:4 3}.wg-svg .wg-node-hit{fill:transparent;stroke:none;cursor:pointer}.wg-svg .wg-point{cursor:pointer}:host.wg-dashboard{gap:12px}:host.wg-dashboard .wg-svg--compare-dash,:host.wg-dashboard .wg-svg--velocity-line{display:block;max-width:none;flex-shrink:0}:host.wg-dashboard .wg-grid-label{font-size:10px}:host.wg-dashboard .wg-value-label{font-size:10px}:host.wg-dashboard .wg-x-main,:host.wg-dashboard .wg-x-label{font-size:10px}\n"] }]
12721
+ }], propDecorators: { weightVsStandardChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightVsStandardChart", required: false }] }], heightVsStandardChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "heightVsStandardChart", required: false }] }], weightTrendChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightTrendChart", required: false }] }], simpleTrendMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "simpleTrendMode", required: false }] }], weightVelocityLineChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "weightVelocityLineChart", required: false }] }], heightVelocityLineChart: [{ type: i0.Input, args: [{ isSignal: true, alias: "heightVelocityLineChart", required: false }] }], dashboardMode: [{ type: i0.Input, args: [{ isSignal: true, alias: "dashboardMode", required: false }] }], comparePeriod: [{ type: i0.Input, args: [{ isSignal: true, alias: "comparePeriod", required: false }] }], growthMetric: [{ type: i0.Input, args: [{ isSignal: true, alias: "growthMetric", required: false }] }] } });
12570
12722
 
12571
12723
  class WeightHeaderComponent {
12572
12724
  constructor() {
@@ -12579,6 +12731,12 @@ class WeightHeaderComponent {
12579
12731
  this.latestHeightEval = input(null, ...(ngDevMode ? [{ debugName: "latestHeightEval" }] : /* istanbul ignore next */ []));
12580
12732
  this.sortedLogsDesc = input([], ...(ngDevMode ? [{ debugName: "sortedLogsDesc" }] : /* istanbul ignore next */ []));
12581
12733
  this.latestHeightLog = input(null, ...(ngDevMode ? [{ debugName: "latestHeightLog" }] : /* istanbul ignore next */ []));
12734
+ /** Chỉ hiện khối đánh giá (tab Đánh giá). */
12735
+ this.showAssessment = input(true, ...(ngDevMode ? [{ debugName: "showAssessment" }] : /* istanbul ignore next */ []));
12736
+ /** Nút mở bảng WHO (đã chuyển sang tab So sánh). */
12737
+ this.showWhoButton = input(false, ...(ngDevMode ? [{ debugName: "showWhoButton" }] : /* istanbul ignore next */ []));
12738
+ /** CTA timeline phát triển. */
12739
+ this.showTimeline = input(true, ...(ngDevMode ? [{ debugName: "showTimeline" }] : /* istanbul ignore next */ []));
12582
12740
  this.openWhoTable = output();
12583
12741
  this.openTimeline = output();
12584
12742
  }
@@ -12624,23 +12782,26 @@ class WeightHeaderComponent {
12624
12782
  return `Hiện tại bé cao hơn mốc tham chiếu khoảng ${absDiff} cm. Thường là khác biệt cá thể, tiếp tục theo dõi xu hướng.`;
12625
12783
  }
12626
12784
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightHeaderComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12627
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightHeaderComponent, isStandalone: true, selector: "app-weight-header", inputs: { errorMsg: { classPropertyName: "errorMsg", publicName: "errorMsg", isSignal: true, isRequired: false, transformFunction: null }, successMsg: { classPropertyName: "successMsg", publicName: "successMsg", isSignal: true, isRequired: false, transformFunction: null }, birthDate: { classPropertyName: "birthDate", publicName: "birthDate", isSignal: true, isRequired: false, transformFunction: null }, currentTimelineMilestone: { classPropertyName: "currentTimelineMilestone", publicName: "currentTimelineMilestone", isSignal: true, isRequired: false, transformFunction: null }, timelineStats: { classPropertyName: "timelineStats", publicName: "timelineStats", isSignal: true, isRequired: false, transformFunction: null }, latestGrowthEval: { classPropertyName: "latestGrowthEval", publicName: "latestGrowthEval", isSignal: true, isRequired: false, transformFunction: null }, latestHeightEval: { classPropertyName: "latestHeightEval", publicName: "latestHeightEval", isSignal: true, isRequired: false, transformFunction: null }, sortedLogsDesc: { classPropertyName: "sortedLogsDesc", publicName: "sortedLogsDesc", isSignal: true, isRequired: false, transformFunction: null }, latestHeightLog: { classPropertyName: "latestHeightLog", publicName: "latestHeightLog", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openWhoTable: "openWhoTable", openTimeline: "openTimeline" }, ngImport: i0, template: "<div class=\"wh-root\">\n <div class=\"wh-alerts\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n </div>\n\n <div class=\"wh-toolbar\">\n @if (!birthDate()) {\n <p class=\"wh-hint\">\n <i class=\"pi pi-calendar\"></i>\n Th\u00EAm <strong>ng\u00E0y sinh</strong> (tab C\u1EEF b\u00FA) \u0111\u1EC3 c\u00F3 chu\u1EA9n WHO v\u00E0 timeline.\n </p>\n }\n <button\n type=\"button\"\n class=\"wh-pill-btn\"\n (click)=\"openWhoTable.emit()\"\n title=\"B\u1EA3ng c\u00E2n n\u1EB7ng chu\u1EA9n WHO theo tu\u1ED5i\"\n >\n <i class=\"pi pi-table\"></i>\n B\u1EA3ng chu\u1EA9n WHO\n </button>\n </div>\n\n @if (birthDate() && currentTimelineMilestone(); as cur) {\n <button\n type=\"button\"\n class=\"wh-timeline-cta\"\n (click)=\"openTimeline.emit()\"\n [title]=\"'Xem d\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n c\u1EE7a b\u00E9'\"\n >\n <span class=\"wh-timeline-cta__emoji\">{{ cur.heroEmoji }}</span>\n <span class=\"wh-timeline-cta__body\">\n <span class=\"wh-timeline-cta__label\">\n <i class=\"pi pi-chart-line\"></i>\n Timeline ph\u00E1t tri\u1EC3n\n </span>\n <span class=\"wh-timeline-cta__sub\">{{ cur.periodLabel }} \u00B7 {{ cur.tagline }}</span>\n </span>\n <span class=\"wh-timeline-cta__stat\">\n {{ timelineStats().past }}/{{ timelineStats().total }}\n </span>\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n }\n\n @if (latestGrowthEval() || latestHeightEval()) {\n <section class=\"wh-assessment\">\n <header class=\"wh-assessment__head\">\n <div class=\"wh-assessment__eyebrow\">\n <i class=\"pi pi-heart\"></i>\n \u0110\u00E1nh gi\u00E1 t\u0103ng tr\u01B0\u1EDFng g\u1EA7n nh\u1EA5t\n </div>\n <p class=\"wh-assessment__sub\">Theo chu\u1EA9n WHO (0-24 th\u00E1ng) \u00B7 d\u00F9ng \u0111\u1EC3 tham kh\u1EA3o</p>\n </header>\n <div class=\"wh-assessment__grid\">\n @if (latestGrowthEval(); as ev) {\n <article class=\"wh-eval-card\" [attr.data-status]=\"statusClass(ev.status)\">\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-heart-fill\"></i>\n C\u00E2n n\u1EB7ng theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(ev.status)\">\n {{ ev.statusLabel }}\n </span>\n </div>\n @if (sortedLogsDesc()[0]; as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatKg(last.weightKg) }} kg \u00B7 {{ ev.weeksLabel }}\n </p>\n }\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.currentWeightKg) }} kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Chu\u1EA9n</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.standardMinKg) }}-{{\n formatKg(ev.estimate.standardMaxKg)\n }}\n kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{ weightDeltaLabel(ev) }}</strong>\n </div>\n </div>\n <p class=\"wh-eval-card__detail wh-eval-card__detail--muted\">\n Thi\u1EBFu c\u00E2n &lt; {{ formatKg(ev.estimate.underweightMaxKg) }} kg \u00B7 Th\u1EEBa c\u00E2n &gt;\n {{ formatKg(ev.estimate.overweightMinKg) }} kg\n </p>\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(ev.status)\"\n [style.left.%]=\"growthMarkerPosition(ev.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\">z {{ formatZScore(ev.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n </article>\n }\n @if (latestHeightEval(); as hv) {\n <article\n class=\"wh-eval-card wh-eval-card--height\"\n [attr.data-status]=\"statusClass(hv.status)\"\n >\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-arrows-v\"></i>\n Chi\u1EC1u cao theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(hv.status)\">\n {{ hv.statusLabel }}\n </span>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatCm(last.heightCm || 0) }} cm \u00B7\n {{ hv.monthsLabel }}\n </p>\n }\n @if (latestHeightLog(); as last) {\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\">{{ formatCm(last.heightCm || 0) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">M\u1ED1c tham chi\u1EBFu</span>\n <strong class=\"wh-score__value\">{{ formatCm(hv.medianCm) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{\n heightDeltaLabel(hv, last.heightCm || 0)\n }}</strong>\n </div>\n </div>\n }\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(hv.status)\"\n [style.left.%]=\"growthMarkerPosition(hv.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\"\n >P{{ hv.percentile }} \u00B7 z {{ formatZScore(hv.zScore) }}</span\n >\n <span>+2 SD</span>\n </div>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__detail\">\n {{ friendlyHeightDetail(hv, last.heightCm || 0) }}\n </p>\n }\n </article>\n }\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.wh-root{display:flex;flex-direction:column;gap:12px;margin-bottom:4px}.wh-alerts{display:flex;flex-direction:column;gap:10px}.alert{display:flex;align-items:flex-start;gap:8px;padding:11px 14px;border-radius:12px;font-size:13px;line-height:1.45}.alert.error{background:linear-gradient(135deg,#fef2f2,#fff7f7);color:#991b1b;border:1px solid #fecaca}.alert.ok{background:linear-gradient(135deg,#ecfdf5,#f6fffb);color:#065f46;border:1px solid #a7f3d0}.wh-toolbar{display:flex;flex-wrap:wrap;align-items:flex-start;gap:10px 12px}.wh-hint{flex:1 1 220px;margin:0;display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.45;color:var(--muted, #64748b)}.wh-hint i{color:var(--primary, #5b6cff);margin-top:2px}.wh-pill-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border-radius:999px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--text);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 2px 8px -4px #0f172a1f;transition:border-color .15s,background .15s,color .15s,transform .12s}.wh-pill-btn i{color:var(--primary, #5b6cff)}.wh-pill-btn:hover{border-color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff)}.wh-pill-btn:active{transform:scale(.98)}.wh-timeline-cta{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--primary, #5b6cff) 22%,var(--border, #e6e8ef));background:linear-gradient(120deg,color-mix(in srgb,var(--primary, #5b6cff) 8%,#fff),#fff);cursor:pointer;font-family:inherit;text-align:left;box-shadow:0 4px 18px -10px #5b6cff73;transition:transform .15s,box-shadow .15s}.wh-timeline-cta:hover{transform:translateY(-1px);box-shadow:0 8px 26px -12px #5b6cff80}.wh-timeline-cta__emoji{width:44px;height:44px;display:flex;align-items:center;justify-content:center;border-radius:14px;background:#fff;font-size:22px;flex-shrink:0;box-shadow:inset 0 0 0 1px #0f172a0f}.wh-timeline-cta__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.wh-timeline-cta__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--primary, #5b6cff);display:flex;align-items:center;gap:6px}.wh-timeline-cta__label i{font-size:12px}.wh-timeline-cta__sub{font-size:13.5px;font-weight:600;color:var(--text, #0f172a);line-height:1.3}.wh-timeline-cta__stat{flex-shrink:0;padding:4px 10px;border-radius:999px;background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff);font-size:12px;font-weight:800;font-variant-numeric:tabular-nums}.wh-timeline-cta .pi-chevron-right{color:var(--muted, #94a3b8);font-size:13px}.wh-assessment{border-radius:18px;border:1px solid var(--border, #e6e8ef);background:linear-gradient(180deg,#fff,#fbfcff);box-shadow:0 6px 24px -16px #0f172a38;padding:14px}.wh-assessment__head{margin-bottom:10px}.wh-assessment__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--primary, #5b6cff)}.wh-assessment__sub{margin:4px 0 0;font-size:12px;color:var(--muted, #64748b)}.wh-assessment__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:740px){.wh-assessment__grid{grid-template-columns:1fr}}.wh-eval-card{border-radius:14px;border:1px solid var(--border, #e6e8ef);background:#fff;padding:12px;min-width:0}.wh-eval-card[data-status=ok]{border-color:#86efac;background:linear-gradient(180deg,#fff,#f6fff8)}.wh-eval-card[data-status=mid]{border-color:#fde68a;background:linear-gradient(180deg,#fff,#fffcf2)}.wh-eval-card[data-status=warn]{border-color:#fecaca;background:linear-gradient(180deg,#fff,#fff7f7)}.wh-eval-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.wh-eval-card__title{margin:0;font-size:14px;font-weight:800;display:inline-flex;align-items:center;gap:6px;color:var(--text)}.wh-eval-card--height .wh-eval-card__title i{color:#0ea5e9}.wh-eval-card__badge{flex-shrink:0;padding:4px 9px;border-radius:999px;font-size:11px;font-weight:800;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.wh-eval-card__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.wh-eval-card__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.wh-eval-card__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.wh-eval-card__meta{margin:8px 0 0;font-size:12px;color:var(--muted, #64748b);line-height:1.45}.wh-score-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:9px}.wh-score{border-radius:10px;border:1px solid #e2e8f0;background:#fff;padding:7px 8px;min-width:0}.wh-score__label{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#64748b;margin-bottom:3px}.wh-score__value{display:block;font-size:13px;line-height:1.25;color:#0f172a;font-weight:800;font-variant-numeric:tabular-nums}.wh-eval-card__detail{margin:7px 0 0;font-size:12.5px;line-height:1.45;color:var(--text)}.wh-eval-card__detail--muted{color:#64748b}.wh-eval-card__scale{margin-top:10px;padding:10px 10px 8px;border-radius:10px;background:#f8fafc;border:1px solid #e2e8f0}.wh-eval-card__scale-labels{display:grid;grid-template-columns:1fr 2fr 1fr;gap:4px;margin-bottom:8px}.wh-eval-card__scale-zone{text-align:center;font-size:10.5px;font-weight:700;color:#64748b;background:#fff;border-radius:999px;padding:3px 4px;border:1px solid #e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wh-eval-card__scale-zone--mid{color:#166534;border-color:#bbf7d0;background:#f0fdf4}.wh-eval-card__scale-track{position:relative;display:flex;height:10px;border-radius:999px;overflow:visible;border:1px solid #cbd5e1}.wh-eval-card__scale-band{height:100%}.wh-eval-card__scale-band--low{flex:1;background:linear-gradient(180deg,#fee2e2,#fecaca);border-radius:999px 0 0 999px}.wh-eval-card__scale-band--mid{flex:2;background:linear-gradient(180deg,#dcfce7,#bbf7d0)}.wh-eval-card__scale-band--high{flex:1;background:linear-gradient(180deg,#dbeafe,#bfdbfe);border-radius:0 999px 999px 0}.wh-eval-card__scale-marker{position:absolute;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;background:#2563eb;border:2px solid #fff;box-shadow:0 2px 8px #2563eb66;z-index:1}.wh-eval-card__scale-marker[data-status=ok]{background:#059669;box-shadow:0 2px 8px #05966959}.wh-eval-card__scale-marker[data-status=mid]{background:#d97706;box-shadow:0 2px 8px #d9770659}.wh-eval-card__scale-marker[data-status=warn]{background:#dc2626;box-shadow:0 2px 8px #dc262659}.wh-eval-card__scale-legend{display:flex;align-items:center;justify-content:space-between;margin-top:6px;font-size:10px;font-weight:700;color:#94a3b8;letter-spacing:.02em}.wh-eval-card__scale-z{font-variant-numeric:tabular-nums;color:#64748b}@media(max-width:420px){.wh-score-grid{grid-template-columns:1fr}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12785
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightHeaderComponent, isStandalone: true, selector: "app-weight-header", inputs: { errorMsg: { classPropertyName: "errorMsg", publicName: "errorMsg", isSignal: true, isRequired: false, transformFunction: null }, successMsg: { classPropertyName: "successMsg", publicName: "successMsg", isSignal: true, isRequired: false, transformFunction: null }, birthDate: { classPropertyName: "birthDate", publicName: "birthDate", isSignal: true, isRequired: false, transformFunction: null }, currentTimelineMilestone: { classPropertyName: "currentTimelineMilestone", publicName: "currentTimelineMilestone", isSignal: true, isRequired: false, transformFunction: null }, timelineStats: { classPropertyName: "timelineStats", publicName: "timelineStats", isSignal: true, isRequired: false, transformFunction: null }, latestGrowthEval: { classPropertyName: "latestGrowthEval", publicName: "latestGrowthEval", isSignal: true, isRequired: false, transformFunction: null }, latestHeightEval: { classPropertyName: "latestHeightEval", publicName: "latestHeightEval", isSignal: true, isRequired: false, transformFunction: null }, sortedLogsDesc: { classPropertyName: "sortedLogsDesc", publicName: "sortedLogsDesc", isSignal: true, isRequired: false, transformFunction: null }, latestHeightLog: { classPropertyName: "latestHeightLog", publicName: "latestHeightLog", isSignal: true, isRequired: false, transformFunction: null }, showAssessment: { classPropertyName: "showAssessment", publicName: "showAssessment", isSignal: true, isRequired: false, transformFunction: null }, showWhoButton: { classPropertyName: "showWhoButton", publicName: "showWhoButton", isSignal: true, isRequired: false, transformFunction: null }, showTimeline: { classPropertyName: "showTimeline", publicName: "showTimeline", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { openWhoTable: "openWhoTable", openTimeline: "openTimeline" }, ngImport: i0, template: "<div class=\"wh-root\">\n <div class=\"wh-alerts\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n </div>\n\n @if (showWhoButton()) {\n <div class=\"wh-toolbar\">\n @if (!birthDate()) {\n <p class=\"wh-hint\">\n <i class=\"pi pi-calendar\"></i>\n Th\u00EAm <strong>ng\u00E0y sinh</strong> (tab C\u1EEF b\u00FA) \u0111\u1EC3 c\u00F3 chu\u1EA9n WHO v\u00E0 timeline.\n </p>\n }\n <button\n type=\"button\"\n class=\"wh-pill-btn\"\n (click)=\"openWhoTable.emit()\"\n title=\"B\u1EA3ng c\u00E2n n\u1EB7ng chu\u1EA9n WHO theo tu\u1ED5i\"\n >\n <i class=\"pi pi-table\"></i>\n B\u1EA3ng chu\u1EA9n WHO\n </button>\n </div>\n }\n\n @if (showTimeline() && birthDate() && currentTimelineMilestone(); as cur) {\n <button\n type=\"button\"\n class=\"wh-timeline-cta\"\n (click)=\"openTimeline.emit()\"\n [title]=\"'Xem d\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n c\u1EE7a b\u00E9'\"\n >\n <span class=\"wh-timeline-cta__emoji\">{{ cur.heroEmoji }}</span>\n <span class=\"wh-timeline-cta__body\">\n <span class=\"wh-timeline-cta__label\">\n <i class=\"pi pi-chart-line\"></i>\n Timeline ph\u00E1t tri\u1EC3n\n </span>\n <span class=\"wh-timeline-cta__sub\">{{ cur.periodLabel }} \u00B7 {{ cur.tagline }}</span>\n </span>\n <span class=\"wh-timeline-cta__stat\">\n {{ timelineStats().past }}/{{ timelineStats().total }}\n </span>\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n }\n\n @if (showAssessment() && (latestGrowthEval() || latestHeightEval())) {\n <section class=\"wh-assessment\">\n <header class=\"wh-assessment__head\">\n <div class=\"wh-assessment__eyebrow\">\n <i class=\"pi pi-heart\"></i>\n \u0110\u00E1nh gi\u00E1 t\u0103ng tr\u01B0\u1EDFng g\u1EA7n nh\u1EA5t\n </div>\n <p class=\"wh-assessment__sub\">Theo chu\u1EA9n WHO (0-24 th\u00E1ng) \u00B7 d\u00F9ng \u0111\u1EC3 tham kh\u1EA3o</p>\n </header>\n <div class=\"wh-assessment__grid\">\n @if (latestGrowthEval(); as ev) {\n <article class=\"wh-eval-card\" [attr.data-status]=\"statusClass(ev.status)\">\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-heart-fill\"></i>\n C\u00E2n n\u1EB7ng theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(ev.status)\">\n {{ ev.statusLabel }}\n </span>\n </div>\n @if (sortedLogsDesc()[0]; as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatKg(last.weightKg) }} kg \u00B7 {{ ev.weeksLabel }}\n </p>\n }\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.currentWeightKg) }} kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Chu\u1EA9n</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.standardMinKg) }}-{{\n formatKg(ev.estimate.standardMaxKg)\n }}\n kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{ weightDeltaLabel(ev) }}</strong>\n </div>\n </div>\n <p class=\"wh-eval-card__detail wh-eval-card__detail--muted\">\n Thi\u1EBFu c\u00E2n &lt; {{ formatKg(ev.estimate.underweightMaxKg) }} kg \u00B7 Th\u1EEBa c\u00E2n &gt;\n {{ formatKg(ev.estimate.overweightMinKg) }} kg\n </p>\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(ev.status)\"\n [style.left.%]=\"growthMarkerPosition(ev.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\">z {{ formatZScore(ev.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n </article>\n }\n @if (latestHeightEval(); as hv) {\n <article\n class=\"wh-eval-card wh-eval-card--height\"\n [attr.data-status]=\"statusClass(hv.status)\"\n >\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-arrows-v\"></i>\n Chi\u1EC1u cao theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(hv.status)\">\n {{ hv.statusLabel }}\n </span>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatCm(last.heightCm || 0) }} cm \u00B7\n {{ hv.monthsLabel }}\n </p>\n }\n @if (latestHeightLog(); as last) {\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\">{{ formatCm(last.heightCm || 0) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">M\u1ED1c tham chi\u1EBFu</span>\n <strong class=\"wh-score__value\">{{ formatCm(hv.medianCm) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{\n heightDeltaLabel(hv, last.heightCm || 0)\n }}</strong>\n </div>\n </div>\n }\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(hv.status)\"\n [style.left.%]=\"growthMarkerPosition(hv.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\"\n >P{{ hv.percentile }} \u00B7 z {{ formatZScore(hv.zScore) }}</span\n >\n <span>+2 SD</span>\n </div>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__detail\">\n {{ friendlyHeightDetail(hv, last.heightCm || 0) }}\n </p>\n }\n </article>\n }\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.wh-root{display:flex;flex-direction:column;gap:12px;margin-bottom:4px}.wh-alerts{display:flex;flex-direction:column;gap:10px}.alert{display:flex;align-items:flex-start;gap:8px;padding:11px 14px;border-radius:12px;font-size:13px;line-height:1.45}.alert.error{background:linear-gradient(135deg,#fef2f2,#fff7f7);color:#991b1b;border:1px solid #fecaca}.alert.ok{background:linear-gradient(135deg,#ecfdf5,#f6fffb);color:#065f46;border:1px solid #a7f3d0}.wh-toolbar{display:flex;flex-wrap:wrap;align-items:flex-start;gap:10px 12px}.wh-hint{flex:1 1 220px;margin:0;display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.45;color:var(--muted, #64748b)}.wh-hint i{color:var(--primary, #5b6cff);margin-top:2px}.wh-pill-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border-radius:999px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--text);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 2px 8px -4px #0f172a1f;transition:border-color .15s,background .15s,color .15s,transform .12s}.wh-pill-btn i{color:var(--primary, #5b6cff)}.wh-pill-btn:hover{border-color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff)}.wh-pill-btn:active{transform:scale(.98)}.wh-timeline-cta{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--primary, #5b6cff) 22%,var(--border, #e6e8ef));background:linear-gradient(120deg,color-mix(in srgb,var(--primary, #5b6cff) 8%,#fff),#fff);cursor:pointer;font-family:inherit;text-align:left;box-shadow:0 4px 18px -10px #5b6cff73;transition:transform .15s,box-shadow .15s}.wh-timeline-cta:hover{transform:translateY(-1px);box-shadow:0 8px 26px -12px #5b6cff80}.wh-timeline-cta__emoji{width:44px;height:44px;display:flex;align-items:center;justify-content:center;border-radius:14px;background:#fff;font-size:22px;flex-shrink:0;box-shadow:inset 0 0 0 1px #0f172a0f}.wh-timeline-cta__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.wh-timeline-cta__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--primary, #5b6cff);display:flex;align-items:center;gap:6px}.wh-timeline-cta__label i{font-size:12px}.wh-timeline-cta__sub{font-size:13.5px;font-weight:600;color:var(--text, #0f172a);line-height:1.3}.wh-timeline-cta__stat{flex-shrink:0;padding:4px 10px;border-radius:999px;background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff);font-size:12px;font-weight:800;font-variant-numeric:tabular-nums}.wh-timeline-cta .pi-chevron-right{color:var(--muted, #94a3b8);font-size:13px}.wh-assessment{border-radius:18px;border:1px solid var(--border, #e6e8ef);background:linear-gradient(180deg,#fff,#fbfcff);box-shadow:0 6px 24px -16px #0f172a38;padding:14px}.wh-assessment__head{margin-bottom:10px}.wh-assessment__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--primary, #5b6cff)}.wh-assessment__sub{margin:4px 0 0;font-size:12px;color:var(--muted, #64748b)}.wh-assessment__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:740px){.wh-assessment__grid{grid-template-columns:1fr}}.wh-eval-card{border-radius:14px;border:1px solid var(--border, #e6e8ef);background:#fff;padding:12px;min-width:0}.wh-eval-card[data-status=ok]{border-color:#86efac;background:linear-gradient(180deg,#fff,#f6fff8)}.wh-eval-card[data-status=mid]{border-color:#fde68a;background:linear-gradient(180deg,#fff,#fffcf2)}.wh-eval-card[data-status=warn]{border-color:#fecaca;background:linear-gradient(180deg,#fff,#fff7f7)}.wh-eval-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.wh-eval-card__title{margin:0;font-size:14px;font-weight:800;display:inline-flex;align-items:center;gap:6px;color:var(--text)}.wh-eval-card--height .wh-eval-card__title i{color:#0ea5e9}.wh-eval-card__badge{flex-shrink:0;padding:4px 9px;border-radius:999px;font-size:11px;font-weight:800;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.wh-eval-card__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.wh-eval-card__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.wh-eval-card__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.wh-eval-card__meta{margin:8px 0 0;font-size:12px;color:var(--muted, #64748b);line-height:1.45}.wh-score-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:9px}.wh-score{border-radius:10px;border:1px solid #e2e8f0;background:#fff;padding:7px 8px;min-width:0}.wh-score__label{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#64748b;margin-bottom:3px}.wh-score__value{display:block;font-size:13px;line-height:1.25;color:#0f172a;font-weight:800;font-variant-numeric:tabular-nums}.wh-eval-card__detail{margin:7px 0 0;font-size:12.5px;line-height:1.45;color:var(--text)}.wh-eval-card__detail--muted{color:#64748b}.wh-eval-card__scale{margin-top:10px;padding:10px 10px 8px;border-radius:10px;background:#f8fafc;border:1px solid #e2e8f0}.wh-eval-card__scale-labels{display:grid;grid-template-columns:1fr 2fr 1fr;gap:4px;margin-bottom:8px}.wh-eval-card__scale-zone{text-align:center;font-size:10.5px;font-weight:700;color:#64748b;background:#fff;border-radius:999px;padding:3px 4px;border:1px solid #e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wh-eval-card__scale-zone--mid{color:#166534;border-color:#bbf7d0;background:#f0fdf4}.wh-eval-card__scale-track{position:relative;display:flex;height:10px;border-radius:999px;overflow:visible;border:1px solid #cbd5e1}.wh-eval-card__scale-band{height:100%}.wh-eval-card__scale-band--low{flex:1;background:linear-gradient(180deg,#fee2e2,#fecaca);border-radius:999px 0 0 999px}.wh-eval-card__scale-band--mid{flex:2;background:linear-gradient(180deg,#dcfce7,#bbf7d0)}.wh-eval-card__scale-band--high{flex:1;background:linear-gradient(180deg,#dbeafe,#bfdbfe);border-radius:0 999px 999px 0}.wh-eval-card__scale-marker{position:absolute;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;background:#2563eb;border:2px solid #fff;box-shadow:0 2px 8px #2563eb66;z-index:1}.wh-eval-card__scale-marker[data-status=ok]{background:#059669;box-shadow:0 2px 8px #05966959}.wh-eval-card__scale-marker[data-status=mid]{background:#d97706;box-shadow:0 2px 8px #d9770659}.wh-eval-card__scale-marker[data-status=warn]{background:#dc2626;box-shadow:0 2px 8px #dc262659}.wh-eval-card__scale-legend{display:flex;align-items:center;justify-content:space-between;margin-top:6px;font-size:10px;font-weight:700;color:#94a3b8;letter-spacing:.02em}.wh-eval-card__scale-z{font-variant-numeric:tabular-nums;color:#64748b}@media(max-width:420px){.wh-score-grid{grid-template-columns:1fr}}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12628
12786
  }
12629
12787
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightHeaderComponent, decorators: [{
12630
12788
  type: Component,
12631
- args: [{ selector: 'app-weight-header', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"wh-root\">\n <div class=\"wh-alerts\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n </div>\n\n <div class=\"wh-toolbar\">\n @if (!birthDate()) {\n <p class=\"wh-hint\">\n <i class=\"pi pi-calendar\"></i>\n Th\u00EAm <strong>ng\u00E0y sinh</strong> (tab C\u1EEF b\u00FA) \u0111\u1EC3 c\u00F3 chu\u1EA9n WHO v\u00E0 timeline.\n </p>\n }\n <button\n type=\"button\"\n class=\"wh-pill-btn\"\n (click)=\"openWhoTable.emit()\"\n title=\"B\u1EA3ng c\u00E2n n\u1EB7ng chu\u1EA9n WHO theo tu\u1ED5i\"\n >\n <i class=\"pi pi-table\"></i>\n B\u1EA3ng chu\u1EA9n WHO\n </button>\n </div>\n\n @if (birthDate() && currentTimelineMilestone(); as cur) {\n <button\n type=\"button\"\n class=\"wh-timeline-cta\"\n (click)=\"openTimeline.emit()\"\n [title]=\"'Xem d\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n c\u1EE7a b\u00E9'\"\n >\n <span class=\"wh-timeline-cta__emoji\">{{ cur.heroEmoji }}</span>\n <span class=\"wh-timeline-cta__body\">\n <span class=\"wh-timeline-cta__label\">\n <i class=\"pi pi-chart-line\"></i>\n Timeline ph\u00E1t tri\u1EC3n\n </span>\n <span class=\"wh-timeline-cta__sub\">{{ cur.periodLabel }} \u00B7 {{ cur.tagline }}</span>\n </span>\n <span class=\"wh-timeline-cta__stat\">\n {{ timelineStats().past }}/{{ timelineStats().total }}\n </span>\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n }\n\n @if (latestGrowthEval() || latestHeightEval()) {\n <section class=\"wh-assessment\">\n <header class=\"wh-assessment__head\">\n <div class=\"wh-assessment__eyebrow\">\n <i class=\"pi pi-heart\"></i>\n \u0110\u00E1nh gi\u00E1 t\u0103ng tr\u01B0\u1EDFng g\u1EA7n nh\u1EA5t\n </div>\n <p class=\"wh-assessment__sub\">Theo chu\u1EA9n WHO (0-24 th\u00E1ng) \u00B7 d\u00F9ng \u0111\u1EC3 tham kh\u1EA3o</p>\n </header>\n <div class=\"wh-assessment__grid\">\n @if (latestGrowthEval(); as ev) {\n <article class=\"wh-eval-card\" [attr.data-status]=\"statusClass(ev.status)\">\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-heart-fill\"></i>\n C\u00E2n n\u1EB7ng theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(ev.status)\">\n {{ ev.statusLabel }}\n </span>\n </div>\n @if (sortedLogsDesc()[0]; as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatKg(last.weightKg) }} kg \u00B7 {{ ev.weeksLabel }}\n </p>\n }\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.currentWeightKg) }} kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Chu\u1EA9n</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.standardMinKg) }}-{{\n formatKg(ev.estimate.standardMaxKg)\n }}\n kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{ weightDeltaLabel(ev) }}</strong>\n </div>\n </div>\n <p class=\"wh-eval-card__detail wh-eval-card__detail--muted\">\n Thi\u1EBFu c\u00E2n &lt; {{ formatKg(ev.estimate.underweightMaxKg) }} kg \u00B7 Th\u1EEBa c\u00E2n &gt;\n {{ formatKg(ev.estimate.overweightMinKg) }} kg\n </p>\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(ev.status)\"\n [style.left.%]=\"growthMarkerPosition(ev.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\">z {{ formatZScore(ev.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n </article>\n }\n @if (latestHeightEval(); as hv) {\n <article\n class=\"wh-eval-card wh-eval-card--height\"\n [attr.data-status]=\"statusClass(hv.status)\"\n >\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-arrows-v\"></i>\n Chi\u1EC1u cao theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(hv.status)\">\n {{ hv.statusLabel }}\n </span>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatCm(last.heightCm || 0) }} cm \u00B7\n {{ hv.monthsLabel }}\n </p>\n }\n @if (latestHeightLog(); as last) {\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\">{{ formatCm(last.heightCm || 0) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">M\u1ED1c tham chi\u1EBFu</span>\n <strong class=\"wh-score__value\">{{ formatCm(hv.medianCm) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{\n heightDeltaLabel(hv, last.heightCm || 0)\n }}</strong>\n </div>\n </div>\n }\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(hv.status)\"\n [style.left.%]=\"growthMarkerPosition(hv.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\"\n >P{{ hv.percentile }} \u00B7 z {{ formatZScore(hv.zScore) }}</span\n >\n <span>+2 SD</span>\n </div>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__detail\">\n {{ friendlyHeightDetail(hv, last.heightCm || 0) }}\n </p>\n }\n </article>\n }\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.wh-root{display:flex;flex-direction:column;gap:12px;margin-bottom:4px}.wh-alerts{display:flex;flex-direction:column;gap:10px}.alert{display:flex;align-items:flex-start;gap:8px;padding:11px 14px;border-radius:12px;font-size:13px;line-height:1.45}.alert.error{background:linear-gradient(135deg,#fef2f2,#fff7f7);color:#991b1b;border:1px solid #fecaca}.alert.ok{background:linear-gradient(135deg,#ecfdf5,#f6fffb);color:#065f46;border:1px solid #a7f3d0}.wh-toolbar{display:flex;flex-wrap:wrap;align-items:flex-start;gap:10px 12px}.wh-hint{flex:1 1 220px;margin:0;display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.45;color:var(--muted, #64748b)}.wh-hint i{color:var(--primary, #5b6cff);margin-top:2px}.wh-pill-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border-radius:999px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--text);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 2px 8px -4px #0f172a1f;transition:border-color .15s,background .15s,color .15s,transform .12s}.wh-pill-btn i{color:var(--primary, #5b6cff)}.wh-pill-btn:hover{border-color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff)}.wh-pill-btn:active{transform:scale(.98)}.wh-timeline-cta{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--primary, #5b6cff) 22%,var(--border, #e6e8ef));background:linear-gradient(120deg,color-mix(in srgb,var(--primary, #5b6cff) 8%,#fff),#fff);cursor:pointer;font-family:inherit;text-align:left;box-shadow:0 4px 18px -10px #5b6cff73;transition:transform .15s,box-shadow .15s}.wh-timeline-cta:hover{transform:translateY(-1px);box-shadow:0 8px 26px -12px #5b6cff80}.wh-timeline-cta__emoji{width:44px;height:44px;display:flex;align-items:center;justify-content:center;border-radius:14px;background:#fff;font-size:22px;flex-shrink:0;box-shadow:inset 0 0 0 1px #0f172a0f}.wh-timeline-cta__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.wh-timeline-cta__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--primary, #5b6cff);display:flex;align-items:center;gap:6px}.wh-timeline-cta__label i{font-size:12px}.wh-timeline-cta__sub{font-size:13.5px;font-weight:600;color:var(--text, #0f172a);line-height:1.3}.wh-timeline-cta__stat{flex-shrink:0;padding:4px 10px;border-radius:999px;background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff);font-size:12px;font-weight:800;font-variant-numeric:tabular-nums}.wh-timeline-cta .pi-chevron-right{color:var(--muted, #94a3b8);font-size:13px}.wh-assessment{border-radius:18px;border:1px solid var(--border, #e6e8ef);background:linear-gradient(180deg,#fff,#fbfcff);box-shadow:0 6px 24px -16px #0f172a38;padding:14px}.wh-assessment__head{margin-bottom:10px}.wh-assessment__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--primary, #5b6cff)}.wh-assessment__sub{margin:4px 0 0;font-size:12px;color:var(--muted, #64748b)}.wh-assessment__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:740px){.wh-assessment__grid{grid-template-columns:1fr}}.wh-eval-card{border-radius:14px;border:1px solid var(--border, #e6e8ef);background:#fff;padding:12px;min-width:0}.wh-eval-card[data-status=ok]{border-color:#86efac;background:linear-gradient(180deg,#fff,#f6fff8)}.wh-eval-card[data-status=mid]{border-color:#fde68a;background:linear-gradient(180deg,#fff,#fffcf2)}.wh-eval-card[data-status=warn]{border-color:#fecaca;background:linear-gradient(180deg,#fff,#fff7f7)}.wh-eval-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.wh-eval-card__title{margin:0;font-size:14px;font-weight:800;display:inline-flex;align-items:center;gap:6px;color:var(--text)}.wh-eval-card--height .wh-eval-card__title i{color:#0ea5e9}.wh-eval-card__badge{flex-shrink:0;padding:4px 9px;border-radius:999px;font-size:11px;font-weight:800;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.wh-eval-card__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.wh-eval-card__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.wh-eval-card__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.wh-eval-card__meta{margin:8px 0 0;font-size:12px;color:var(--muted, #64748b);line-height:1.45}.wh-score-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:9px}.wh-score{border-radius:10px;border:1px solid #e2e8f0;background:#fff;padding:7px 8px;min-width:0}.wh-score__label{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#64748b;margin-bottom:3px}.wh-score__value{display:block;font-size:13px;line-height:1.25;color:#0f172a;font-weight:800;font-variant-numeric:tabular-nums}.wh-eval-card__detail{margin:7px 0 0;font-size:12.5px;line-height:1.45;color:var(--text)}.wh-eval-card__detail--muted{color:#64748b}.wh-eval-card__scale{margin-top:10px;padding:10px 10px 8px;border-radius:10px;background:#f8fafc;border:1px solid #e2e8f0}.wh-eval-card__scale-labels{display:grid;grid-template-columns:1fr 2fr 1fr;gap:4px;margin-bottom:8px}.wh-eval-card__scale-zone{text-align:center;font-size:10.5px;font-weight:700;color:#64748b;background:#fff;border-radius:999px;padding:3px 4px;border:1px solid #e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wh-eval-card__scale-zone--mid{color:#166534;border-color:#bbf7d0;background:#f0fdf4}.wh-eval-card__scale-track{position:relative;display:flex;height:10px;border-radius:999px;overflow:visible;border:1px solid #cbd5e1}.wh-eval-card__scale-band{height:100%}.wh-eval-card__scale-band--low{flex:1;background:linear-gradient(180deg,#fee2e2,#fecaca);border-radius:999px 0 0 999px}.wh-eval-card__scale-band--mid{flex:2;background:linear-gradient(180deg,#dcfce7,#bbf7d0)}.wh-eval-card__scale-band--high{flex:1;background:linear-gradient(180deg,#dbeafe,#bfdbfe);border-radius:0 999px 999px 0}.wh-eval-card__scale-marker{position:absolute;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;background:#2563eb;border:2px solid #fff;box-shadow:0 2px 8px #2563eb66;z-index:1}.wh-eval-card__scale-marker[data-status=ok]{background:#059669;box-shadow:0 2px 8px #05966959}.wh-eval-card__scale-marker[data-status=mid]{background:#d97706;box-shadow:0 2px 8px #d9770659}.wh-eval-card__scale-marker[data-status=warn]{background:#dc2626;box-shadow:0 2px 8px #dc262659}.wh-eval-card__scale-legend{display:flex;align-items:center;justify-content:space-between;margin-top:6px;font-size:10px;font-weight:700;color:#94a3b8;letter-spacing:.02em}.wh-eval-card__scale-z{font-variant-numeric:tabular-nums;color:#64748b}@media(max-width:420px){.wh-score-grid{grid-template-columns:1fr}}\n"] }]
12632
- }], propDecorators: { errorMsg: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMsg", required: false }] }], successMsg: [{ type: i0.Input, args: [{ isSignal: true, alias: "successMsg", required: false }] }], birthDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "birthDate", required: false }] }], currentTimelineMilestone: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTimelineMilestone", required: false }] }], timelineStats: [{ type: i0.Input, args: [{ isSignal: true, alias: "timelineStats", required: false }] }], latestGrowthEval: [{ type: i0.Input, args: [{ isSignal: true, alias: "latestGrowthEval", required: false }] }], latestHeightEval: [{ type: i0.Input, args: [{ isSignal: true, alias: "latestHeightEval", required: false }] }], sortedLogsDesc: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortedLogsDesc", required: false }] }], latestHeightLog: [{ type: i0.Input, args: [{ isSignal: true, alias: "latestHeightLog", required: false }] }], openWhoTable: [{ type: i0.Output, args: ["openWhoTable"] }], openTimeline: [{ type: i0.Output, args: ["openTimeline"] }] } });
12789
+ args: [{ selector: 'app-weight-header', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"wh-root\">\n <div class=\"wh-alerts\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n </div>\n\n @if (showWhoButton()) {\n <div class=\"wh-toolbar\">\n @if (!birthDate()) {\n <p class=\"wh-hint\">\n <i class=\"pi pi-calendar\"></i>\n Th\u00EAm <strong>ng\u00E0y sinh</strong> (tab C\u1EEF b\u00FA) \u0111\u1EC3 c\u00F3 chu\u1EA9n WHO v\u00E0 timeline.\n </p>\n }\n <button\n type=\"button\"\n class=\"wh-pill-btn\"\n (click)=\"openWhoTable.emit()\"\n title=\"B\u1EA3ng c\u00E2n n\u1EB7ng chu\u1EA9n WHO theo tu\u1ED5i\"\n >\n <i class=\"pi pi-table\"></i>\n B\u1EA3ng chu\u1EA9n WHO\n </button>\n </div>\n }\n\n @if (showTimeline() && birthDate() && currentTimelineMilestone(); as cur) {\n <button\n type=\"button\"\n class=\"wh-timeline-cta\"\n (click)=\"openTimeline.emit()\"\n [title]=\"'Xem d\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n c\u1EE7a b\u00E9'\"\n >\n <span class=\"wh-timeline-cta__emoji\">{{ cur.heroEmoji }}</span>\n <span class=\"wh-timeline-cta__body\">\n <span class=\"wh-timeline-cta__label\">\n <i class=\"pi pi-chart-line\"></i>\n Timeline ph\u00E1t tri\u1EC3n\n </span>\n <span class=\"wh-timeline-cta__sub\">{{ cur.periodLabel }} \u00B7 {{ cur.tagline }}</span>\n </span>\n <span class=\"wh-timeline-cta__stat\">\n {{ timelineStats().past }}/{{ timelineStats().total }}\n </span>\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n }\n\n @if (showAssessment() && (latestGrowthEval() || latestHeightEval())) {\n <section class=\"wh-assessment\">\n <header class=\"wh-assessment__head\">\n <div class=\"wh-assessment__eyebrow\">\n <i class=\"pi pi-heart\"></i>\n \u0110\u00E1nh gi\u00E1 t\u0103ng tr\u01B0\u1EDFng g\u1EA7n nh\u1EA5t\n </div>\n <p class=\"wh-assessment__sub\">Theo chu\u1EA9n WHO (0-24 th\u00E1ng) \u00B7 d\u00F9ng \u0111\u1EC3 tham kh\u1EA3o</p>\n </header>\n <div class=\"wh-assessment__grid\">\n @if (latestGrowthEval(); as ev) {\n <article class=\"wh-eval-card\" [attr.data-status]=\"statusClass(ev.status)\">\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-heart-fill\"></i>\n C\u00E2n n\u1EB7ng theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(ev.status)\">\n {{ ev.statusLabel }}\n </span>\n </div>\n @if (sortedLogsDesc()[0]; as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatKg(last.weightKg) }} kg \u00B7 {{ ev.weeksLabel }}\n </p>\n }\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.currentWeightKg) }} kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Chu\u1EA9n</span>\n <strong class=\"wh-score__value\"\n >{{ formatKg(ev.estimate.standardMinKg) }}-{{\n formatKg(ev.estimate.standardMaxKg)\n }}\n kg</strong\n >\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{ weightDeltaLabel(ev) }}</strong>\n </div>\n </div>\n <p class=\"wh-eval-card__detail wh-eval-card__detail--muted\">\n Thi\u1EBFu c\u00E2n &lt; {{ formatKg(ev.estimate.underweightMaxKg) }} kg \u00B7 Th\u1EEBa c\u00E2n &gt;\n {{ formatKg(ev.estimate.overweightMinKg) }} kg\n </p>\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(ev.status)\"\n [style.left.%]=\"growthMarkerPosition(ev.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\">z {{ formatZScore(ev.zScore) }}</span>\n <span>+2 SD</span>\n </div>\n </div>\n </article>\n }\n @if (latestHeightEval(); as hv) {\n <article\n class=\"wh-eval-card wh-eval-card--height\"\n [attr.data-status]=\"statusClass(hv.status)\"\n >\n <div class=\"wh-eval-card__head\">\n <h3 class=\"wh-eval-card__title\">\n <i class=\"pi pi-arrows-v\"></i>\n Chi\u1EC1u cao theo tu\u1ED5i\n </h3>\n <span class=\"wh-eval-card__badge\" [attr.data-status]=\"statusClass(hv.status)\">\n {{ hv.statusLabel }}\n </span>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__meta\">\n {{ formatDate(last.date) }} \u00B7 {{ formatCm(last.heightCm || 0) }} cm \u00B7\n {{ hv.monthsLabel }}\n </p>\n }\n @if (latestHeightLog(); as last) {\n <div class=\"wh-score-grid\">\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">Hi\u1EC7n t\u1EA1i</span>\n <strong class=\"wh-score__value\">{{ formatCm(last.heightCm || 0) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">M\u1ED1c tham chi\u1EBFu</span>\n <strong class=\"wh-score__value\">{{ formatCm(hv.medianCm) }} cm</strong>\n </div>\n <div class=\"wh-score\">\n <span class=\"wh-score__label\">L\u1EC7ch</span>\n <strong class=\"wh-score__value\">{{\n heightDeltaLabel(hv, last.heightCm || 0)\n }}</strong>\n </div>\n </div>\n }\n <div class=\"wh-eval-card__scale\" aria-hidden=\"true\">\n <div class=\"wh-eval-card__scale-labels\">\n <span class=\"wh-eval-card__scale-zone\">Th\u1EA5p</span>\n <span class=\"wh-eval-card__scale-zone wh-eval-card__scale-zone--mid\">Ph\u00F9 h\u1EE3p</span>\n <span class=\"wh-eval-card__scale-zone\">Cao</span>\n </div>\n <div class=\"wh-eval-card__scale-track\">\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--low\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--mid\"></span>\n <span class=\"wh-eval-card__scale-band wh-eval-card__scale-band--high\"></span>\n <span\n class=\"wh-eval-card__scale-marker\"\n [attr.data-status]=\"statusClass(hv.status)\"\n [style.left.%]=\"growthMarkerPosition(hv.zScore)\"\n ></span>\n </div>\n <div class=\"wh-eval-card__scale-legend\">\n <span>\u22122 SD</span>\n <span class=\"wh-eval-card__scale-z\"\n >P{{ hv.percentile }} \u00B7 z {{ formatZScore(hv.zScore) }}</span\n >\n <span>+2 SD</span>\n </div>\n </div>\n @if (latestHeightLog(); as last) {\n <p class=\"wh-eval-card__detail\">\n {{ friendlyHeightDetail(hv, last.heightCm || 0) }}\n </p>\n }\n </article>\n }\n </div>\n </section>\n }\n</div>\n", styles: [":host{display:block}.wh-root{display:flex;flex-direction:column;gap:12px;margin-bottom:4px}.wh-alerts{display:flex;flex-direction:column;gap:10px}.alert{display:flex;align-items:flex-start;gap:8px;padding:11px 14px;border-radius:12px;font-size:13px;line-height:1.45}.alert.error{background:linear-gradient(135deg,#fef2f2,#fff7f7);color:#991b1b;border:1px solid #fecaca}.alert.ok{background:linear-gradient(135deg,#ecfdf5,#f6fffb);color:#065f46;border:1px solid #a7f3d0}.wh-toolbar{display:flex;flex-wrap:wrap;align-items:flex-start;gap:10px 12px}.wh-hint{flex:1 1 220px;margin:0;display:flex;align-items:flex-start;gap:8px;font-size:12.5px;line-height:1.45;color:var(--muted, #64748b)}.wh-hint i{color:var(--primary, #5b6cff);margin-top:2px}.wh-pill-btn{display:inline-flex;align-items:center;gap:8px;padding:9px 16px;border-radius:999px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--text);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 2px 8px -4px #0f172a1f;transition:border-color .15s,background .15s,color .15s,transform .12s}.wh-pill-btn i{color:var(--primary, #5b6cff)}.wh-pill-btn:hover{border-color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff)}.wh-pill-btn:active{transform:scale(.98)}.wh-timeline-cta{display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;border-radius:16px;border:1px solid color-mix(in srgb,var(--primary, #5b6cff) 22%,var(--border, #e6e8ef));background:linear-gradient(120deg,color-mix(in srgb,var(--primary, #5b6cff) 8%,#fff),#fff);cursor:pointer;font-family:inherit;text-align:left;box-shadow:0 4px 18px -10px #5b6cff73;transition:transform .15s,box-shadow .15s}.wh-timeline-cta:hover{transform:translateY(-1px);box-shadow:0 8px 26px -12px #5b6cff80}.wh-timeline-cta__emoji{width:44px;height:44px;display:flex;align-items:center;justify-content:center;border-radius:14px;background:#fff;font-size:22px;flex-shrink:0;box-shadow:inset 0 0 0 1px #0f172a0f}.wh-timeline-cta__body{flex:1;min-width:0;display:flex;flex-direction:column;gap:3px}.wh-timeline-cta__label{font-size:12px;font-weight:800;letter-spacing:.04em;text-transform:uppercase;color:var(--primary, #5b6cff);display:flex;align-items:center;gap:6px}.wh-timeline-cta__label i{font-size:12px}.wh-timeline-cta__sub{font-size:13.5px;font-weight:600;color:var(--text, #0f172a);line-height:1.3}.wh-timeline-cta__stat{flex-shrink:0;padding:4px 10px;border-radius:999px;background:var(--primary-soft, #eef0ff);color:var(--primary, #5b6cff);font-size:12px;font-weight:800;font-variant-numeric:tabular-nums}.wh-timeline-cta .pi-chevron-right{color:var(--muted, #94a3b8);font-size:13px}.wh-assessment{border-radius:18px;border:1px solid var(--border, #e6e8ef);background:linear-gradient(180deg,#fff,#fbfcff);box-shadow:0 6px 24px -16px #0f172a38;padding:14px}.wh-assessment__head{margin-bottom:10px}.wh-assessment__eyebrow{display:inline-flex;align-items:center;gap:6px;font-size:11px;font-weight:800;letter-spacing:.05em;text-transform:uppercase;color:var(--primary, #5b6cff)}.wh-assessment__sub{margin:4px 0 0;font-size:12px;color:var(--muted, #64748b)}.wh-assessment__grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:740px){.wh-assessment__grid{grid-template-columns:1fr}}.wh-eval-card{border-radius:14px;border:1px solid var(--border, #e6e8ef);background:#fff;padding:12px;min-width:0}.wh-eval-card[data-status=ok]{border-color:#86efac;background:linear-gradient(180deg,#fff,#f6fff8)}.wh-eval-card[data-status=mid]{border-color:#fde68a;background:linear-gradient(180deg,#fff,#fffcf2)}.wh-eval-card[data-status=warn]{border-color:#fecaca;background:linear-gradient(180deg,#fff,#fff7f7)}.wh-eval-card__head{display:flex;align-items:flex-start;justify-content:space-between;gap:10px}.wh-eval-card__title{margin:0;font-size:14px;font-weight:800;display:inline-flex;align-items:center;gap:6px;color:var(--text)}.wh-eval-card--height .wh-eval-card__title i{color:#0ea5e9}.wh-eval-card__badge{flex-shrink:0;padding:4px 9px;border-radius:999px;font-size:11px;font-weight:800;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.wh-eval-card__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.wh-eval-card__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.wh-eval-card__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.wh-eval-card__meta{margin:8px 0 0;font-size:12px;color:var(--muted, #64748b);line-height:1.45}.wh-score-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:8px;margin-top:9px}.wh-score{border-radius:10px;border:1px solid #e2e8f0;background:#fff;padding:7px 8px;min-width:0}.wh-score__label{display:block;font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.04em;color:#64748b;margin-bottom:3px}.wh-score__value{display:block;font-size:13px;line-height:1.25;color:#0f172a;font-weight:800;font-variant-numeric:tabular-nums}.wh-eval-card__detail{margin:7px 0 0;font-size:12.5px;line-height:1.45;color:var(--text)}.wh-eval-card__detail--muted{color:#64748b}.wh-eval-card__scale{margin-top:10px;padding:10px 10px 8px;border-radius:10px;background:#f8fafc;border:1px solid #e2e8f0}.wh-eval-card__scale-labels{display:grid;grid-template-columns:1fr 2fr 1fr;gap:4px;margin-bottom:8px}.wh-eval-card__scale-zone{text-align:center;font-size:10.5px;font-weight:700;color:#64748b;background:#fff;border-radius:999px;padding:3px 4px;border:1px solid #e2e8f0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.wh-eval-card__scale-zone--mid{color:#166534;border-color:#bbf7d0;background:#f0fdf4}.wh-eval-card__scale-track{position:relative;display:flex;height:10px;border-radius:999px;overflow:visible;border:1px solid #cbd5e1}.wh-eval-card__scale-band{height:100%}.wh-eval-card__scale-band--low{flex:1;background:linear-gradient(180deg,#fee2e2,#fecaca);border-radius:999px 0 0 999px}.wh-eval-card__scale-band--mid{flex:2;background:linear-gradient(180deg,#dcfce7,#bbf7d0)}.wh-eval-card__scale-band--high{flex:1;background:linear-gradient(180deg,#dbeafe,#bfdbfe);border-radius:0 999px 999px 0}.wh-eval-card__scale-marker{position:absolute;top:50%;transform:translate(-50%,-50%);width:14px;height:14px;border-radius:50%;background:#2563eb;border:2px solid #fff;box-shadow:0 2px 8px #2563eb66;z-index:1}.wh-eval-card__scale-marker[data-status=ok]{background:#059669;box-shadow:0 2px 8px #05966959}.wh-eval-card__scale-marker[data-status=mid]{background:#d97706;box-shadow:0 2px 8px #d9770659}.wh-eval-card__scale-marker[data-status=warn]{background:#dc2626;box-shadow:0 2px 8px #dc262659}.wh-eval-card__scale-legend{display:flex;align-items:center;justify-content:space-between;margin-top:6px;font-size:10px;font-weight:700;color:#94a3b8;letter-spacing:.02em}.wh-eval-card__scale-z{font-variant-numeric:tabular-nums;color:#64748b}@media(max-width:420px){.wh-score-grid{grid-template-columns:1fr}}\n"] }]
12790
+ }], propDecorators: { errorMsg: [{ type: i0.Input, args: [{ isSignal: true, alias: "errorMsg", required: false }] }], successMsg: [{ type: i0.Input, args: [{ isSignal: true, alias: "successMsg", required: false }] }], birthDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "birthDate", required: false }] }], currentTimelineMilestone: [{ type: i0.Input, args: [{ isSignal: true, alias: "currentTimelineMilestone", required: false }] }], timelineStats: [{ type: i0.Input, args: [{ isSignal: true, alias: "timelineStats", required: false }] }], latestGrowthEval: [{ type: i0.Input, args: [{ isSignal: true, alias: "latestGrowthEval", required: false }] }], latestHeightEval: [{ type: i0.Input, args: [{ isSignal: true, alias: "latestHeightEval", required: false }] }], sortedLogsDesc: [{ type: i0.Input, args: [{ isSignal: true, alias: "sortedLogsDesc", required: false }] }], latestHeightLog: [{ type: i0.Input, args: [{ isSignal: true, alias: "latestHeightLog", required: false }] }], showAssessment: [{ type: i0.Input, args: [{ isSignal: true, alias: "showAssessment", required: false }] }], showWhoButton: [{ type: i0.Input, args: [{ isSignal: true, alias: "showWhoButton", required: false }] }], showTimeline: [{ type: i0.Input, args: [{ isSignal: true, alias: "showTimeline", required: false }] }], openWhoTable: [{ type: i0.Output, args: ["openWhoTable"] }], openTimeline: [{ type: i0.Output, args: ["openTimeline"] }] } });
12633
12791
 
12634
12792
  class WeightHistoryListComponent {
12635
12793
  constructor() {
12636
12794
  this.previewLimit = 10;
12637
12795
  this.loading = input(false, ...(ngDevMode ? [{ debugName: "loading" }] : /* istanbul ignore next */ []));
12638
12796
  this.logs = input([], ...(ngDevMode ? [{ debugName: "logs" }] : /* istanbul ignore next */ []));
12797
+ /** Tab Lịch sử: hiện toàn bộ danh sách, không giới hạn preview. */
12798
+ this.expanded = input(false, ...(ngDevMode ? [{ debugName: "expanded" }] : /* istanbul ignore next */ []));
12639
12799
  this.fullHistoryOpen = signal(false, ...(ngDevMode ? [{ debugName: "fullHistoryOpen" }] : /* istanbul ignore next */ []));
12640
12800
  this.edit = output();
12641
12801
  this.remove = output();
12802
+ this.displayLogs = computed(() => this.expanded() ? this.logs() : this.logs().slice(0, this.previewLimit), ...(ngDevMode ? [{ debugName: "displayLogs" }] : /* istanbul ignore next */ []));
12642
12803
  this.previewLogs = computed(() => this.logs().slice(0, this.previewLimit), ...(ngDevMode ? [{ debugName: "previewLogs" }] : /* istanbul ignore next */ []));
12643
- this.hasMoreLogs = computed(() => this.logs().length > this.previewLimit, ...(ngDevMode ? [{ debugName: "hasMoreLogs" }] : /* istanbul ignore next */ []));
12804
+ this.hasMoreLogs = computed(() => !this.expanded() && this.logs().length > this.previewLimit, ...(ngDevMode ? [{ debugName: "hasMoreLogs" }] : /* istanbul ignore next */ []));
12644
12805
  this.hiddenLogsCount = computed(() => Math.max(0, this.logs().length - this.previewLimit), ...(ngDevMode ? [{ debugName: "hiddenLogsCount" }] : /* istanbul ignore next */ []));
12645
12806
  }
12646
12807
  formatDate(iso) {
@@ -12659,12 +12820,12 @@ class WeightHistoryListComponent {
12659
12820
  this.fullHistoryOpen.set(false);
12660
12821
  }
12661
12822
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightHistoryListComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
12662
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightHistoryListComponent, isStandalone: true, selector: "app-weight-history-list", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { edit: "edit", remove: "remove" }, ngImport: i0, template: "<section class=\"whl-card\">\n <div class=\"whl-head\">\n <h3>L\u1ECBch s\u1EED c\u00E2n</h3>\n @if (loading()) {\n <span class=\"whl-loading\"> <i class=\"pi pi-spin pi-spinner\"></i> \u0110ang t\u1EA3i\u2026 </span>\n }\n </div>\n\n @if (!loading() && logs().length === 0) {\n <p class=\"whl-empty\">\n Ch\u01B0a c\u00F3 d\u00F2ng n\u00E0o tr\u00EAn sheet Weight. Nh\u1EA5n <strong>Ghi c\u00E2n</strong> \u0111\u1EC3 th\u00EAm.\n </p>\n }\n\n @if (logs().length > 0) {\n <ul class=\"whl-list\">\n @for (log of previewLogs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n }\n\n @if (hasMoreLogs()) {\n <div class=\"whl-more\">\n <button type=\"button\" class=\"whl-more__btn\" (click)=\"openFullHistory()\">\n Xem th\u00EAm {{ hiddenLogsCount() }} b\u1EA3n ghi\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n }\n</section>\n\n@if (fullHistoryOpen()) {\n <div class=\"whl-dialog-overlay\" (click)=\"closeFullHistory()\">\n <section class=\"whl-dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <header class=\"whl-dialog__head\">\n <h3>To\u00E0n b\u1ED9 l\u1ECBch s\u1EED c\u00E2n ({{ logs().length }})</h3>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--icon\"\n (click)=\"closeFullHistory()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <ul class=\"whl-list whl-list--dialog\">\n @for (log of logs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n </section>\n </div>\n}\n", styles: [":host{display:block}.whl-card{background:var(--surface, #fff);border:1px solid color-mix(in srgb,var(--border, #e6e8ef) 88%,var(--primary, #5b6cff));border-radius:14px;padding:10px 12px 8px;box-shadow:0 1px 10px -6px #0f172a1a}.whl-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;padding-bottom:6px;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 92%,transparent)}.whl-head h3{margin:0;font-size:14px;font-weight:800;letter-spacing:-.02em;color:var(--text, #0f172a)}.whl-loading{font-size:11.5px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.whl-loading i{margin-right:3px;font-size:12px}.whl-empty{margin:0;padding:4px 0 2px;font-size:12.5px;line-height:1.4;color:var(--muted, #64748b)}.whl-list{list-style:none;margin:0;padding:0}.whl-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px 10px;padding:8px 0;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 85%,transparent)}.whl-row:last-child{border-bottom:none;padding-bottom:2px}.whl-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.whl-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 4px;font-size:13px;line-height:1.25}.whl-sep{color:var(--muted-2, #94a3b8);font-weight:700;-webkit-user-select:none;user-select:none}.whl-date{font-weight:700;font-size:12.5px;color:var(--text);letter-spacing:.01em}.whl-kg{font-size:14px;font-weight:800;color:var(--primary, #5b6cff);letter-spacing:-.02em}.whl-height{font-size:13px;font-weight:700;color:#0ea5e9}.whl-note{font-size:12px;color:var(--muted, #64748b);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.whl-actions{display:flex;flex-direction:row;align-items:center;gap:4px;flex-shrink:0;padding-top:1px}.whl-btn{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border-radius:9px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--muted, #64748b);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.whl-btn i{font-size:13px}.whl-btn:hover:not(:disabled){border-color:var(--primary, #5b6cff);color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff)}.whl-btn--danger:hover:not(:disabled){border-color:#fecaca;color:#b91c1c;background:#fef2f2}.whl-btn:disabled{opacity:.45;cursor:not-allowed}.whl-btn--icon{width:34px;height:34px;border-radius:10px}.whl-more{margin-top:10px}.whl-more__btn{width:100%;display:inline-flex;justify-content:center;align-items:center;gap:6px;border:1px dashed #c7d2fe;background:#eef2ff;color:#3730a3;font-size:12.5px;font-weight:700;border-radius:10px;padding:9px 12px;cursor:pointer;transition:background .15s ease,border-color .15s ease}.whl-more__btn:hover{background:#e0e7ff;border-color:#a5b4fc}.whl-dialog-overlay{position:fixed;inset:0;z-index:120;background:#0f172a85;display:flex;align-items:center;justify-content:center;padding:16px}.whl-dialog{width:min(760px,100%);max-height:min(85vh,760px);background:#fff;border-radius:16px;border:1px solid #e2e8f0;box-shadow:0 20px 48px #0f172a3d;padding:12px;display:flex;flex-direction:column;gap:8px}.whl-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 2px 8px;border-bottom:1px solid #e2e8f0}.whl-dialog__head h3{margin:0;font-size:15px;font-weight:800;color:#0f172a}.whl-list--dialog{overflow:auto;padding-right:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12823
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightHistoryListComponent, isStandalone: true, selector: "app-weight-history-list", inputs: { loading: { classPropertyName: "loading", publicName: "loading", isSignal: true, isRequired: false, transformFunction: null }, logs: { classPropertyName: "logs", publicName: "logs", isSignal: true, isRequired: false, transformFunction: null }, expanded: { classPropertyName: "expanded", publicName: "expanded", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { edit: "edit", remove: "remove" }, ngImport: i0, template: "<section class=\"whl-card\">\n <div class=\"whl-head\">\n <h3>L\u1ECBch s\u1EED c\u00E2n</h3>\n @if (loading()) {\n <span class=\"whl-loading\"> <i class=\"pi pi-spin pi-spinner\"></i> \u0110ang t\u1EA3i\u2026 </span>\n }\n </div>\n\n @if (!loading() && logs().length === 0) {\n <p class=\"whl-empty\">\n Ch\u01B0a c\u00F3 d\u00F2ng n\u00E0o tr\u00EAn sheet Weight. Nh\u1EA5n <strong>Ghi c\u00E2n</strong> \u0111\u1EC3 th\u00EAm.\n </p>\n }\n\n @if (logs().length > 0) {\n <ul class=\"whl-list\">\n @for (log of displayLogs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n }\n\n @if (hasMoreLogs()) {\n <div class=\"whl-more\">\n <button type=\"button\" class=\"whl-more__btn\" (click)=\"openFullHistory()\">\n Xem th\u00EAm {{ hiddenLogsCount() }} b\u1EA3n ghi\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n }\n</section>\n\n@if (fullHistoryOpen()) {\n <div class=\"whl-dialog-overlay\" (click)=\"closeFullHistory()\">\n <section class=\"whl-dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <header class=\"whl-dialog__head\">\n <h3>To\u00E0n b\u1ED9 l\u1ECBch s\u1EED c\u00E2n ({{ logs().length }})</h3>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--icon\"\n (click)=\"closeFullHistory()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <ul class=\"whl-list whl-list--dialog\">\n @for (log of logs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n </section>\n </div>\n}\n", styles: [":host{display:block}.whl-card{background:var(--surface, #fff);border:1px solid color-mix(in srgb,var(--border, #e6e8ef) 88%,var(--primary, #5b6cff));border-radius:14px;padding:10px 12px 8px;box-shadow:0 1px 10px -6px #0f172a1a}.whl-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;padding-bottom:6px;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 92%,transparent)}.whl-head h3{margin:0;font-size:14px;font-weight:800;letter-spacing:-.02em;color:var(--text, #0f172a)}.whl-loading{font-size:11.5px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.whl-loading i{margin-right:3px;font-size:12px}.whl-empty{margin:0;padding:4px 0 2px;font-size:12.5px;line-height:1.4;color:var(--muted, #64748b)}.whl-list{list-style:none;margin:0;padding:0}.whl-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px 10px;padding:8px 0;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 85%,transparent)}.whl-row:last-child{border-bottom:none;padding-bottom:2px}.whl-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.whl-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 4px;font-size:13px;line-height:1.25}.whl-sep{color:var(--muted-2, #94a3b8);font-weight:700;-webkit-user-select:none;user-select:none}.whl-date{font-weight:700;font-size:12.5px;color:var(--text);letter-spacing:.01em}.whl-kg{font-size:14px;font-weight:800;color:var(--primary, #5b6cff);letter-spacing:-.02em}.whl-height{font-size:13px;font-weight:700;color:#0ea5e9}.whl-note{font-size:12px;color:var(--muted, #64748b);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.whl-actions{display:flex;flex-direction:row;align-items:center;gap:4px;flex-shrink:0;padding-top:1px}.whl-btn{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border-radius:9px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--muted, #64748b);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.whl-btn i{font-size:13px}.whl-btn:hover:not(:disabled){border-color:var(--primary, #5b6cff);color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff)}.whl-btn--danger:hover:not(:disabled){border-color:#fecaca;color:#b91c1c;background:#fef2f2}.whl-btn:disabled{opacity:.45;cursor:not-allowed}.whl-btn--icon{width:34px;height:34px;border-radius:10px}.whl-more{margin-top:10px}.whl-more__btn{width:100%;display:inline-flex;justify-content:center;align-items:center;gap:6px;border:1px dashed #c7d2fe;background:#eef2ff;color:#3730a3;font-size:12.5px;font-weight:700;border-radius:10px;padding:9px 12px;cursor:pointer;transition:background .15s ease,border-color .15s ease}.whl-more__btn:hover{background:#e0e7ff;border-color:#a5b4fc}.whl-dialog-overlay{position:fixed;inset:0;z-index:120;background:#0f172a85;display:flex;align-items:center;justify-content:center;padding:16px}.whl-dialog{width:min(760px,100%);max-height:min(85vh,760px);background:#fff;border-radius:16px;border:1px solid #e2e8f0;box-shadow:0 20px 48px #0f172a3d;padding:12px;display:flex;flex-direction:column;gap:8px}.whl-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 2px 8px;border-bottom:1px solid #e2e8f0}.whl-dialog__head h3{margin:0;font-size:15px;font-weight:800;color:#0f172a}.whl-list--dialog{overflow:auto;padding-right:2px}\n"], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
12663
12824
  }
12664
12825
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightHistoryListComponent, decorators: [{
12665
12826
  type: Component,
12666
- args: [{ selector: 'app-weight-history-list', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"whl-card\">\n <div class=\"whl-head\">\n <h3>L\u1ECBch s\u1EED c\u00E2n</h3>\n @if (loading()) {\n <span class=\"whl-loading\"> <i class=\"pi pi-spin pi-spinner\"></i> \u0110ang t\u1EA3i\u2026 </span>\n }\n </div>\n\n @if (!loading() && logs().length === 0) {\n <p class=\"whl-empty\">\n Ch\u01B0a c\u00F3 d\u00F2ng n\u00E0o tr\u00EAn sheet Weight. Nh\u1EA5n <strong>Ghi c\u00E2n</strong> \u0111\u1EC3 th\u00EAm.\n </p>\n }\n\n @if (logs().length > 0) {\n <ul class=\"whl-list\">\n @for (log of previewLogs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n }\n\n @if (hasMoreLogs()) {\n <div class=\"whl-more\">\n <button type=\"button\" class=\"whl-more__btn\" (click)=\"openFullHistory()\">\n Xem th\u00EAm {{ hiddenLogsCount() }} b\u1EA3n ghi\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n }\n</section>\n\n@if (fullHistoryOpen()) {\n <div class=\"whl-dialog-overlay\" (click)=\"closeFullHistory()\">\n <section class=\"whl-dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <header class=\"whl-dialog__head\">\n <h3>To\u00E0n b\u1ED9 l\u1ECBch s\u1EED c\u00E2n ({{ logs().length }})</h3>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--icon\"\n (click)=\"closeFullHistory()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <ul class=\"whl-list whl-list--dialog\">\n @for (log of logs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n </section>\n </div>\n}\n", styles: [":host{display:block}.whl-card{background:var(--surface, #fff);border:1px solid color-mix(in srgb,var(--border, #e6e8ef) 88%,var(--primary, #5b6cff));border-radius:14px;padding:10px 12px 8px;box-shadow:0 1px 10px -6px #0f172a1a}.whl-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;padding-bottom:6px;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 92%,transparent)}.whl-head h3{margin:0;font-size:14px;font-weight:800;letter-spacing:-.02em;color:var(--text, #0f172a)}.whl-loading{font-size:11.5px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.whl-loading i{margin-right:3px;font-size:12px}.whl-empty{margin:0;padding:4px 0 2px;font-size:12.5px;line-height:1.4;color:var(--muted, #64748b)}.whl-list{list-style:none;margin:0;padding:0}.whl-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px 10px;padding:8px 0;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 85%,transparent)}.whl-row:last-child{border-bottom:none;padding-bottom:2px}.whl-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.whl-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 4px;font-size:13px;line-height:1.25}.whl-sep{color:var(--muted-2, #94a3b8);font-weight:700;-webkit-user-select:none;user-select:none}.whl-date{font-weight:700;font-size:12.5px;color:var(--text);letter-spacing:.01em}.whl-kg{font-size:14px;font-weight:800;color:var(--primary, #5b6cff);letter-spacing:-.02em}.whl-height{font-size:13px;font-weight:700;color:#0ea5e9}.whl-note{font-size:12px;color:var(--muted, #64748b);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.whl-actions{display:flex;flex-direction:row;align-items:center;gap:4px;flex-shrink:0;padding-top:1px}.whl-btn{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border-radius:9px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--muted, #64748b);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.whl-btn i{font-size:13px}.whl-btn:hover:not(:disabled){border-color:var(--primary, #5b6cff);color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff)}.whl-btn--danger:hover:not(:disabled){border-color:#fecaca;color:#b91c1c;background:#fef2f2}.whl-btn:disabled{opacity:.45;cursor:not-allowed}.whl-btn--icon{width:34px;height:34px;border-radius:10px}.whl-more{margin-top:10px}.whl-more__btn{width:100%;display:inline-flex;justify-content:center;align-items:center;gap:6px;border:1px dashed #c7d2fe;background:#eef2ff;color:#3730a3;font-size:12.5px;font-weight:700;border-radius:10px;padding:9px 12px;cursor:pointer;transition:background .15s ease,border-color .15s ease}.whl-more__btn:hover{background:#e0e7ff;border-color:#a5b4fc}.whl-dialog-overlay{position:fixed;inset:0;z-index:120;background:#0f172a85;display:flex;align-items:center;justify-content:center;padding:16px}.whl-dialog{width:min(760px,100%);max-height:min(85vh,760px);background:#fff;border-radius:16px;border:1px solid #e2e8f0;box-shadow:0 20px 48px #0f172a3d;padding:12px;display:flex;flex-direction:column;gap:8px}.whl-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 2px 8px;border-bottom:1px solid #e2e8f0}.whl-dialog__head h3{margin:0;font-size:15px;font-weight:800;color:#0f172a}.whl-list--dialog{overflow:auto;padding-right:2px}\n"] }]
12667
- }], propDecorators: { loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], logs: [{ type: i0.Input, args: [{ isSignal: true, alias: "logs", required: false }] }], edit: [{ type: i0.Output, args: ["edit"] }], remove: [{ type: i0.Output, args: ["remove"] }] } });
12827
+ args: [{ selector: 'app-weight-history-list', standalone: true, imports: [], changeDetection: ChangeDetectionStrategy.OnPush, template: "<section class=\"whl-card\">\n <div class=\"whl-head\">\n <h3>L\u1ECBch s\u1EED c\u00E2n</h3>\n @if (loading()) {\n <span class=\"whl-loading\"> <i class=\"pi pi-spin pi-spinner\"></i> \u0110ang t\u1EA3i\u2026 </span>\n }\n </div>\n\n @if (!loading() && logs().length === 0) {\n <p class=\"whl-empty\">\n Ch\u01B0a c\u00F3 d\u00F2ng n\u00E0o tr\u00EAn sheet Weight. Nh\u1EA5n <strong>Ghi c\u00E2n</strong> \u0111\u1EC3 th\u00EAm.\n </p>\n }\n\n @if (logs().length > 0) {\n <ul class=\"whl-list\">\n @for (log of displayLogs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n }\n\n @if (hasMoreLogs()) {\n <div class=\"whl-more\">\n <button type=\"button\" class=\"whl-more__btn\" (click)=\"openFullHistory()\">\n Xem th\u00EAm {{ hiddenLogsCount() }} b\u1EA3n ghi\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n }\n</section>\n\n@if (fullHistoryOpen()) {\n <div class=\"whl-dialog-overlay\" (click)=\"closeFullHistory()\">\n <section class=\"whl-dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <header class=\"whl-dialog__head\">\n <h3>To\u00E0n b\u1ED9 l\u1ECBch s\u1EED c\u00E2n ({{ logs().length }})</h3>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--icon\"\n (click)=\"closeFullHistory()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <ul class=\"whl-list whl-list--dialog\">\n @for (log of logs(); track log) {\n <li class=\"whl-row\">\n <div class=\"whl-main\">\n <div class=\"whl-line\">\n <span class=\"whl-date\">{{ formatDate(log.date) }}</span>\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-kg\">{{ formatKg(log.weightKg) }} kg</span>\n @if (log.heightCm !== undefined) {\n <span class=\"whl-sep\" aria-hidden=\"true\">\u00B7</span>\n <span class=\"whl-height\">{{ formatCm(log.heightCm) }} cm</span>\n }\n </div>\n @if (log.note) {\n <span class=\"whl-note\">{{ log.note }}</span>\n }\n </div>\n <div class=\"whl-actions\">\n <button\n type=\"button\"\n class=\"whl-btn\"\n (click)=\"edit.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"whl-btn whl-btn--danger\"\n (click)=\"remove.emit(log)\"\n [disabled]=\"!log.rowIndex\"\n title=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n </li>\n }\n </ul>\n </section>\n </div>\n}\n", styles: [":host{display:block}.whl-card{background:var(--surface, #fff);border:1px solid color-mix(in srgb,var(--border, #e6e8ef) 88%,var(--primary, #5b6cff));border-radius:14px;padding:10px 12px 8px;box-shadow:0 1px 10px -6px #0f172a1a}.whl-head{display:flex;align-items:center;justify-content:space-between;gap:8px;margin-bottom:6px;padding-bottom:6px;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 92%,transparent)}.whl-head h3{margin:0;font-size:14px;font-weight:800;letter-spacing:-.02em;color:var(--text, #0f172a)}.whl-loading{font-size:11.5px;font-weight:600;color:var(--muted, #64748b);white-space:nowrap}.whl-loading i{margin-right:3px;font-size:12px}.whl-empty{margin:0;padding:4px 0 2px;font-size:12.5px;line-height:1.4;color:var(--muted, #64748b)}.whl-list{list-style:none;margin:0;padding:0}.whl-row{display:flex;align-items:flex-start;justify-content:space-between;gap:8px 10px;padding:8px 0;border-bottom:1px solid color-mix(in srgb,var(--border, #e6e8ef) 85%,transparent)}.whl-row:last-child{border-bottom:none;padding-bottom:2px}.whl-main{flex:1;min-width:0;display:flex;flex-direction:column;gap:2px}.whl-line{display:flex;flex-wrap:wrap;align-items:baseline;gap:0 4px;font-size:13px;line-height:1.25}.whl-sep{color:var(--muted-2, #94a3b8);font-weight:700;-webkit-user-select:none;user-select:none}.whl-date{font-weight:700;font-size:12.5px;color:var(--text);letter-spacing:.01em}.whl-kg{font-size:14px;font-weight:800;color:var(--primary, #5b6cff);letter-spacing:-.02em}.whl-height{font-size:13px;font-weight:700;color:#0ea5e9}.whl-note{font-size:12px;color:var(--muted, #64748b);line-height:1.35;display:-webkit-box;-webkit-line-clamp:2;-webkit-box-orient:vertical;overflow:hidden}.whl-actions{display:flex;flex-direction:row;align-items:center;gap:4px;flex-shrink:0;padding-top:1px}.whl-btn{width:32px;height:32px;display:inline-flex;align-items:center;justify-content:center;border-radius:9px;border:1px solid var(--border, #e6e8ef);background:#fff;color:var(--muted, #64748b);cursor:pointer;transition:border-color .15s,color .15s,background .15s}.whl-btn i{font-size:13px}.whl-btn:hover:not(:disabled){border-color:var(--primary, #5b6cff);color:var(--primary, #5b6cff);background:var(--primary-soft, #eef0ff)}.whl-btn--danger:hover:not(:disabled){border-color:#fecaca;color:#b91c1c;background:#fef2f2}.whl-btn:disabled{opacity:.45;cursor:not-allowed}.whl-btn--icon{width:34px;height:34px;border-radius:10px}.whl-more{margin-top:10px}.whl-more__btn{width:100%;display:inline-flex;justify-content:center;align-items:center;gap:6px;border:1px dashed #c7d2fe;background:#eef2ff;color:#3730a3;font-size:12.5px;font-weight:700;border-radius:10px;padding:9px 12px;cursor:pointer;transition:background .15s ease,border-color .15s ease}.whl-more__btn:hover{background:#e0e7ff;border-color:#a5b4fc}.whl-dialog-overlay{position:fixed;inset:0;z-index:120;background:#0f172a85;display:flex;align-items:center;justify-content:center;padding:16px}.whl-dialog{width:min(760px,100%);max-height:min(85vh,760px);background:#fff;border-radius:16px;border:1px solid #e2e8f0;box-shadow:0 20px 48px #0f172a3d;padding:12px;display:flex;flex-direction:column;gap:8px}.whl-dialog__head{display:flex;align-items:center;justify-content:space-between;gap:10px;padding:4px 2px 8px;border-bottom:1px solid #e2e8f0}.whl-dialog__head h3{margin:0;font-size:15px;font-weight:800;color:#0f172a}.whl-list--dialog{overflow:auto;padding-right:2px}\n"] }]
12828
+ }], propDecorators: { loading: [{ type: i0.Input, args: [{ isSignal: true, alias: "loading", required: false }] }], logs: [{ type: i0.Input, args: [{ isSignal: true, alias: "logs", required: false }] }], expanded: [{ type: i0.Input, args: [{ isSignal: true, alias: "expanded", required: false }] }], edit: [{ type: i0.Output, args: ["edit"] }], remove: [{ type: i0.Output, args: ["remove"] }] } });
12668
12829
 
12669
12830
  class WeightComponent {
12670
12831
  constructor() {
@@ -12696,6 +12857,15 @@ class WeightComponent {
12696
12857
  this.whoTableDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "whoTableDialogOpen" }] : /* istanbul ignore next */ []));
12697
12858
  /** Bảng tham chiếu WHO bé gái (VN) — dialog thông tin */
12698
12859
  this.whoGirlReferenceDialogOpen = signal(false, ...(ngDevMode ? [{ debugName: "whoGirlReferenceDialogOpen" }] : /* istanbul ignore next */ []));
12860
+ this.screenTab = signal('dashboard', ...(ngDevMode ? [{ debugName: "screenTab" }] : /* istanbul ignore next */ []));
12861
+ this.comparePeriod = signal('month', ...(ngDevMode ? [{ debugName: "comparePeriod" }] : /* istanbul ignore next */ []));
12862
+ this.growthMetric = signal('weight', ...(ngDevMode ? [{ debugName: "growthMetric" }] : /* istanbul ignore next */ []));
12863
+ this.screenTabs = [
12864
+ { id: 'dashboard', label: 'Tổng quan', icon: 'pi-home' },
12865
+ { id: 'assessment', label: 'Đánh giá', icon: 'pi-heart' },
12866
+ { id: 'history', label: 'Lịch sử', icon: 'pi-history' },
12867
+ { id: 'reference', label: 'So sánh', icon: 'pi-table' },
12868
+ ];
12699
12869
  /** Alias cho template (WHO dialog + đánh giá). */
12700
12870
  this.growthStatusClass = growthStatusClass;
12701
12871
  this.growthSex = computed(() => (this.gender() === 'girl' ? 'female' : 'male'), ...(ngDevMode ? [{ debugName: "growthSex" }] : /* istanbul ignore next */ []));
@@ -12752,6 +12922,45 @@ class WeightComponent {
12752
12922
  return null;
12753
12923
  return evaluateHeightForAge(days, latest.heightCm, this.growthSex());
12754
12924
  }, ...(ngDevMode ? [{ debugName: "latestHeightEval" }] : /* istanbul ignore next */ []));
12925
+ this.growthSnapshot = computed(() => {
12926
+ const latest = this.sortedLogsDesc()[0];
12927
+ const heightLog = this.latestHeightLog();
12928
+ if (!latest)
12929
+ return null;
12930
+ const periodDays = this.comparePeriod() === 'week' ? 7 : 30;
12931
+ const tag = this.comparePeriod() === 'week' ? 'W−1' : 'T−1';
12932
+ const tip = this.comparePeriod() === 'week' ? 'so với tuần trước' : 'so với tháng trước';
12933
+ const prevWeight = this.findLogDaysBefore(latest.date, periodDays);
12934
+ const weightNow = latest.weightKg;
12935
+ const weightPrev = prevWeight?.weightKg ?? weightNow;
12936
+ const weightDelta = Math.round((weightNow - weightPrev) * 100) / 100;
12937
+ const weightPct = this.deltaPercent(weightNow, weightPrev);
12938
+ const heightNow = heightLog?.heightCm ?? null;
12939
+ const prevHeight = heightLog ? this.findLogDaysBefore(heightLog.date, periodDays) : null;
12940
+ const heightPrev = prevHeight?.heightCm ?? heightNow ?? 0;
12941
+ const heightDelta = heightNow !== null ? Math.round((heightNow - heightPrev) * 10) / 10 : 0;
12942
+ const heightPct = heightNow !== null ? this.deltaPercent(heightNow, heightPrev) : 0;
12943
+ const velocity = this.snapshotVelocity(periodDays);
12944
+ return {
12945
+ tag,
12946
+ tip,
12947
+ weight: {
12948
+ value: weightNow,
12949
+ delta: weightDelta,
12950
+ pct: weightPct,
12951
+ suffix: 'kg',
12952
+ unit: 'kg',
12953
+ },
12954
+ height: {
12955
+ value: heightNow,
12956
+ delta: heightDelta,
12957
+ pct: heightPct,
12958
+ suffix: 'cm',
12959
+ unit: 'cm',
12960
+ },
12961
+ velocity,
12962
+ };
12963
+ }, ...(ngDevMode ? [{ debugName: "growthSnapshot" }] : /* istanbul ignore next */ []));
12755
12964
  /**
12756
12965
  * Bảng chuẩn WHO với min/max/median theo tuần tuổi.
12757
12966
  * Highlight dòng tuần hiện tại của bé.
@@ -13025,6 +13234,14 @@ class WeightComponent {
13025
13234
  }
13026
13235
  const firstLog = logs[0];
13027
13236
  const birthRecordLine = `${this.formatKg(firstLog.weightKg)} kg — ${this.formatDateDisplay(firstLog.date)}`;
13237
+ const lastPt = sorted[sorted.length - 1];
13238
+ let refMedian = ref[0]?.medianKg ?? lastPt.log.weightKg;
13239
+ for (const r of ref) {
13240
+ if (r.daysSinceBirth <= lastPt.days)
13241
+ refMedian = r.medianKg;
13242
+ else
13243
+ break;
13244
+ }
13028
13245
  return {
13029
13246
  regionCode: GROWTH_REFERENCE_REGION_VN,
13030
13247
  referenceCaption: GROWTH_REFERENCE_CAPTION_VI,
@@ -13051,6 +13268,12 @@ class WeightComponent {
13051
13268
  : this.gender() === 'boy'
13052
13269
  ? 'Nam (theo profile)'
13053
13270
  : 'Nam (mặc định khi chưa chọn)',
13271
+ latestSummary: {
13272
+ ageLabel: formatAgeShortVi(lastPt.days),
13273
+ baby: lastPt.log.weightKg,
13274
+ ref: Math.round(refMedian * 100) / 100,
13275
+ unit: 'kg',
13276
+ },
13054
13277
  };
13055
13278
  }, ...(ngDevMode ? [{ debugName: "weightVsStandardChart" }] : /* istanbul ignore next */ []));
13056
13279
  this.heightVsStandardChart = computed(() => {
@@ -13149,6 +13372,15 @@ class WeightComponent {
13149
13372
  label: month === 0 ? 'Sinh' : `${month} tháng`,
13150
13373
  });
13151
13374
  }
13375
+ const sortedHeight = [...raw].sort((a, b) => a.days - b.days);
13376
+ const lastHeightPt = sortedHeight[sortedHeight.length - 1];
13377
+ let refMedianCm = ref[0]?.medianCm ?? lastHeightPt.heightCm;
13378
+ for (const r of ref) {
13379
+ if (r.daysSinceBirth <= lastHeightPt.days)
13380
+ refMedianCm = r.medianCm;
13381
+ else
13382
+ break;
13383
+ }
13152
13384
  return {
13153
13385
  regionCode: GROWTH_REFERENCE_REGION_VN,
13154
13386
  referenceCaption: GROWTH_REFERENCE_CAPTION_VI,
@@ -13172,73 +13404,60 @@ class WeightComponent {
13172
13404
  : this.gender() === 'boy'
13173
13405
  ? 'Nam (theo profile)'
13174
13406
  : 'Nam (mặc định khi chưa chọn)',
13407
+ latestSummary: {
13408
+ ageLabel: formatAgeShortVi(lastHeightPt.days),
13409
+ baby: lastHeightPt.heightCm,
13410
+ ref: Math.round(refMedianCm * 10) / 10,
13411
+ unit: 'cm',
13412
+ },
13175
13413
  };
13176
13414
  }, ...(ngDevMode ? [{ debugName: "heightVsStandardChart" }] : /* istanbul ignore next */ []));
13177
13415
  /**
13178
13416
  * Đường tốc độ tăng (g/ngày) giữa các cặp lần cân liên tiếp (chỉ khoảng ≥1 ngày).
13179
13417
  */
13180
13418
  this.weightVelocityLineChart = computed(() => {
13181
- const intervals = this.weightLogIntervals().filter((s) => s.gPerDay !== null);
13419
+ const intervals = this.weightLogIntervals()
13420
+ .filter((s) => s.gPerDay !== null)
13421
+ .map((s) => ({ value: s.gPerDay, labelShort: s.labelShort }));
13182
13422
  if (intervals.length === 0)
13183
13423
  return null;
13184
- const vals = intervals.map((s) => s.gPerDay);
13185
- let minV = Math.min(...vals);
13186
- let maxV = Math.max(...vals);
13187
- minV = Math.min(minV, 0);
13188
- maxV = Math.max(maxV, 0);
13189
- const span = maxV - minV;
13190
- const pad = span < 1 ? 8 : Math.max(4, span * 0.14);
13191
- minV -= pad;
13192
- maxV += pad;
13193
- const range = Math.max(12, maxV - minV);
13194
- const PAD_L = 46;
13195
- const PAD_R = 12;
13196
- const PAD_T = 14;
13197
- const PAD_B = 58;
13198
- const H = 220;
13199
- const n = intervals.length;
13200
- const innerW = Math.max(300, Math.max(n - 1, 1) * 64);
13201
- const W = PAD_L + innerW + PAD_R;
13202
- const chartH = H - PAD_T - PAD_B;
13203
- const yOf = (v) => PAD_T + chartH - ((v - minV) / range) * chartH;
13204
- const xOf = (i) => PAD_L + (n === 1 ? innerW / 2 : (i / Math.max(1, n - 1)) * innerW);
13205
- const zeroY = yOf(0);
13206
- const bottomY = PAD_T + chartH;
13207
- const showZeroLine = zeroY >= PAD_T - 0.5 && zeroY <= bottomY + 0.5;
13208
- let pathLine = '';
13209
- const pts = intervals.map((seg, i) => {
13210
- const g = seg.gPerDay;
13211
- const x = xOf(i);
13212
- const y = yOf(g);
13213
- pathLine += i === 0 ? `M ${x},${y}` : ` L ${x},${y}`;
13214
- const labelStep = n <= 8 ? 1 : Math.max(1, Math.ceil(n / 6));
13215
- return {
13216
- x,
13217
- y,
13218
- gPerDay: g,
13219
- label: seg.labelShort,
13220
- showValue: true,
13221
- showXLabel: i === 0 || i === n - 1 || i % labelStep === 0,
13222
- };
13424
+ return this.buildVelocityChartVm(intervals, {
13425
+ seriesLabel: 'g/ngày TB',
13426
+ refSuffix: ' g',
13427
+ captionPrefix: 'Cân nặng',
13428
+ formatGridLabel: (v) => `${Math.round(v)} g/ngày`,
13223
13429
  });
13224
- const gridLines = [0, 0.5, 1].map((t) => ({
13225
- y: PAD_T + chartH - t * chartH,
13226
- label: `${Math.round(minV + (1 - t) * range)} g/ngày`,
13227
- }));
13228
- return {
13229
- W,
13230
- H,
13231
- PAD_L,
13232
- innerW,
13233
- PAD_T,
13234
- PAD_B,
13235
- pathLine,
13236
- pts,
13237
- gridLines,
13238
- zeroY,
13239
- showZeroLine,
13240
- };
13241
13430
  }, ...(ngDevMode ? [{ debugName: "weightVelocityLineChart" }] : /* istanbul ignore next */ []));
13431
+ this.heightVelocityLineChart = computed(() => {
13432
+ const logs = this.sortedLogsAsc().filter((l) => l.heightCm !== undefined && l.heightCm > 0);
13433
+ if (logs.length < 2)
13434
+ return null;
13435
+ const isMonth = this.comparePeriod() === 'month';
13436
+ const intervals = [];
13437
+ for (let i = 1; i < logs.length; i++) {
13438
+ const prev = logs[i - 1];
13439
+ const cur = logs[i];
13440
+ const days = this.diffDaysIso(prev.date, cur.date);
13441
+ if (days <= 0 || prev.heightCm === undefined || cur.heightCm === undefined)
13442
+ continue;
13443
+ const deltaCm = cur.heightCm - prev.heightCm;
13444
+ const value = isMonth
13445
+ ? Math.round((deltaCm / days) * 30.4375 * 100) / 100
13446
+ : Math.round((deltaCm / days) * 7 * 100) / 100;
13447
+ intervals.push({
13448
+ value,
13449
+ labelShort: `${this.formatDateShort(prev.date)}→${this.formatDateShort(cur.date)}`,
13450
+ });
13451
+ }
13452
+ if (intervals.length === 0)
13453
+ return null;
13454
+ return this.buildVelocityChartVm(intervals, {
13455
+ seriesLabel: isMonth ? 'cm/tháng TB' : 'cm/tuần TB',
13456
+ refSuffix: isMonth ? ' cm/th' : ' cm/tuần',
13457
+ captionPrefix: 'Chiều cao',
13458
+ formatGridLabel: (v) => `${v.toFixed(1)} ${isMonth ? 'cm/tháng' : 'cm/tuần'}`,
13459
+ });
13460
+ }, ...(ngDevMode ? [{ debugName: "heightVelocityLineChart" }] : /* istanbul ignore next */ []));
13242
13461
  // ===== Timeline phát triển =====
13243
13462
  this.currentTimelineMilestone = computed(() => {
13244
13463
  const days = this.ageInDays();
@@ -13378,11 +13597,177 @@ class WeightComponent {
13378
13597
  return this.whoMetricLevel(row.currentHeight, row.minCm, row.medianCm, row.maxCm);
13379
13598
  }
13380
13599
  openWhoTableDialog() {
13381
- this.whoTableDialogOpen.set(true);
13600
+ this.screenTab.set('reference');
13382
13601
  }
13383
13602
  closeWhoTableDialog() {
13384
13603
  this.whoTableDialogOpen.set(false);
13385
13604
  }
13605
+ setScreenTab(tab) {
13606
+ this.screenTab.set(tab);
13607
+ }
13608
+ setComparePeriod(period) {
13609
+ this.comparePeriod.set(period);
13610
+ }
13611
+ setGrowthMetric(metric) {
13612
+ this.growthMetric.set(metric);
13613
+ }
13614
+ formatSignedDelta(n, suffix) {
13615
+ const sign = n >= 0 ? '+' : '';
13616
+ return `${sign}${n}${suffix}`;
13617
+ }
13618
+ formatSignedPercent(n) {
13619
+ const sign = n >= 0 ? '+' : '';
13620
+ return `${sign}${n}%`;
13621
+ }
13622
+ findLogDaysBefore(fromDate, days) {
13623
+ const sorted = this.sortedLogsAsc();
13624
+ if (sorted.length === 0)
13625
+ return null;
13626
+ const fromMs = new Date(`${fromDate}T12:00:00`).getTime();
13627
+ const targetMs = fromMs - days * 86_400_000;
13628
+ let best = null;
13629
+ for (const log of sorted) {
13630
+ const ms = new Date(`${log.date}T12:00:00`).getTime();
13631
+ if (ms <= targetMs)
13632
+ best = log;
13633
+ if (ms >= fromMs)
13634
+ break;
13635
+ }
13636
+ return best;
13637
+ }
13638
+ deltaPercent(current, previous) {
13639
+ if (!previous)
13640
+ return 0;
13641
+ return Math.round(((current - previous) / previous) * 1000) / 10;
13642
+ }
13643
+ snapshotVelocity(periodDays) {
13644
+ const metric = this.growthMetric();
13645
+ if (metric === 'height') {
13646
+ return this.snapshotHeightVelocity(periodDays);
13647
+ }
13648
+ return this.snapshotWeightVelocity(periodDays);
13649
+ }
13650
+ snapshotWeightVelocity(periodDays) {
13651
+ const latest = this.sortedLogsDesc()[0];
13652
+ const cutoffMs = latest
13653
+ ? new Date(`${latest.date}T12:00:00`).getTime() - periodDays * 86_400_000
13654
+ : 0;
13655
+ const recent = this.weightLogIntervals().filter((s) => {
13656
+ const toMs = new Date(`${s.toDate}T12:00:00`).getTime();
13657
+ return s.gPerDay !== null && toMs >= cutoffMs;
13658
+ });
13659
+ const pool = recent.length > 0 ? recent : this.weightLogIntervals();
13660
+ const last = pool[pool.length - 1];
13661
+ const value = last?.gPerDay ?? 0;
13662
+ const prev = pool.length > 1 ? pool[pool.length - 2] : last;
13663
+ const prevVal = prev?.gPerDay ?? value;
13664
+ const delta = Math.round((value - prevVal) * 10) / 10;
13665
+ return {
13666
+ label: 'Tốc độ PT',
13667
+ value,
13668
+ delta,
13669
+ pct: this.deltaPercent(value, prevVal || 1),
13670
+ suffix: ' g',
13671
+ unit: 'g/ngày',
13672
+ };
13673
+ }
13674
+ snapshotHeightVelocity(periodDays) {
13675
+ const logs = this.sortedLogsAsc().filter((l) => l.heightCm !== undefined && l.heightCm > 0);
13676
+ const latest = logs[logs.length - 1];
13677
+ if (!latest?.heightCm) {
13678
+ return { label: 'Tốc độ PT', value: null, delta: 0, pct: 0, suffix: ' cm', unit: 'cm/tuần' };
13679
+ }
13680
+ const prev = this.findLogDaysBefore(latest.date, periodDays);
13681
+ const days = prev ? this.diffDaysIso(prev.date, latest.date) : periodDays;
13682
+ const deltaCm = prev?.heightCm !== undefined
13683
+ ? Math.round((latest.heightCm - prev.heightCm) * 10) / 10
13684
+ : 0;
13685
+ const perWeek = days > 0 ? Math.round(((deltaCm / days) * 7) * 100) / 100 : 0;
13686
+ const unit = this.comparePeriod() === 'month' ? 'cm/tháng' : 'cm/tuần';
13687
+ const value = this.comparePeriod() === 'month'
13688
+ ? Math.round(perWeek * 4.33 * 10) / 10
13689
+ : perWeek;
13690
+ return {
13691
+ label: 'Tốc độ PT',
13692
+ value,
13693
+ delta: deltaCm,
13694
+ pct: prev?.heightCm ? this.deltaPercent(latest.heightCm, prev.heightCm) : 0,
13695
+ suffix: ' cm',
13696
+ unit,
13697
+ };
13698
+ }
13699
+ /**
13700
+ * Đường tốc độ tăng (g/ngày) giữa các cặp lần cân liên tiếp (chỉ khoảng ≥1 ngày).
13701
+ */
13702
+ buildVelocityChartVm(intervals, meta) {
13703
+ const vals = intervals.map((s) => s.value);
13704
+ let minV = Math.min(...vals);
13705
+ let maxV = Math.max(...vals);
13706
+ const refValue = Math.round((vals.reduce((a, b) => a + b, 0) / vals.length) * 10) / 10;
13707
+ minV = Math.min(minV, refValue, 0);
13708
+ maxV = Math.max(maxV, refValue, 0);
13709
+ const span = maxV - minV;
13710
+ const pad = span < 0.5 ? 0.4 : Math.max(span * 0.14, 0.2);
13711
+ minV -= pad;
13712
+ maxV += pad;
13713
+ const range = Math.max(0.5, maxV - minV);
13714
+ const PAD_L = 40;
13715
+ const PAD_R = 10;
13716
+ const PAD_T = 14;
13717
+ const PAD_B = 36;
13718
+ const H = 200;
13719
+ const n = intervals.length;
13720
+ const innerW = Math.max(280, Math.max(n, 1) * 56);
13721
+ const W = PAD_L + innerW + PAD_R;
13722
+ const chartH = H - PAD_T - PAD_B;
13723
+ const yOf = (v) => PAD_T + chartH - ((v - minV) / range) * chartH;
13724
+ const xOf = (i) => PAD_L + (n === 1 ? innerW / 2 : (i / Math.max(1, n - 1)) * innerW);
13725
+ const zeroY = yOf(0);
13726
+ const bottomY = PAD_T + chartH;
13727
+ const showZeroLine = zeroY >= PAD_T - 0.5 && zeroY <= bottomY + 0.5;
13728
+ const barWidth = Math.min(26, Math.max(10, (innerW / Math.max(n, 1)) * 0.52));
13729
+ const refY = yOf(refValue);
13730
+ let pathLine = '';
13731
+ const pts = intervals.map((seg, i) => {
13732
+ const x = xOf(i);
13733
+ const y = yOf(seg.value);
13734
+ pathLine += i === 0 ? `M ${x},${y}` : ` L ${x},${y}`;
13735
+ const labelStep = n <= 8 ? 1 : Math.max(1, Math.ceil(n / 6));
13736
+ return {
13737
+ x,
13738
+ y,
13739
+ gPerDay: seg.value,
13740
+ label: seg.labelShort,
13741
+ showValue: true,
13742
+ showXLabel: i === 0 || i === n - 1 || i % labelStep === 0,
13743
+ };
13744
+ });
13745
+ const gridLines = [0, 0.5, 1].map((t) => ({
13746
+ y: PAD_T + chartH - t * chartH,
13747
+ label: meta.formatGridLabel(minV + (1 - t) * range),
13748
+ }));
13749
+ const periodLabel = this.comparePeriod() === 'month' ? 'tháng' : 'tuần';
13750
+ return {
13751
+ W,
13752
+ H,
13753
+ PAD_L,
13754
+ innerW,
13755
+ PAD_T,
13756
+ PAD_B,
13757
+ bottomY,
13758
+ barWidth,
13759
+ pathLine,
13760
+ pts,
13761
+ gridLines,
13762
+ zeroY,
13763
+ showZeroLine,
13764
+ refValue,
13765
+ refY,
13766
+ seriesLabel: meta.seriesLabel,
13767
+ refSuffix: meta.refSuffix,
13768
+ caption: `${meta.captionPrefix} · Trục X: khoảng đo · Trục Y: ${meta.seriesLabel} · ${n} kỳ gần nhất · ${periodLabel}`,
13769
+ };
13770
+ }
13386
13771
  setTimelineView(view) {
13387
13772
  this.timelineView.set(view);
13388
13773
  }
@@ -13751,7 +14136,7 @@ class WeightComponent {
13751
14136
  };
13752
14137
  }
13753
14138
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
13754
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightComponent, isStandalone: true, selector: "app-weight", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, birthDate: { classPropertyName: "birthDate", publicName: "birthDate", isSignal: true, isRequired: false, transformFunction: null }, gender: { classPropertyName: "gender", publicName: "gender", isSignal: true, isRequired: false, transformFunction: null }, babyName: { classPropertyName: "babyName", publicName: "babyName", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown": "onTimelineKey($event)" } }, ngImport: i0, template: "<div class=\"weight-page\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n (openWhoTable)=\"openWhoTableDialog()\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n <app-weight-charts\n [weightVsStandardChart]=\"weightVsStandardChart()\"\n [heightVsStandardChart]=\"heightVsStandardChart()\"\n [weightTrendChart]=\"weightTrendChart()\"\n [simpleTrendMode]=\"simpleTrendForCharts()\"\n [weightVelocityLineChart]=\"weightVelocityLineChart()\"\n />\n\n <app-weight-history-list\n [loading]=\"loading()\"\n [logs]=\"sortedLogsDesc()\"\n (edit)=\"openEdit($event)\"\n (remove)=\"deleteLog($event)\"\n />\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAddDialog()\"\n aria-label=\"Ghi nh\u1EADn c\u00E2n n\u1EB7ng\"\n title=\"Ghi nh\u1EADn c\u00E2n n\u1EB7ng m\u1EDBi\"\n >\n <i class=\"pi pi-plus\"></i>\n <span class=\"fab-label\">Ghi c\u00E2n</span>\n </button>\n\n <!-- Dialog th\u00EAm (gi\u1ED1ng c\u1EEF b\u00FA) -->\n @if (addDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeAddDialog()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">Nh\u1EADt k\u00FD c\u00E2n n\u1EB7ng</div>\n <h2><i class=\"pi pi-chart-line\"></i> Th\u00EAm l\u1EA7n c\u00E2n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAddDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <form class=\"log-form\" (ngSubmit)=\"submitAdd()\">\n <div class=\"log-grid weight-log-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y c\u00E2n</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"draft().date\"\n (input)=\"updateDraftDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n n\u1EB7ng (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 4,25\"\n [value]=\"draft().weightInput\"\n (input)=\"updateDraftWeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 63,5\"\n [value]=\"draft().heightInput\"\n (input)=\"updateDraftHeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"Tu\u1EF3 ch\u1ECDn \u2014 ho\u1EB7c ch\u1ECDn g\u1EE3i \u00FD b\u00EAn d\u01B0\u1EDBi\"\n [value]=\"draft().note\"\n (input)=\"updateDraftNote($any($event.target).value)\"\n />\n </label>\n </div>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(draft().note, t)\"\n (click)=\"toggleDraftNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAddDialog()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving() || loading()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n }\n\n @if (editingLog()) {\n <div class=\"edit-overlay\" (click)=\"cancelEdit()\"></div>\n }\n @if (editingLog()) {\n <aside class=\"edit-sheet\" role=\"dialog\" aria-modal=\"true\">\n <header class=\"edit-sheet-head\">\n <h3>S\u1EEDa ghi nh\u1EADn</h3>\n <button type=\"button\" class=\"btn icon ghost\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <form class=\"form\" (ngSubmit)=\"submitEdit()\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"editDraft().date\"\n (input)=\"updateEditDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().weightInput\"\n (input)=\"updateEditWeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().heightInput\"\n (input)=\"updateEditHeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n [value]=\"editDraft().note\"\n (input)=\"updateEditNote($any($event.target).value)\"\n />\n </label>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(editDraft().note, t)\"\n (click)=\"toggleEditNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"edit-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </form>\n </aside>\n }\n\n <!-- Timeline ph\u00E1t tri\u1EC3n -->\n @if (timelineDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeTimelineDialog()\">\n <div\n class=\"dialog dialog-wide timeline-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">\n <i class=\"pi pi-chart-line\"></i>\n D\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n\n </div>\n <h2>\uD83C\uDF31 H\u00E0nh tr\u00ECnh c\u1EE7a {{ babyName() || 'b\u00E9' }}</h2>\n @if (ageInDays() !== null) {\n <p class=\"dialog-lead\">\n {{ ageInDays() }} ng\u00E0y tu\u1ED5i \u00B7\n <span class=\"tl-head-stat\"\n >{{ timelineStats().past }}/{{ timelineStats().total }} c\u1ED9t m\u1ED1c \u0111\u00E3 qua</span\n >\n </p>\n }\n </div>\n <button\n type=\"button\"\n class=\"dialog-close\"\n (click)=\"closeTimelineDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <div class=\"tl-tabs\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'list'\"\n (click)=\"setTimelineView('list')\"\n >\n <i class=\"pi pi-list\"></i>\n <span>Danh s\u00E1ch</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'chart'\"\n (click)=\"setTimelineView('chart')\"\n >\n <i class=\"pi pi-chart-line\"></i>\n <span>Bi\u1EC3u \u0111\u1ED3 t\u00E2m tr\u1EA1ng</span>\n </button>\n </div>\n @if (timelineView() === 'chart') {\n <div class=\"tl-chart-view\">\n <div class=\"tl-chart-mood-legend\">\n <span class=\"tl-mood-badge\" data-mood=\"happy\">\n <i class=\"pi pi-heart-fill\"></i> Vui\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"milestone\">\n <i class=\"pi pi-star-fill\"></i> C\u1ED9t m\u1ED1c\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"calm\">\n <i class=\"pi pi-circle-fill\"></i> B\u00ECnh y\u00EAn\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"growth\">\n <i class=\"pi pi-arrow-up\"></i> T\u0103ng tr\u01B0\u1EDFng\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"leap\">\n <i class=\"pi pi-bolt\"></i> Leap/WW\n </span>\n </div>\n @if (timelineChartData(); as cd) {\n <div class=\"tl-chart-scroll\">\n <svg\n [attr.viewBox]=\"'0 0 ' + cd.W + ' ' + cd.H\"\n [attr.width]=\"cd.W\"\n [attr.height]=\"cd.H\"\n class=\"tl-chart-svg\"\n preserveAspectRatio=\"xMinYMid meet\"\n >\n <defs>\n <linearGradient id=\"tlMoodGradientW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.20\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" stop-opacity=\"0.14\" />\n <stop offset=\"100%\" stop-color=\"#ef4444\" stop-opacity=\"0.08\" />\n </linearGradient>\n <linearGradient id=\"tlMoodLineW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#16a34a\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" />\n <stop offset=\"100%\" stop-color=\"#dc2626\" />\n </linearGradient>\n </defs>\n <g class=\"tl-chart-guides\">\n @for (g of cd.moodGuides; track g) {\n <line\n [attr.x1]=\"cd.PAD_L\"\n [attr.x2]=\"cd.W - cd.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n stroke=\"#e5e7eb\"\n stroke-dasharray=\"2 4\"\n stroke-width=\"1\"\n />\n }\n @for (g of cd.moodGuides; track g) {\n <text\n [attr.x]=\"cd.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n [attr.data-mood]=\"g.mood\"\n text-anchor=\"end\"\n class=\"tl-chart-guide-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path [attr.d]=\"cd.areaPath\" fill=\"url(#tlMoodGradientW)\" />\n <path\n [attr.d]=\"cd.linePath\"\n fill=\"none\"\n stroke=\"url(#tlMoodLineW)\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <g class=\"tl-chart-x-labels\">\n @for (p of cd.points; track p) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"cd.PAD_T + cd.PLOT_H + 38\"\n text-anchor=\"middle\"\n class=\"tl-chart-x-label\"\n [class.past]=\"p.entry.state === 'past'\"\n [class.current]=\"p.entry.state === 'current'\"\n >\n {{ p.entry.milestone.periodLabel }}\n </text>\n }\n </g>\n <g class=\"tl-chart-nodes\">\n @for (p of cd.points; track p) {\n <g\n class=\"tl-chart-node\"\n [attr.data-state]=\"p.entry.state\"\n [attr.data-mood]=\"p.entry.milestone.mood\"\n [attr.data-accent]=\"p.entry.milestone.accent\"\n [attr.data-current]=\"p.entry.state === 'current' ? 'true' : null\"\n [attr.data-selected]=\"\n selectedTimelineId() === p.entry.milestone.id ? 'true' : null\n \"\n [attr.transform]=\"'translate(' + p.x + ',' + p.y + ')'\"\n (click)=\"selectTimelineNode(p.entry.milestone.id)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"\n p.entry.milestone.periodLabel + ' - ' + p.entry.milestone.tagline\n \"\n >\n @if (p.entry.state === 'current') {\n <circle\n class=\"tl-chart-pulse\"\n r=\"22\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n />\n }\n @if (selectedTimelineId() === p.entry.milestone.id) {\n <circle\n class=\"tl-chart-halo\"\n r=\"20\"\n fill=\"currentColor\"\n fill-opacity=\"0.18\"\n />\n }\n <circle class=\"tl-chart-dot\" r=\"15\" />\n <text\n class=\"tl-chart-emoji\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n y=\"1\"\n >\n {{ p.entry.milestone.heroEmoji }}\n </text>\n @if (p.entry.state === 'past') {\n <g transform=\"translate(11,-11)\">\n <circle r=\"6.5\" fill=\"#10b981\" />\n <text\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n fill=\"#ffffff\"\n font-size=\"8\"\n font-weight=\"700\"\n y=\"0.5\"\n >\n \u2713\n </text>\n </g>\n }\n </g>\n }\n </g>\n </svg>\n </div>\n }\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-chart-detail\">\n <article\n class=\"tl-item tl-item--detail\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n @if (timelineView() === 'list') {\n <div class=\"tl-nav-view\">\n <div class=\"tl-nav-bar\">\n <button\n type=\"button\"\n class=\"tl-nav-btn\"\n (click)=\"prevTimeline()\"\n [disabled]=\"selectedTimelineIndex() === 0\"\n aria-label=\"C\u1ED9t m\u1ED1c tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n @if (selectedTimelineIndex() > 0) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() - 1].milestone.periodLabel }}\n </span>\n }\n </button>\n <div class=\"tl-nav-position\">\n @if (currentTimelineMilestone()) {\n <button\n type=\"button\"\n class=\"tl-nav-today\"\n (click)=\"goToCurrentTimeline()\"\n [class.active]=\"\n selectedTimelineEntry() && selectedTimelineEntry()!.state === 'current'\n \"\n title=\"V\u1EC1 c\u1ED9t m\u1ED1c hi\u1EC7n t\u1EA1i\"\n >\n <i class=\"pi pi-compass\"></i>\n Hi\u1EC7n t\u1EA1i\n </button>\n }\n <span class=\"tl-nav-count\">\n {{ selectedTimelineIndex() + 1 }}/{{ timelineEntries().length }}\n </span>\n </div>\n <button\n type=\"button\"\n class=\"tl-nav-btn tl-nav-btn--right\"\n (click)=\"nextTimeline()\"\n [disabled]=\"selectedTimelineIndex() >= timelineEntries().length - 1\"\n aria-label=\"C\u1ED9t m\u1ED1c ti\u1EBFp theo\"\n >\n @if (selectedTimelineIndex() < timelineEntries().length - 1) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() + 1].milestone.periodLabel }}\n </span>\n }\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n <div class=\"tl-progress-rail\">\n @for (e of timelineEntries(); track e; let i = $index) {\n <button\n type=\"button\"\n class=\"tl-rail-dot\"\n [attr.data-state]=\"e.state\"\n [attr.data-selected]=\"selectedTimelineIndex() === i ? 'true' : null\"\n (click)=\"selectTimelineNode(e.milestone.id)\"\n [attr.aria-label]=\"e.milestone.periodLabel\"\n [title]=\"e.milestone.periodLabel + ' \u00B7 ' + e.milestone.tagline\"\n ></button>\n }\n </div>\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-nav-body\">\n <article\n class=\"tl-item tl-item--nav\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n <div class=\"tl-sources\">\n <i class=\"pi pi-info-circle\"></i>\n T\u1ED5ng h\u1EE3p t\u1EEB: AAP HealthyChildren, CDC Milestones, WHO, The Wonder Weeks (Plooij), Vinmec.\n </div>\n </div>\n </div>\n }\n\n <!-- WHO Standards Table Dialog -->\n @if (whoTableDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeWhoTableDialog()\">\n <div\n class=\"dialog dialog-wide who-table-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">\n <i class=\"pi pi-globe\"></i>\n WHO 0-24 th\u00E1ng\n </div>\n <h2>B\u1EA3ng c\u00E2n n\u1EB7ng & chi\u1EC1u cao theo tu\u1ED5i</h2>\n <p class=\"dialog-lead\">{{ gender() === 'girl' ? 'B\u00E9 g\u00E1i' : 'B\u00E9 trai' }} \u00B7 0-24 th\u00E1ng</p>\n </div>\n <button\n type=\"button\"\n class=\"dialog-close\"\n (click)=\"closeWhoTableDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <div class=\"who-table-legend\">\n <span class=\"who-legend-item who-legend-item--min\">\n <i class=\"pi pi-arrow-down\"></i> Min (\u22122SD)\n </span>\n <span class=\"who-legend-item who-legend-item--median\">\n <i class=\"pi pi-minus\"></i> Trung v\u1ECB (P50)\n </span>\n <span class=\"who-legend-item who-legend-item--max\">\n <i class=\"pi pi-arrow-up\"></i> Max (+2SD)\n </span>\n </div>\n <div class=\"who-table-scroll\">\n <table class=\"who-table\">\n <thead>\n <tr>\n <th class=\"who-th-age\">Tu\u1ED5i</th>\n <th class=\"who-th-value who-th-merged\">\n <span class=\"who-th-title\">C\u00E2n n\u1EB7ng</span>\n <span class=\"who-th-unit\">kg</span>\n </th>\n <th class=\"who-th-value who-th-merged\">\n <span class=\"who-th-title\">Chi\u1EC1u cao</span>\n <span class=\"who-th-unit\">cm</span>\n </th>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <th class=\"who-th-current\">B\u00E9</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of whoStandardTable(); track row) {\n <tr [class.who-row-current]=\"row.isCurrent\">\n <td class=\"who-td-age\">\n <span class=\"who-age-label\">{{ row.label }}</span>\n @if (row.weeks > 0) {\n <span class=\"who-age-weeks\">{{ row.weeks }} tu\u1EA7n</span>\n }\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatKg(row.minKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatKg(row.medianKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatKg(row.maxKg) }}</strong>\n </div>\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatCm(row.minCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatCm(row.medianCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatCm(row.maxCm) }}</strong>\n </div>\n </td>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <td class=\"who-td-current who-td-current--stack\">\n <div class=\"who-current-stack\">\n @if (row.currentWeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentWeightLevel(row)\"\n >\n <strong>{{ formatKg(row.currentWeight) }}</strong>\n <span class=\"who-current-unit\">kg</span>\n </div>\n }\n @if (row.currentHeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentHeightLevel(row)\"\n >\n <strong>{{ formatCm(row.currentHeight) }}</strong>\n <span class=\"who-current-unit\">cm</span>\n </div>\n }\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n <div class=\"who-table-footer\">\n <p class=\"who-footer-note\">\n <i class=\"pi pi-info-circle\"></i>\n M\u1ED1c <strong>-2SD</strong>: c\u00F3 th\u1EC3 g\u1EE3i \u00FD suy dinh d\u01B0\u1EE1ng, thi\u1EBFu ch\u1EA5t ho\u1EB7c th\u1EA5p c\u00F2i. M\u1ED1c\n <strong>+2SD</strong>: c\u00F3 th\u1EC3 g\u1EE3i \u00FD th\u1EEBa c\u00E2n, b\u00E9o ph\u00EC ho\u1EB7c r\u1EA5t cao.\n </p>\n <p class=\"who-footer-note who-footer-note--muted\">\n S\u1ED1 li\u1EC7u theo Chu\u1EA9n t\u0103ng tr\u01B0\u1EDFng tr\u1EBB em WHO 2024 (\u00E1p d\u1EE5ng t\u1EA1i Vi\u1EC7t Nam), ch\u1EC9 mang t\u00EDnh\n tham kh\u1EA3o v\u00E0 kh\u00F4ng thay cho t\u01B0 v\u1EA5n y t\u1EBF.\n </p>\n @if (whoReferenceRows()[0]; as r0) {\n <p class=\"who-footer-note who-footer-note--muted\">\n P50 tham kh\u1EA3o: s\u01A1 sinh ~{{ formatKg(r0.weightMedianKg) }} kg,\n {{ formatCm(r0.heightMedianCm) }} cm.\n </p>\n }\n </div>\n </div>\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--border-strong: #d1d5db;--primary: #5b6cff;--primary-2: #4c5cf0;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--rose: #ef4444;--rose-soft: #fee2e2}.weight-page{max-width:920px;margin:0 auto;padding:12px 16px calc(132px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:4px}.weight-page>.card,.weight-page>.weight-secondary-suite{padding:14px;margin-bottom:11px;border-radius:14px}.weight-page>.timeline-cta{margin-bottom:11px;padding:10px 12px;gap:10px;border-radius:12px}.weight-page>.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.weight-page>.timeline-cta .tcta-now{font-size:12.5px}.weight-alerts-block{padding-top:10px;margin-bottom:2px}.weight-inline-hint{display:flex;align-items:flex-start;gap:8px;margin:0 0 8px;padding:0;font-size:12px;line-height:1.45;color:var(--muted)}.weight-inline-hint i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-ref-cta{display:inline-flex;align-items:center;gap:8px;margin:0 0 10px;padding:8px 14px;border-radius:10px;border:1px solid var(--border);background:#fff;color:var(--text);font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;transition:border-color .15s,background .15s,color .15s}.who-ref-cta i{color:var(--primary)}.who-ref-cta:hover{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.card{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:20px;margin-bottom:16px}.card-header{margin-bottom:16px}.card-header h2{margin:0 0 6px;font-size:17px;font-weight:700;display:flex;align-items:center;gap:8px;color:var(--text)}.card-header h2 i{color:var(--primary)}.card-header p{margin:0;font-size:13px;color:var(--muted);line-height:1.45}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:10px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.form.weight-form{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:end}@media(max-width:640px){.form.weight-form{grid-template-columns:1fr}}.form.weight-form .field--grow{grid-column:1/-1}@media(min-width:641px){.form.weight-form .field--grow{grid-column:span 2}}.form.weight-form .btn-primary{grid-column:1/-1;justify-self:start}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit;background:#fff;color:var(--text)}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:10px 16px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer;transition:background .15s,transform .12s}.btn:disabled{opacity:.55;cursor:not-allowed}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost:hover:not(:disabled){color:var(--primary);border-color:var(--primary)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon{padding:8px}.btn:active:not(:disabled){transform:scale(.98)}.history-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.history-head h3{margin:0;font-size:15px;font-weight:700}.history-head .muted{font-size:12px;color:var(--muted)}.empty-mini{font-size:13px;color:var(--muted);padding:12px 0}.weight-list{list-style:none;margin:0;padding:0}.weight-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:12px 0;border-bottom:1px solid var(--border)}.weight-row:last-child{border-bottom:none}.weight-row-main{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 12px;min-width:0}.w-date{font-weight:600;font-size:14px;color:var(--text)}.w-kg{font-size:15px;font-weight:700;color:var(--primary)}.w-height{font-size:14px;font-weight:700;color:#0ea5e9}.w-note{font-size:13px;color:var(--muted);flex-basis:100%}.weight-row-actions{display:flex;gap:4px;flex-shrink:0}.weight-page .weight-history .history-head{margin-bottom:8px}.weight-page .weight-history .history-head h3{font-size:14px}.weight-page .weight-history .weight-row{padding:9px 0}.weight-page .weight-history .empty-mini{padding:8px 0;font-size:12.5px}.edit-overlay{position:fixed;inset:0;background:#0f172a59;z-index:120}.edit-sheet{position:fixed;left:0;right:0;bottom:0;z-index:121;max-height:85vh;overflow:auto;background:var(--surface);border-radius:18px 18px 0 0;padding:16px 20px calc(20px + env(safe-area-inset-bottom,0px));box-shadow:0 -12px 40px #0f172a1f;animation:sheetUp .22s ease-out}@media(min-width:640px){.edit-sheet{inset:50% auto auto 50%;transform:translate(-50%,-50%);max-width:400px;border-radius:16px;max-height:90vh;animation:none}}@keyframes sheetUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.edit-sheet-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.edit-sheet-head h3{margin:0;font-size:16px;font-weight:700}.edit-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px}.edit-sheet .field{margin-bottom:12px}.weight-eval[data-status=ok]{border-color:#a7f3d0}.weight-eval[data-status=mid]{border-color:#fde68a}.weight-eval[data-status=warn]{border-color:#fecaca}.weight-eval__head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px}.weight-eval__head h3{margin:2px 0 0;font-size:16px;font-weight:700}.weight-eval__head .muted{font-size:11.5px;color:var(--muted);margin:3px 0 0}.eyebrow{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--primary);display:flex;align-items:center;gap:6px}.eyebrow i{font-size:11px}.weight-eval__badge{flex-shrink:0;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:700;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.weight-eval__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.weight-eval__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.weight-eval__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.weight-eval__detail{margin:0;font-size:13px;line-height:1.55;color:var(--text);text-align:justify;text-justify:inter-word;-webkit-hyphens:manual;hyphens:manual}.weight-chart-card .history-head{margin-bottom:10px}.weight-chart-card .history-head h3{font-size:14px}.weight-chart-card.weight-chart-card--compare .history-head--chart h3{margin-bottom:6px}.weight-chart-card.weight-chart-card--compare .chart-sub{margin:0 0 8px;font-size:13px;line-height:1.45;color:var(--muted)}.weight-chart-card.weight-chart-card--compare .chart-meta{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;background:var(--surface-2, rgba(0, 0, 0, .04));color:var(--text)}.weight-chart-card.weight-chart-card--compare .chart-meta__pill i{font-size:12px;opacity:.85}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 220px}.weight-chart-card.weight-chart-card--compare .compare-legend{display:flex;flex-wrap:wrap;gap:8px 14px;margin-bottom:10px;font-size:11.5px;color:var(--text)}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-item{display:inline-flex;align-items:center;gap:8px}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--actual{background:#5b6cff;box-shadow:0 0 0 2px #fff}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--height{background:#0ea5e9}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line{width:20px;height:3px;border-radius:2px;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line--median{background:#16a34a}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-band{width:20px;height:11px;border-radius:2px;flex-shrink:0;background:linear-gradient(180deg,#22c55e38,#22c55e0f);border:1px solid rgba(34,197,94,.35)}.weight-chart-card.weight-chart-card--compare .chart-meta--tight{margin-top:6px;gap:6px}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 180px;min-width:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth strong{font-weight:700;word-break:break-word}.chart-meta--tight{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-secondary-suite .weight-secondary-suite__head{margin-bottom:8px}.weight-secondary-suite .weight-secondary-suite__head h3{margin:0;font-size:14px;font-weight:800;display:flex;align-items:center;gap:6px;color:var(--text)}.weight-secondary-suite .weight-secondary-suite__head h3 i{color:var(--primary)}.weight-insights-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:720px){.weight-insights-grid{grid-template-columns:1fr}}.weight-subchart--full{grid-column:1/-1}.weight-subchart{min-width:0}.weight-subchart h4{margin:0 0 5px;font-size:12px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:5px}.weight-subchart h4 i{color:var(--primary);font-size:12px}.weight-subchart-svg .w-zero-line{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 3}.weight-subchart-svg .w-path--velocity{fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}.weight-subchart-svg .w-dot--velocity{fill:#fff;stroke:#14b8a6;stroke-width:2}.chart-scroll-x{overflow-x:auto;margin:0 -6px;padding:4px 6px}.chart-scroll-x::-webkit-scrollbar{height:6px}.chart-scroll-x::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:4px}.weight-svg-chart{display:block;min-width:100%;height:auto}.weight-svg-chart .grid line{stroke:#eef2f7;stroke-width:1}.weight-svg-chart .grid-label{font-size:11px;letter-spacing:.02em;fill:var(--muted)}.weight-svg-chart .w-area{stroke:none}.weight-svg-chart .w-path--actual{fill:none;stroke-width:2.75;stroke-linecap:round;stroke-linejoin:round}.weight-svg-chart .w-dot{fill:#5b6cff;stroke:#fff;stroke-width:2}.weight-svg-chart .w-dot--height{fill:#0ea5e9}.weight-svg-chart .w-dot-label{font-size:10px;font-weight:700;fill:var(--text)}.weight-svg-chart .x-label{font-size:9px;fill:var(--muted);letter-spacing:.04em}.weight-svg-chart .axis-x-baseline{stroke:var(--border-strong);stroke-width:1.25}.weight-svg-chart .x-label--rot{font-size:10.5px;font-weight:600;fill:#475569}.weight-svg-chart .x-label--tick{font-size:10px;font-weight:600}.weight-svg-chart.weight-svg-chart--compare .w-band-fill{stroke:none}.weight-svg-chart.weight-svg-chart--compare .w-path--ref-edge{fill:none;stroke:#94a3b8;stroke-width:1.15;stroke-dasharray:5 4;opacity:.9}.weight-svg-chart.weight-svg-chart--compare .w-path--median{fill:none;stroke:#16a34a;stroke-width:2.1;opacity:.95}.weight-svg-chart.weight-svg-chart--compare .w-path--height{stroke-width:2.8}.weight-svg-chart.weight-svg-chart--compare .x-label--age{font-size:10px;font-weight:600;fill:var(--text);letter-spacing:.01em}.weight-svg-chart.weight-svg-chart--compare .x-label--sub{font-size:8px;font-weight:400;fill:var(--muted-2);letter-spacing:.02em}.weight-svg-chart.weight-svg-chart--compare .x-label--small{font-size:9px;opacity:.92}.weight-svg-chart.weight-svg-chart--compare .x-tick-line{stroke:var(--border-strong);stroke-width:1}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:20px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 8px 24px #5b6cff73;transition:transform .15s,box-shadow .15s,background .15s}.fab i{font-size:18px;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:#ffffff2e}.fab .fab-label{white-space:nowrap}.fab:hover{background:var(--primary-2);transform:translateY(-2px);box-shadow:0 12px 28px #5b6cff8c}.fab:active{transform:translateY(0)}@media(max-width:480px){.fab{padding:14px}.fab .fab-label{display:none}.fab i{font-size:22px;width:28px;height:28px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px;animation:wovelayIn .2s ease-out}@media(max-width:640px){.dialog-overlay{padding:calc(env(safe-area-inset-top,0px) + 16px) 12px calc(env(safe-area-inset-bottom,0px) + 16px);align-items:flex-start}}@keyframes wovelayIn{0%{opacity:0}to{opacity:1}}.dialog{background:var(--surface);border-radius:20px;width:100%;max-width:560px;max-height:min(100vh - 40px,100dvh - 40px);overflow-y:auto;padding:24px;animation:wdialogIn .25s cubic-bezier(.4,0,.2,1);box-shadow:0 20px 60px #0f172a40}@media(max-width:640px){.dialog{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 32px);border-radius:16px}}@keyframes wdialogIn{0%{opacity:0;transform:translateY(20px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:18px}.dialog-head h2{margin:4px 0 0;font-size:20px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{background:#f1f3f7;border:none;width:36px;height:36px;border-radius:50%;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.log-form{display:flex;flex-direction:column;gap:14px}.weight-log-grid{grid-template-columns:1fr 1fr!important}@media(max-width:560px){.weight-log-grid{grid-template-columns:1fr!important}}.log-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.log-grid .field.full{grid-column:1/-1}.note-quick-tags{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;margin-top:4px;margin-bottom:4px}.note-quick-label{font-size:12px;font-weight:600;color:var(--muted);width:100%;margin-bottom:2px}@media(min-width:400px){.note-quick-label{width:auto;margin-bottom:0}}.note-chip{appearance:none;border:1px solid var(--border);background:#f8fafc;color:var(--text);font-size:12px;font-weight:600;font-family:inherit;padding:6px 12px;border-radius:999px;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.note-chip:hover{border-color:var(--primary);color:var(--primary);background:var(--primary-soft)}.note-chip.active{background:var(--primary);border-color:var(--primary);color:#fff}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:8px}.dialog-wide{max-width:720px;display:flex;flex-direction:column;padding:0;overflow:hidden}@media(max-width:640px){.dialog-wide{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 72px);border-radius:14px}}.dialog-wide .dialog-head{position:sticky;top:0;z-index:5;background:var(--surface);padding:16px 20px;margin:0;border-bottom:1px solid var(--border);-webkit-backdrop-filter:saturate(1.2) blur(6px);backdrop-filter:saturate(1.2) blur(6px)}@media(max-width:640px){.dialog-wide .dialog-head{padding:12px 14px}}.dialog-wide .dialog-close{width:40px;height:40px;font-size:16px;background:#f1f3f7;color:var(--text)}.dialog-wide .dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.who-table-btn{display:inline-flex;align-items:center;gap:6px;margin-top:12px;padding:8px 14px;font-size:12px;font-weight:600;color:var(--primary);background:var(--primary-soft);border:1px solid transparent;border-radius:8px;cursor:pointer;transition:all .15s ease}.who-table-btn i{font-size:14px}.who-table-btn:hover{background:var(--primary);color:#fff}.who-table-dialog .dialog-head h2{display:flex;align-items:center;gap:8px;font-size:17px;font-weight:700;margin:0;color:var(--text)}.who-table-legend{display:flex;flex-wrap:wrap;align-items:center;gap:12px 20px;padding:12px 20px;background:#f8fafc;border-bottom:1px solid var(--border);font-size:12px}@media(max-width:640px){.who-table-legend{padding:10px 14px;gap:8px 14px}}.who-legend-item{display:inline-flex;align-items:center;gap:5px;font-weight:500;color:var(--muted)}.who-legend-item i{font-size:11px}.who-legend-item--min,.who-legend-item--min i{color:#f59e0b}.who-legend-item--median,.who-legend-item--median i{color:#22c55e}.who-legend-item--max,.who-legend-item--max i{color:#3b82f6}.who-table-scroll{flex:1;overflow-y:auto;padding:0;max-height:420px}@media(max-width:640px){.who-table-scroll{max-height:320px}}.who-table{width:100%;border-collapse:collapse;font-size:13px}.who-table thead{position:sticky;top:0;z-index:2;background:var(--surface)}.who-table th{padding:10px 12px;text-align:center;font-weight:600;color:var(--text);border-bottom:2px solid var(--border);background:var(--surface)}.who-table th.who-th-age{text-align:left;padding-left:20px}.who-table th.who-th-median{background:#f0fdf4;color:#166534}.who-table th.who-th-merged{background:#f8fafc;color:var(--text);min-width:120px}.who-table th.who-th-current{background:#eef2ff;color:var(--primary)}.who-table td{padding:10px 12px;text-align:center;border-bottom:1px solid #f1f5f9;vertical-align:middle}.who-table tbody tr{transition:background .15s ease}.who-table tbody tr:hover{background:#f8fafc}.who-td-age{text-align:left!important;padding-left:20px!important}.who-age-label{display:block;font-weight:600;color:var(--text);font-size:13px}.who-th-title{display:block}.who-th-unit{display:block;margin-top:2px;font-size:10.5px;font-weight:600;color:var(--muted);line-height:1.15}.who-age-weeks{display:block;font-size:11px;color:var(--muted-2);margin-top:1px}.who-td-value{font-variant-numeric:tabular-nums;font-weight:500}.who-td-merged{text-align:left!important;min-width:120px}.who-range-line{display:flex;align-items:baseline;justify-content:center;gap:8px;line-height:1.3;border-radius:8px;padding:3px 8px}.who-range-line strong{font-weight:700;color:var(--text)}.who-range-line+.who-range-line{margin-top:2px}.who-range-line--min{background:#f59e0b24}.who-range-line--median{background:#22c55e24}.who-range-line--median strong{color:#16a34a}.who-range-line--max{background:#3b82f624}.who-td-min{color:#f59e0b}.who-td-median{color:#16a34a;font-weight:600;background:#f0fdf4}.who-td-max{color:#3b82f6}.who-td-current{background:#eef2ff}.who-td-current strong{color:var(--primary);font-weight:700;font-size:14px}.who-td-current .who-current-unit{color:var(--muted);font-size:11px;margin-left:2px}.who-td-current--stack{min-width:92px}.who-current-stack{display:flex;flex-direction:column;gap:2px}.who-current-row{display:flex;align-items:baseline;justify-content:center;gap:2px;border-radius:999px;border:1px solid transparent;padding:2px 8px}.who-current-row[data-level=ok]{background:#22c55e2e;border-color:#16a34a61}.who-current-row[data-level=ok] strong{color:#166534}.who-current-row[data-level=mid]{background:#f59e0b33;border-color:#b4530959}.who-current-row[data-level=mid] strong{color:#9a3412}.who-current-row[data-level=warn]{background:#ef44442e;border-color:#dc262659}.who-current-row[data-level=warn] strong{color:#b91c1c}.who-row-current{background:linear-gradient(90deg,#fef3c7,#fffbeb)!important}.who-row-current td:first-child{border-left:4px solid #f59e0b;padding-left:16px!important}.who-row-current .who-age-label{color:#92400e}.who-row-current .who-td-median,.who-row-current .who-td-current{background:#fef3c7}.who-row-current .who-td-current strong{color:#92400e}.who-row-current:hover{background:linear-gradient(90deg,#fde68a,#fef3c7)!important}.who-table-footer{padding:14px 20px;background:#f8fafc;border-top:1px solid var(--border)}@media(max-width:640px){.who-table-footer{padding:12px 14px}}.who-footer-note{display:flex;align-items:flex-start;gap:8px;margin:0;font-size:11px;color:var(--muted);line-height:1.5}.who-footer-note i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-footer-note--muted{margin-top:6px;font-size:10.5px;opacity:.9}\n", "@charset \"UTF-8\";.timeline-cta{appearance:none;display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;background:linear-gradient(135deg,#fff,#fbfaff);border:1px solid var(--border);border-radius:12px;cursor:pointer;text-align:left;color:var(--text);transition:border-color .18s ease,box-shadow .18s ease,transform .18s ease}.timeline-cta:hover{border-color:#c4b5fd;box-shadow:0 6px 18px -8px #8b5cf666;transform:translateY(-1px)}.timeline-cta:active{transform:translateY(0)}.timeline-cta .tcta-emoji{flex-shrink:0;width:42px;height:42px;display:flex;align-items:center;justify-content:center;font-size:22px;line-height:1;background:linear-gradient(135deg,#f5f3ff,#fdf2f8);border-radius:10px;border:1px solid #ede9fe}.timeline-cta .tcta-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.timeline-cta .tcta-label{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:#7c3aed;letter-spacing:.5px;text-transform:uppercase}.timeline-cta .tcta-label i{font-size:10px}.timeline-cta .tcta-now{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.timeline-cta .tcta-progress{flex-shrink:0;padding:3px 8px;background:#ede9fe;color:#6d28d9;border-radius:999px;font-size:11px;font-weight:700;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;line-height:1.4}.timeline-cta>i{flex-shrink:0;color:var(--muted-2);font-size:13px;transition:color .18s ease,transform .18s ease}.timeline-cta:hover>i{color:#7c3aed;transform:translate(2px)}@media(max-width:640px){.timeline-cta{padding:10px 12px;gap:10px}.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.timeline-cta .tcta-progress{display:none}}.timeline-dialog .dialog-head .eyebrow{display:inline-flex;align-items:center;gap:6px}.timeline-dialog .dialog-head .eyebrow i{font-size:11px}.timeline-dialog .dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.timeline-dialog .dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.tl-legend{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:10px 20px;background:#fafbfc;border-bottom:1px solid var(--border);font-size:12px;color:var(--muted)}.tl-legend>span:not(.sep):not(.tl-legend-note){display:inline-flex;align-items:center;gap:6px}.tl-legend .sep{color:var(--border-strong)}.tl-legend .tl-legend-note{color:var(--muted-2);font-style:italic;margin-left:auto}@media(max-width:640px){.tl-legend .tl-legend-note{margin-left:0;flex-basis:100%;font-size:11px}}@media(max-width:640px){.tl-legend{gap:10px;padding:10px 14px}}.tl-dot{display:inline-block;width:10px;height:10px;border-radius:50%}.tl-dot.past{background:#cbd5e1}.tl-dot.current{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf633}.tl-dot.future{background:#fff;border:1.5px solid #cbd5e1}.tl-body{padding:18px 20px 8px}@media(max-width:640px){.tl-body{padding:12px 12px 6px}}.tl-axis{display:none}.tl-item{display:grid;grid-template-columns:56px 1fr;gap:14px;position:relative;padding-bottom:20px}@media(max-width:640px){.tl-item{grid-template-columns:44px 1fr;gap:10px;padding-bottom:14px}}.tl-item:before{content:\"\";position:absolute;left:27px;top:56px;bottom:0;width:2px;background:#e5e7eb}@media(max-width:640px){.tl-item:before{left:21px;top:44px}}.tl-item:last-child:before{display:none}.tl-item[data-accent=rose]{--tl-accent: #f43f5e;--tl-accent-soft: #fff1f2;--tl-accent-dim: #fecdd3}.tl-item[data-accent=peach]{--tl-accent: #fb923c;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=coral]{--tl-accent: #f97316;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=amber]{--tl-accent: #d97706;--tl-accent-soft: #fffbeb;--tl-accent-dim: #fde68a}.tl-item[data-accent=mint]{--tl-accent: #10b981;--tl-accent-soft: #ecfdf5;--tl-accent-dim: #a7f3d0}.tl-item[data-accent=sky]{--tl-accent: #0ea5e9;--tl-accent-soft: #f0f9ff;--tl-accent-dim: #bae6fd}.tl-item[data-accent=lavender]{--tl-accent: #8b5cf6;--tl-accent-soft: #f5f3ff;--tl-accent-dim: #ddd6fe}.tl-item[data-accent=plum]{--tl-accent: #c026d3;--tl-accent-soft: #fdf4ff;--tl-accent-dim: #f5d0fe}.tl-item[data-state=past] .tl-card{background:#fafbfc;border-color:#e5e7eb}.tl-item[data-state=past] .tl-tagline,.tl-item[data-state=past] .tl-period-label{color:var(--muted)}.tl-item[data-state=past] .tl-marker{background:#f3f4f6;border-color:#d1d5db}.tl-item[data-state=past] .tl-marker .tl-hero-emoji{filter:grayscale(.35) opacity(.8)}.tl-item[data-state=current] .tl-card{background:var(--tl-accent-soft, #f5f3ff);border-color:var(--tl-accent, #8b5cf6);box-shadow:0 4px 16px -6px color-mix(in srgb,var(--tl-accent) 30%,transparent)}.tl-item[data-state=current] .tl-marker{background:var(--tl-accent);border-color:var(--tl-accent)}.tl-item[data-state=current]:before{background:linear-gradient(180deg,var(--tl-accent) 0%,#e5e7eb 100%)}.tl-item[data-state=future] .tl-card{background:#fff}.tl-marker{position:relative;width:56px;height:56px;border-radius:50%;background:#fff;border:2.5px solid var(--tl-accent, #cbd5e1);display:flex;align-items:center;justify-content:center;transition:all .2s ease;z-index:2}@media(max-width:640px){.tl-marker{width:44px;height:44px;border-width:2px}}.tl-marker .tl-hero-emoji{font-size:26px;line-height:1}@media(max-width:640px){.tl-marker .tl-hero-emoji{font-size:20px}}.tl-marker .tl-check{position:absolute;bottom:-2px;right:-2px;width:20px;height:20px;background:#10b981;color:#fff;border:2px solid #ffffff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px}@media(max-width:640px){.tl-marker .tl-check{width:16px;height:16px;font-size:8px;border-width:1.5px}}.tl-marker .tl-now-pulse{position:absolute;inset:-5px;border-radius:50%;border:2px solid var(--tl-accent);animation:tl-pulse 2.2s ease-in-out infinite;pointer-events:none}@keyframes tl-pulse{0%,to{transform:scale(1);opacity:.5}50%{transform:scale(1.25);opacity:0}}.tl-card{min-width:0;background:#fff;border:1px solid var(--border);border-radius:12px;padding:14px 14px 12px;transition:background .2s ease,border-color .2s ease,box-shadow .2s ease}@media(max-width:640px){.tl-card{padding:12px 12px 10px;border-radius:10px}}.tl-card-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;flex-wrap:wrap;margin-bottom:10px}.tl-period{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.tl-period-label{font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.6px}.tl-tagline{font-size:14.5px;font-weight:700;color:var(--text);line-height:1.3;word-break:break-word}.tl-mood-badge{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:6px;font-size:11px;font-weight:600;line-height:1.3;white-space:nowrap;flex-shrink:0;max-width:100%;overflow:hidden;text-overflow:ellipsis}.tl-mood-badge i{font-size:9px;flex-shrink:0}@media(max-width:640px){.tl-mood-badge{font-size:10.5px;padding:3px 8px}}.tl-mood-badge[data-mood=happy]{background:#dcfce7;color:#15803d}.tl-mood-badge[data-mood=milestone]{background:#d1fae5;color:#047857}.tl-mood-badge[data-mood=calm]{background:#fef9c3;color:#854d0e}.tl-mood-badge[data-mood=growth]{background:#ffedd5;color:#c2410c}.tl-mood-badge[data-mood=leap]{background:#fee2e2;color:#b91c1c}.tl-stomach{display:grid;grid-template-columns:52px 1fr;align-items:center;gap:12px;padding:10px 12px;background:#fafbfc;border:1px solid var(--border);border-radius:10px;margin-bottom:10px}.tl-stomach-visual{position:relative;width:52px;height:52px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.tl-stomach-circle{position:absolute;top:50%;left:50%;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:color-mix(in srgb,var(--tl-accent, #8b5cf6) 15%,#ffffff);border:1.5px dashed color-mix(in srgb,var(--tl-accent, #8b5cf6) 40%,transparent)}.tl-stomach-emoji{position:relative;font-size:26px;line-height:1;z-index:1;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.tl-stomach-text{min-width:0}.tl-stomach-title{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:var(--text);line-height:1.4}.tl-stomach-title i{color:var(--tl-accent, var(--muted));font-size:12px;flex-shrink:0}.tl-stomach-vol{font-size:11.5px;color:var(--muted);margin-top:2px;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-weight:600}.tl-now-banner{display:flex;align-items:center;gap:7px;padding:8px 12px;background:linear-gradient(135deg,#8b5cf6,#ec4899);color:#fff;border-radius:8px;font-size:12.5px;font-weight:600;margin-bottom:10px}.tl-now-banner i{font-size:13px}.tl-skills{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:10px}.tl-skills-label{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--muted)}.tl-skills-label i{color:var(--tl-accent);font-size:10px}.tl-skill-chip{padding:3px 9px;background:var(--tl-accent-soft, #f3f4f6);color:var(--tl-accent, var(--text));border:1px solid color-mix(in srgb,var(--tl-accent, #cbd5e1) 35%,transparent);border-radius:999px;font-size:11px;font-weight:600;line-height:1.3}.tl-section{margin-top:12px}.tl-section:first-of-type{margin-top:8px}.tl-section-label{display:flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tl-section-label i{font-size:10px}.tl-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}.tl-list li{font-size:12.5px;line-height:1.55;color:var(--text);padding-left:14px;position:relative}.tl-list li:before{content:\"\";position:absolute;left:3px;top:8px;width:5px;height:5px;border-radius:50%;background:var(--tl-accent, var(--muted-2))}.tl-section-notes{background:color-mix(in srgb,var(--tl-accent, #f59e0b) 7%,#ffffff);padding:10px 12px;border-radius:0;border-left:3px solid var(--tl-accent, #f59e0b);margin-top:12px}.tl-section-notes .tl-section-label{margin-bottom:6px}.tl-section-notes .tl-list li:before{background:var(--tl-accent, #f59e0b)}.tl-care-grid{display:grid;grid-template-columns:1fr;gap:10px;margin-top:12px}.tl-section-care{margin-top:0;padding:10px 12px;border-radius:12px;border:1px solid var(--border);background:#fff}.tl-section-care .tl-section-label{margin-bottom:7px}.tl-section-care--todo{background:color-mix(in srgb,var(--mint, #10b981) 8%,#ffffff);border-color:color-mix(in srgb,var(--mint, #10b981) 24%,var(--border))}.tl-section-care--todo .tl-section-label{color:#047857}.tl-section-care--todo .tl-list li:before{background:#047857}.tl-section-care--avoid{background:color-mix(in srgb,var(--rose, #ef4444) 7%,#ffffff);border-color:color-mix(in srgb,var(--rose, #ef4444) 24%,var(--border))}.tl-section-care--avoid .tl-section-label{color:#b91c1c}.tl-section-care--avoid .tl-list li:before{background:#ef4444}.tl-section-care--watch{background:color-mix(in srgb,var(--amber, #f59e0b) 9%,#ffffff);border-color:color-mix(in srgb,var(--amber, #f59e0b) 28%,var(--border))}.tl-section-care--watch .tl-section-label{color:#b45309}.tl-section-care--watch .tl-list li:before{background:#f59e0b}.tl-sources{display:flex;gap:6px;padding:10px 20px 14px;font-size:11px;color:var(--muted-2);line-height:1.5;border-top:1px solid var(--border);background:#fafbfc}.tl-sources i{color:var(--muted);font-size:12px;margin-top:1px;flex-shrink:0}@media(max-width:640px){.tl-sources{padding:10px 14px 12px}}.tl-tabs{display:flex;gap:4px;padding:10px 20px 0;background:#fff;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-tabs{padding:8px 12px 0}}.tl-tab{appearance:none;background:transparent;border:none;border-bottom:2px solid transparent;padding:9px 14px 10px;cursor:pointer;display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:600;color:var(--muted);transition:color .15s ease,border-color .15s ease,background .15s ease;border-radius:8px 8px 0 0}.tl-tab i{font-size:12px}.tl-tab:hover{color:var(--text);background:#fafbfc}.tl-tab.active{color:#7c3aed;border-bottom-color:#7c3aed;background:#f5f3ff}@media(max-width:640px){.tl-tab{padding:8px 10px 9px;font-size:12.5px;gap:5px}.tl-tab i{font-size:11px}}.tl-chart-view{padding:14px 20px 20px}@media(max-width:640px){.tl-chart-view{padding:10px 12px 14px}}.tl-chart-mood-legend{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;padding:8px 10px;background:#fafbfc;border:1px solid var(--border);border-radius:8px}.tl-chart-scroll{overflow-x:auto;overflow-y:hidden;background:#fff;border:1px solid var(--border);border-radius:10px;padding:4px;scrollbar-width:thin}.tl-chart-scroll::-webkit-scrollbar{height:8px}.tl-chart-scroll::-webkit-scrollbar-track{background:#f3f4f6;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb{background:#c4b5fd;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb:hover{background:#8b5cf6}.tl-chart-svg{display:block;min-width:100%}.tl-chart-guide-label{font-size:10px;fill:var(--muted-2);font-weight:700;font-family:inherit}.tl-chart-guide-label[data-mood=happy]{fill:#15803d}.tl-chart-guide-label[data-mood=calm]{fill:#a16207}.tl-chart-guide-label[data-mood=growth]{fill:#c2410c}.tl-chart-guide-label[data-mood=leap]{fill:#b91c1c}.tl-chart-x-label{font-size:10.5px;fill:var(--muted);font-weight:600;font-family:inherit}.tl-chart-x-label.current{fill:#7c3aed;font-weight:700}.tl-chart-x-label.past{fill:var(--muted-2)}.tl-chart-node{cursor:pointer;transition:transform .15s ease}.tl-chart-node[data-mood=happy]{--tl-mood: #22c55e;--tl-mood-soft: #dcfce7;color:#22c55e}.tl-chart-node[data-mood=milestone]{--tl-mood: #10b981;--tl-mood-soft: #d1fae5;color:#10b981}.tl-chart-node[data-mood=calm]{--tl-mood: #eab308;--tl-mood-soft: #fef9c3;color:#eab308}.tl-chart-node[data-mood=growth]{--tl-mood: #f97316;--tl-mood-soft: #ffedd5;color:#f97316}.tl-chart-node[data-mood=leap]{--tl-mood: #ef4444;--tl-mood-soft: #fee2e2;color:#ef4444}.tl-chart-node .tl-chart-dot{fill:#fff;stroke:var(--tl-mood, #cbd5e1);stroke-width:2.5;transition:r .15s ease,stroke-width .15s ease}.tl-chart-node .tl-chart-emoji{font-size:14px;pointer-events:none;-webkit-user-select:none;user-select:none}.tl-chart-node:hover .tl-chart-dot{r:17;stroke-width:3}.tl-chart-node[data-state=past] .tl-chart-dot{fill:#f3f4f6;stroke:#cbd5e1}.tl-chart-node[data-state=past] .tl-chart-emoji{opacity:.6}.tl-chart-node[data-state=current] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current] .tl-chart-emoji{filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))}.tl-chart-node[data-selected=true] .tl-chart-dot{r:17;stroke-width:3;fill:var(--tl-mood-soft, #f5f3ff);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current][data-selected=true] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6)}.tl-chart-pulse{transform-origin:center;transform-box:fill-box;animation:tl-chart-pulse 2s ease-in-out infinite}@keyframes tl-chart-pulse{0%,to{opacity:.5;r:20}50%{opacity:0;r:28}}.tl-chart-detail{margin-top:16px;animation:tl-detail-fade .25s ease}.tl-item--detail{padding-bottom:0!important}.tl-item--detail:before{display:none!important}@keyframes tl-detail-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.timeline-dialog{padding:0!important;overflow:hidden!important;display:flex;flex-direction:column}.timeline-dialog .dialog-head{position:sticky;top:0;z-index:20;margin:0;padding:16px 20px 12px;background:#fff;border-bottom:1px solid var(--border);box-shadow:0 2px 8px -4px #0f172a0f}@media(max-width:640px){.timeline-dialog .dialog-head{padding:14px 14px 10px}}.timeline-dialog .tl-tabs{position:sticky;top:0;z-index:19;background:#fff;padding:6px 20px 0;border-top:none}@media(max-width:640px){.timeline-dialog .tl-tabs{padding:4px 12px 0}}.timeline-dialog .tl-chart-view,.timeline-dialog .tl-nav-view{flex:1;overflow-y:auto}.timeline-dialog .tl-sources{flex-shrink:0;position:sticky;bottom:0;background:#fff;z-index:18;border-top:1px solid var(--border)}.tl-nav-view{display:flex;flex-direction:column}.tl-nav-bar{display:grid;grid-template-columns:1fr auto 1fr;gap:8px;padding:12px 16px;background:#fafbfc;border-bottom:1px solid var(--border);align-items:center}@media(max-width:640px){.tl-nav-bar{padding:10px;gap:6px}}.tl-nav-btn{appearance:none;display:inline-flex;align-items:center;gap:6px;padding:8px 12px;background:#fff;border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:12.5px;font-weight:600;cursor:pointer;transition:all .15s ease;min-width:0;justify-self:start}.tl-nav-btn i{font-size:12px;color:var(--muted);flex-shrink:0}.tl-nav-btn .tl-nav-btn-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:110px}@media(max-width:640px){.tl-nav-btn .tl-nav-btn-label{display:none}}.tl-nav-btn:hover:not(:disabled){border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff;transform:translateY(-1px)}.tl-nav-btn:hover:not(:disabled) i{color:#7c3aed}.tl-nav-btn:disabled{opacity:.4;cursor:not-allowed}.tl-nav-btn--right{justify-self:end;flex-direction:row}@media(max-width:640px){.tl-nav-btn{padding:8px 10px}}.tl-nav-position{display:inline-flex;align-items:center;gap:8px;justify-self:center}.tl-nav-today{appearance:none;display:inline-flex;align-items:center;gap:5px;padding:6px 10px;background:#fff;border:1px solid var(--border);border-radius:999px;color:var(--muted);font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.tl-nav-today i{font-size:10px}.tl-nav-today:hover{border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff}.tl-nav-today.active{background:linear-gradient(135deg,#8b5cf6,#ec4899);border-color:transparent;color:#fff}.tl-nav-today.active:hover{color:#fff}.tl-nav-count{font-size:12px;font-weight:700;color:var(--muted);font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;padding:4px 9px;background:#fff;border:1px solid var(--border);border-radius:6px;white-space:nowrap}.tl-progress-rail{display:flex;align-items:center;gap:3px;padding:10px 16px 2px;overflow-x:auto;scrollbar-width:none}.tl-progress-rail::-webkit-scrollbar{display:none}@media(max-width:640px){.tl-progress-rail{padding:8px 10px 2px}}.tl-rail-dot{appearance:none;flex:1 1 0;min-width:8px;max-width:30px;height:6px;background:#e5e7eb;border:none;border-radius:3px;padding:0;cursor:pointer;transition:background .15s ease,transform .15s ease,height .15s ease}.tl-rail-dot[data-state=past]{background:#cbd5e1}.tl-rail-dot[data-state=current]{background:linear-gradient(90deg,#8b5cf6,#ec4899)}.tl-rail-dot[data-state=future]{background:#e5e7eb}.tl-rail-dot:hover{transform:scaleY(1.6)}.tl-rail-dot[data-selected=true]{height:10px;background:#7c3aed!important;box-shadow:0 0 0 2px #fff,0 0 0 3px #c4b5fd}.tl-nav-body{padding:16px 20px 12px}@media(max-width:640px){.tl-nav-body{padding:12px 12px 8px}}.tl-item--nav,.tl-item--detail{display:block!important;padding-bottom:0!important}.tl-item--nav:before,.tl-item--detail:before{display:none!important}.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{gap:10px}}.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:48px;height:48px;border-width:2px;flex-shrink:0}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:22px}.tl-item--nav .tl-marker .tl-check,.tl-item--detail .tl-marker .tl-check{width:18px;height:18px;font-size:9px;bottom:-2px;right:-2px;border-width:2px}@media(max-width:640px){.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:42px;height:42px}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:20px}}.tl-item--nav .tl-period,.tl-item--detail .tl-period{flex:1 1 120px;min-width:0}.tl-item--nav .tl-mood-badge,.tl-item--detail .tl-mood-badge{margin-left:auto}.tl-item--nav .tl-card,.tl-item--detail .tl-card{animation:tl-card-fade .3s ease;padding:14px}@media(max-width:640px){.tl-item--nav .tl-card,.tl-item--detail .tl-card{padding:12px}}@keyframes tl-card-fade{0%{opacity:0;transform:translate(6px)}to{opacity:1;transform:translate(0)}}.tl-chart-view{padding-top:14px}@media(max-width:640px){.tl-chart-view{padding-top:10px}}.timeline-dialog .dialog-head{align-items:center}.timeline-dialog .dialog-head h2{font-size:17px;margin:2px 0 0}.timeline-dialog .dialog-head .eyebrow{font-size:11px}.timeline-dialog .dialog-head .dialog-lead{margin-top:4px;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: WeightHeaderComponent, selector: "app-weight-header", inputs: ["errorMsg", "successMsg", "birthDate", "currentTimelineMilestone", "timelineStats", "latestGrowthEval", "latestHeightEval", "sortedLogsDesc", "latestHeightLog"], outputs: ["openWhoTable", "openTimeline"] }, { kind: "component", type: WeightChartsComponent, selector: "app-weight-charts", inputs: ["weightVsStandardChart", "heightVsStandardChart", "weightTrendChart", "simpleTrendMode", "weightVelocityLineChart"] }, { kind: "component", type: WeightHistoryListComponent, selector: "app-weight-history-list", inputs: ["loading", "logs"], outputs: ["edit", "remove"] }, { kind: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
14139
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: WeightComponent, isStandalone: true, selector: "app-weight", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null }, birthDate: { classPropertyName: "birthDate", publicName: "birthDate", isSignal: true, isRequired: false, transformFunction: null }, gender: { classPropertyName: "gender", publicName: "gender", isSignal: true, isRequired: false, transformFunction: null }, babyName: { classPropertyName: "babyName", publicName: "babyName", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "document:keydown": "onTimelineKey($event)" } }, ngImport: i0, template: "<div class=\"weight-page\">\n <nav class=\"weight-screen-tabs\" role=\"tablist\" aria-label=\"Th\u1EC3 tr\u1EA1ng\">\n @for (tab of screenTabs; track tab.id) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"weight-screen-tabs__btn\"\n [class.active]=\"screenTab() === tab.id\"\n [attr.aria-selected]=\"screenTab() === tab.id\"\n (click)=\"setScreenTab(tab.id)\"\n >\n <i class=\"pi\" [ngClass]=\"tab.icon\"></i>\n <span>{{ tab.label }}</span>\n </button>\n }\n </nav>\n\n @if (screenTab() === 'dashboard') {\n <div class=\"weight-panel\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"false\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"true\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n @if (growthSnapshot(); as snap) {\n <section class=\"weight-snapshot\" aria-label=\"T\u00F3m t\u1EAFt s\u1ED1 li\u1EC7u\">\n <div class=\"weight-snapshot__head\">\n <h3 class=\"weight-snapshot__title\">T\u00F3m t\u1EAFt s\u1ED1 li\u1EC7u</h3>\n <div class=\"weight-snapshot__toggles\">\n <div class=\"wg-toggle\" role=\"group\" aria-label=\"So s\u00E1nh k\u1EF3\">\n <button\n type=\"button\"\n class=\"wg-toggle__btn\"\n [class.active]=\"comparePeriod() === 'week'\"\n (click)=\"setComparePeriod('week')\"\n >\n Tu\u1EA7n\n </button>\n <button\n type=\"button\"\n class=\"wg-toggle__btn\"\n [class.active]=\"comparePeriod() === 'month'\"\n (click)=\"setComparePeriod('month')\"\n >\n Th\u00E1ng\n </button>\n </div>\n </div>\n </div>\n <div class=\"weight-snapshot__growth\">\n <span class=\"weight-snapshot__growth-label\">Xem theo</span>\n <div class=\"wg-toggle wg-toggle--growth\" role=\"group\" aria-label=\"Ch\u1EC9 s\u1ED1 t\u0103ng tr\u01B0\u1EDFng\">\n <button\n type=\"button\"\n class=\"wg-toggle__btn wg-toggle__btn--icon\"\n [class.active]=\"growthMetric() === 'weight'\"\n (click)=\"setGrowthMetric('weight')\"\n title=\"C\u00E2n n\u1EB7ng\"\n >\n <i class=\"pi pi-chart-bar\"></i>\n <span>C\u00E2n n\u1EB7ng</span>\n </button>\n <button\n type=\"button\"\n class=\"wg-toggle__btn wg-toggle__btn--icon\"\n [class.active]=\"growthMetric() === 'height'\"\n (click)=\"setGrowthMetric('height')\"\n title=\"Chi\u1EC1u cao\"\n >\n <i class=\"pi pi-arrows-v\"></i>\n <span>Chi\u1EC1u cao</span>\n </button>\n </div>\n </div>\n <div class=\"weight-snapshot__grid\">\n <div class=\"weight-metric\">\n <div class=\"weight-metric__label\">\n <i class=\"pi pi-chart-bar\"></i>\n C\u00E2n n\u1EB7ng\n </div>\n <div class=\"weight-metric__value\">\n {{ formatKg(snap.weight.value ?? 0) }}\n <span class=\"weight-metric__unit\">kg</span>\n </div>\n <div\n class=\"weight-metric__delta\"\n [class.weight-metric__delta--down]=\"snap.weight.delta < 0\"\n [class.weight-metric__delta--up]=\"snap.weight.delta > 0\"\n >\n {{ formatSignedDelta(snap.weight.delta, 'kg') }} ({{ formatSignedPercent(snap.weight.pct) }})\n <span class=\"weight-metric__tag\" [title]=\"snap.tip\">{{ snap.tag }}</span>\n </div>\n </div>\n <div class=\"weight-metric\">\n <div class=\"weight-metric__label\">\n <i class=\"pi pi-arrows-v\"></i>\n Chi\u1EC1u cao\n </div>\n <div class=\"weight-metric__value\">\n @if (snap.height.value !== null) {\n {{ formatCm(snap.height.value) }}\n <span class=\"weight-metric__unit\">cm</span>\n } @else {\n <span class=\"weight-metric__unit\">\u2014</span>\n }\n </div>\n <div\n class=\"weight-metric__delta\"\n [class.weight-metric__delta--down]=\"snap.height.delta < 0\"\n [class.weight-metric__delta--up]=\"snap.height.delta > 0\"\n >\n {{ formatSignedDelta(snap.height.delta, 'cm') }}\n <span class=\"weight-metric__tag\" [title]=\"snap.tip\">{{ snap.tag }}</span>\n </div>\n </div>\n <div class=\"weight-metric\">\n <div class=\"weight-metric__label\">\n <i class=\"pi pi-bolt\"></i>\n {{ snap.velocity.label }}\n </div>\n <div class=\"weight-metric__value\">\n @if (snap.velocity.value !== null) {\n {{ snap.velocity.value }}\n <span class=\"weight-metric__unit\">{{ snap.velocity.unit }}</span>\n } @else {\n <span class=\"weight-metric__unit\">\u2014</span>\n }\n </div>\n <div\n class=\"weight-metric__delta\"\n [class.weight-metric__delta--down]=\"snap.velocity.delta < 0\"\n [class.weight-metric__delta--up]=\"snap.velocity.delta > 0\"\n >\n {{ formatSignedDelta(snap.velocity.delta, snap.velocity.suffix) }}\n <span class=\"weight-metric__tag\" [title]=\"snap.tip\">{{ snap.tag }}</span>\n </div>\n </div>\n </div>\n </section>\n }\n\n <app-weight-charts\n [dashboardMode]=\"true\"\n [comparePeriod]=\"comparePeriod()\"\n [growthMetric]=\"growthMetric()\"\n [weightVsStandardChart]=\"weightVsStandardChart()\"\n [heightVsStandardChart]=\"heightVsStandardChart()\"\n [weightTrendChart]=\"weightTrendChart()\"\n [simpleTrendMode]=\"simpleTrendForCharts()\"\n [weightVelocityLineChart]=\"weightVelocityLineChart()\"\n [heightVelocityLineChart]=\"heightVelocityLineChart()\"\n />\n </div>\n }\n\n @if (screenTab() === 'assessment') {\n <div class=\"weight-panel\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"true\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"false\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n </div>\n }\n\n @if (screenTab() === 'history') {\n <div class=\"weight-panel\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"false\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"false\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n <app-weight-history-list\n [loading]=\"loading()\"\n [logs]=\"sortedLogsDesc()\"\n [expanded]=\"true\"\n (edit)=\"openEdit($event)\"\n (remove)=\"deleteLog($event)\"\n />\n </div>\n }\n\n @if (screenTab() === 'reference') {\n <div class=\"weight-panel weight-panel--reference\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"false\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"false\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n <section class=\"who-ref-panel\">\n <header class=\"who-ref-panel__head\">\n <h2><i class=\"pi pi-table\"></i> B\u1EA3ng so s\u00E1nh theo tu\u1ED5i</h2>\n <p>{{ gender() === 'girl' ? 'B\u00E9 g\u00E1i' : 'B\u00E9 trai' }} \u00B7 0\u201324 th\u00E1ng</p>\n </header>\n <div class=\"who-table-legend\">\n <span class=\"who-legend-item who-legend-item--min\">\n <i class=\"pi pi-arrow-down\"></i>\n <span>\n <strong>Min (\u22122SD)</strong>\n <small>h\u01A1i g\u1EA7y \u00B7 h\u01A1i th\u1EA5p</small>\n </span>\n </span>\n <span class=\"who-legend-item who-legend-item--median\">\n <i class=\"pi pi-minus\"></i>\n <span>\n <strong>Trung v\u1ECB (P50)</strong>\n <small>b\u00ECnh th\u01B0\u1EDDng</small>\n </span>\n </span>\n <span class=\"who-legend-item who-legend-item--max\">\n <i class=\"pi pi-arrow-up\"></i>\n <span>\n <strong>Max (+2SD)</strong>\n <small>h\u01A1i n\u1EB7ng \u00B7 h\u01A1i cao</small>\n </span>\n </span>\n </div>\n <div class=\"who-table-scroll who-table-scroll--inline\">\n <table class=\"who-table who-table--inline\">\n <thead>\n <tr>\n <th class=\"who-th-age\">Tu\u1ED5i</th>\n <th class=\"who-th-value who-th-icon\">\n <i class=\"pi pi-chart-bar\" aria-hidden=\"true\"></i>\n <span>(kg)</span>\n </th>\n <th class=\"who-th-value who-th-icon\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n <span>(cm)</span>\n </th>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <th class=\"who-th-current\">B\u00E9</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of whoStandardTable(); track row) {\n <tr [class.who-row-current]=\"row.isCurrent\">\n <td class=\"who-td-age\">\n <span class=\"who-age-label\">{{ row.label }}</span>\n @if (row.weeks > 0) {\n <span class=\"who-age-weeks\">{{ row.weeks }} tu\u1EA7n</span>\n }\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatKg(row.minKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatKg(row.medianKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatKg(row.maxKg) }}</strong>\n </div>\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatCm(row.minCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatCm(row.medianCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatCm(row.maxCm) }}</strong>\n </div>\n </td>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <td class=\"who-td-current who-td-current--stack\">\n <div class=\"who-current-stack\">\n @if (row.currentWeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentWeightLevel(row)\"\n >\n <strong>{{ formatKg(row.currentWeight) }}</strong>\n <span class=\"who-current-unit\">kg</span>\n </div>\n }\n @if (row.currentHeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentHeightLevel(row)\"\n >\n <strong>{{ formatCm(row.currentHeight) }}</strong>\n <span class=\"who-current-unit\">cm</span>\n </div>\n }\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n <p class=\"who-footer-note who-footer-note--inline\">\n <i class=\"pi pi-info-circle\"></i>\n M\u1ED1c <strong>\u22122SD</strong> / <strong>+2SD</strong> ch\u1EC9 mang t\u00EDnh tham kh\u1EA3o.\n </p>\n </section>\n </div>\n }\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAddDialog()\"\n aria-label=\"Th\u00EAm l\u1EA7n c\u00E2n\"\n title=\"Th\u00EAm l\u1EA7n c\u00E2n\"\n >\n <i class=\"pi pi-plus\"></i>\n <span class=\"fab-label\">Th\u00EAm l\u1EA7n c\u00E2n</span>\n </button>\n\n <!-- Dialog th\u00EAm (gi\u1ED1ng c\u1EEF b\u00FA) -->\n @if (addDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeAddDialog()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">Nh\u1EADt k\u00FD c\u00E2n n\u1EB7ng</div>\n <h2><i class=\"pi pi-chart-line\"></i> Th\u00EAm l\u1EA7n c\u00E2n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAddDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <form class=\"log-form\" (ngSubmit)=\"submitAdd()\">\n <div class=\"log-grid weight-log-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y c\u00E2n</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"draft().date\"\n (input)=\"updateDraftDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n n\u1EB7ng (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 4,25\"\n [value]=\"draft().weightInput\"\n (input)=\"updateDraftWeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 63,5\"\n [value]=\"draft().heightInput\"\n (input)=\"updateDraftHeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"Tu\u1EF3 ch\u1ECDn \u2014 ho\u1EB7c ch\u1ECDn g\u1EE3i \u00FD b\u00EAn d\u01B0\u1EDBi\"\n [value]=\"draft().note\"\n (input)=\"updateDraftNote($any($event.target).value)\"\n />\n </label>\n </div>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(draft().note, t)\"\n (click)=\"toggleDraftNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAddDialog()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving() || loading()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n }\n\n @if (editingLog()) {\n <div class=\"edit-overlay\" (click)=\"cancelEdit()\"></div>\n }\n @if (editingLog()) {\n <aside class=\"edit-sheet\" role=\"dialog\" aria-modal=\"true\">\n <header class=\"edit-sheet-head\">\n <h3>S\u1EEDa ghi nh\u1EADn</h3>\n <button type=\"button\" class=\"btn icon ghost\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <form class=\"form\" (ngSubmit)=\"submitEdit()\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"editDraft().date\"\n (input)=\"updateEditDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().weightInput\"\n (input)=\"updateEditWeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().heightInput\"\n (input)=\"updateEditHeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n [value]=\"editDraft().note\"\n (input)=\"updateEditNote($any($event.target).value)\"\n />\n </label>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(editDraft().note, t)\"\n (click)=\"toggleEditNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"edit-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </form>\n </aside>\n }\n\n <!-- Timeline ph\u00E1t tri\u1EC3n -->\n @if (timelineDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeTimelineDialog()\">\n <div\n class=\"dialog dialog-wide timeline-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">\n <i class=\"pi pi-chart-line\"></i>\n D\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n\n </div>\n <h2>\uD83C\uDF31 H\u00E0nh tr\u00ECnh c\u1EE7a {{ babyName() || 'b\u00E9' }}</h2>\n @if (ageInDays() !== null) {\n <p class=\"dialog-lead\">\n {{ ageInDays() }} ng\u00E0y tu\u1ED5i \u00B7\n <span class=\"tl-head-stat\"\n >{{ timelineStats().past }}/{{ timelineStats().total }} c\u1ED9t m\u1ED1c \u0111\u00E3 qua</span\n >\n </p>\n }\n </div>\n <button\n type=\"button\"\n class=\"dialog-close\"\n (click)=\"closeTimelineDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <div class=\"tl-tabs\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'list'\"\n (click)=\"setTimelineView('list')\"\n >\n <i class=\"pi pi-list\"></i>\n <span>Danh s\u00E1ch</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'chart'\"\n (click)=\"setTimelineView('chart')\"\n >\n <i class=\"pi pi-chart-line\"></i>\n <span>Bi\u1EC3u \u0111\u1ED3 t\u00E2m tr\u1EA1ng</span>\n </button>\n </div>\n @if (timelineView() === 'chart') {\n <div class=\"tl-chart-view\">\n <div class=\"tl-chart-mood-legend\">\n <span class=\"tl-mood-badge\" data-mood=\"happy\">\n <i class=\"pi pi-heart-fill\"></i> Vui\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"milestone\">\n <i class=\"pi pi-star-fill\"></i> C\u1ED9t m\u1ED1c\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"calm\">\n <i class=\"pi pi-circle-fill\"></i> B\u00ECnh y\u00EAn\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"growth\">\n <i class=\"pi pi-arrow-up\"></i> T\u0103ng tr\u01B0\u1EDFng\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"leap\">\n <i class=\"pi pi-bolt\"></i> Leap/WW\n </span>\n </div>\n @if (timelineChartData(); as cd) {\n <div class=\"tl-chart-scroll\">\n <svg\n [attr.viewBox]=\"'0 0 ' + cd.W + ' ' + cd.H\"\n [attr.width]=\"cd.W\"\n [attr.height]=\"cd.H\"\n class=\"tl-chart-svg\"\n preserveAspectRatio=\"xMinYMid meet\"\n >\n <defs>\n <linearGradient id=\"tlMoodGradientW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.20\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" stop-opacity=\"0.14\" />\n <stop offset=\"100%\" stop-color=\"#ef4444\" stop-opacity=\"0.08\" />\n </linearGradient>\n <linearGradient id=\"tlMoodLineW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#16a34a\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" />\n <stop offset=\"100%\" stop-color=\"#dc2626\" />\n </linearGradient>\n </defs>\n <g class=\"tl-chart-guides\">\n @for (g of cd.moodGuides; track g) {\n <line\n [attr.x1]=\"cd.PAD_L\"\n [attr.x2]=\"cd.W - cd.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n stroke=\"#e5e7eb\"\n stroke-dasharray=\"2 4\"\n stroke-width=\"1\"\n />\n }\n @for (g of cd.moodGuides; track g) {\n <text\n [attr.x]=\"cd.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n [attr.data-mood]=\"g.mood\"\n text-anchor=\"end\"\n class=\"tl-chart-guide-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path [attr.d]=\"cd.areaPath\" fill=\"url(#tlMoodGradientW)\" />\n <path\n [attr.d]=\"cd.linePath\"\n fill=\"none\"\n stroke=\"url(#tlMoodLineW)\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <g class=\"tl-chart-x-labels\">\n @for (p of cd.points; track p) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"cd.PAD_T + cd.PLOT_H + 38\"\n text-anchor=\"middle\"\n class=\"tl-chart-x-label\"\n [class.past]=\"p.entry.state === 'past'\"\n [class.current]=\"p.entry.state === 'current'\"\n >\n {{ p.entry.milestone.periodLabel }}\n </text>\n }\n </g>\n <g class=\"tl-chart-nodes\">\n @for (p of cd.points; track p) {\n <g\n class=\"tl-chart-node\"\n [attr.data-state]=\"p.entry.state\"\n [attr.data-mood]=\"p.entry.milestone.mood\"\n [attr.data-accent]=\"p.entry.milestone.accent\"\n [attr.data-current]=\"p.entry.state === 'current' ? 'true' : null\"\n [attr.data-selected]=\"\n selectedTimelineId() === p.entry.milestone.id ? 'true' : null\n \"\n [attr.transform]=\"'translate(' + p.x + ',' + p.y + ')'\"\n (click)=\"selectTimelineNode(p.entry.milestone.id)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"\n p.entry.milestone.periodLabel + ' - ' + p.entry.milestone.tagline\n \"\n >\n @if (p.entry.state === 'current') {\n <circle\n class=\"tl-chart-pulse\"\n r=\"22\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n />\n }\n @if (selectedTimelineId() === p.entry.milestone.id) {\n <circle\n class=\"tl-chart-halo\"\n r=\"20\"\n fill=\"currentColor\"\n fill-opacity=\"0.18\"\n />\n }\n <circle class=\"tl-chart-dot\" r=\"15\" />\n <text\n class=\"tl-chart-emoji\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n y=\"1\"\n >\n {{ p.entry.milestone.heroEmoji }}\n </text>\n @if (p.entry.state === 'past') {\n <g transform=\"translate(11,-11)\">\n <circle r=\"6.5\" fill=\"#10b981\" />\n <text\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n fill=\"#ffffff\"\n font-size=\"8\"\n font-weight=\"700\"\n y=\"0.5\"\n >\n \u2713\n </text>\n </g>\n }\n </g>\n }\n </g>\n </svg>\n </div>\n }\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-chart-detail\">\n <article\n class=\"tl-item tl-item--detail\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n @if (timelineView() === 'list') {\n <div class=\"tl-nav-view\">\n <div class=\"tl-nav-bar\">\n <button\n type=\"button\"\n class=\"tl-nav-btn\"\n (click)=\"prevTimeline()\"\n [disabled]=\"selectedTimelineIndex() === 0\"\n aria-label=\"C\u1ED9t m\u1ED1c tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n @if (selectedTimelineIndex() > 0) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() - 1].milestone.periodLabel }}\n </span>\n }\n </button>\n <div class=\"tl-nav-position\">\n @if (currentTimelineMilestone()) {\n <button\n type=\"button\"\n class=\"tl-nav-today\"\n (click)=\"goToCurrentTimeline()\"\n [class.active]=\"\n selectedTimelineEntry() && selectedTimelineEntry()!.state === 'current'\n \"\n title=\"V\u1EC1 c\u1ED9t m\u1ED1c hi\u1EC7n t\u1EA1i\"\n >\n <i class=\"pi pi-compass\"></i>\n Hi\u1EC7n t\u1EA1i\n </button>\n }\n <span class=\"tl-nav-count\">\n {{ selectedTimelineIndex() + 1 }}/{{ timelineEntries().length }}\n </span>\n </div>\n <button\n type=\"button\"\n class=\"tl-nav-btn tl-nav-btn--right\"\n (click)=\"nextTimeline()\"\n [disabled]=\"selectedTimelineIndex() >= timelineEntries().length - 1\"\n aria-label=\"C\u1ED9t m\u1ED1c ti\u1EBFp theo\"\n >\n @if (selectedTimelineIndex() < timelineEntries().length - 1) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() + 1].milestone.periodLabel }}\n </span>\n }\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n <div class=\"tl-progress-rail\">\n @for (e of timelineEntries(); track e; let i = $index) {\n <button\n type=\"button\"\n class=\"tl-rail-dot\"\n [attr.data-state]=\"e.state\"\n [attr.data-selected]=\"selectedTimelineIndex() === i ? 'true' : null\"\n (click)=\"selectTimelineNode(e.milestone.id)\"\n [attr.aria-label]=\"e.milestone.periodLabel\"\n [title]=\"e.milestone.periodLabel + ' \u00B7 ' + e.milestone.tagline\"\n ></button>\n }\n </div>\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-nav-body\">\n <article\n class=\"tl-item tl-item--nav\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n <div class=\"tl-sources\">\n <i class=\"pi pi-info-circle\"></i>\n T\u1ED5ng h\u1EE3p t\u1EEB: AAP HealthyChildren, CDC Milestones, WHO, The Wonder Weeks (Plooij), Vinmec.\n </div>\n </div>\n </div>\n }\n\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--border-strong: #d1d5db;--primary: #5b6cff;--primary-2: #4c5cf0;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--rose: #ef4444;--rose-soft: #fee2e2}.weight-page{max-width:920px;margin:0 auto;padding:12px 16px calc(132px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:4px}.weight-page>.card,.weight-page>.weight-secondary-suite{padding:14px;margin-bottom:11px;border-radius:14px}.weight-page>.timeline-cta{margin-bottom:11px;padding:10px 12px;gap:10px;border-radius:12px}.weight-page>.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.weight-page>.timeline-cta .tcta-now{font-size:12.5px}.weight-alerts-block{padding-top:10px;margin-bottom:2px}.weight-inline-hint{display:flex;align-items:flex-start;gap:8px;margin:0 0 8px;padding:0;font-size:12px;line-height:1.45;color:var(--muted)}.weight-inline-hint i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-ref-cta{display:inline-flex;align-items:center;gap:8px;margin:0 0 10px;padding:8px 14px;border-radius:10px;border:1px solid var(--border);background:#fff;color:var(--text);font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;transition:border-color .15s,background .15s,color .15s}.who-ref-cta i{color:var(--primary)}.who-ref-cta:hover{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.card{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:20px;margin-bottom:16px}.card-header{margin-bottom:16px}.card-header h2{margin:0 0 6px;font-size:17px;font-weight:700;display:flex;align-items:center;gap:8px;color:var(--text)}.card-header h2 i{color:var(--primary)}.card-header p{margin:0;font-size:13px;color:var(--muted);line-height:1.45}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:10px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.form.weight-form{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:end}@media(max-width:640px){.form.weight-form{grid-template-columns:1fr}}.form.weight-form .field--grow{grid-column:1/-1}@media(min-width:641px){.form.weight-form .field--grow{grid-column:span 2}}.form.weight-form .btn-primary{grid-column:1/-1;justify-self:start}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit;background:#fff;color:var(--text)}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:10px 16px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer;transition:background .15s,transform .12s}.btn:disabled{opacity:.55;cursor:not-allowed}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost:hover:not(:disabled){color:var(--primary);border-color:var(--primary)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon{padding:8px}.btn:active:not(:disabled){transform:scale(.98)}.history-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.history-head h3{margin:0;font-size:15px;font-weight:700}.history-head .muted{font-size:12px;color:var(--muted)}.empty-mini{font-size:13px;color:var(--muted);padding:12px 0}.weight-list{list-style:none;margin:0;padding:0}.weight-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:12px 0;border-bottom:1px solid var(--border)}.weight-row:last-child{border-bottom:none}.weight-row-main{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 12px;min-width:0}.w-date{font-weight:600;font-size:14px;color:var(--text)}.w-kg{font-size:15px;font-weight:700;color:var(--primary)}.w-height{font-size:14px;font-weight:700;color:#0ea5e9}.w-note{font-size:13px;color:var(--muted);flex-basis:100%}.weight-row-actions{display:flex;gap:4px;flex-shrink:0}.weight-page .weight-history .history-head{margin-bottom:8px}.weight-page .weight-history .history-head h3{font-size:14px}.weight-page .weight-history .weight-row{padding:9px 0}.weight-page .weight-history .empty-mini{padding:8px 0;font-size:12.5px}.edit-overlay{position:fixed;inset:0;background:#0f172a59;z-index:120}.edit-sheet{position:fixed;left:0;right:0;bottom:0;z-index:121;max-height:85vh;overflow:auto;background:var(--surface);border-radius:18px 18px 0 0;padding:16px 20px calc(20px + env(safe-area-inset-bottom,0px));box-shadow:0 -12px 40px #0f172a1f;animation:sheetUp .22s ease-out}@media(min-width:640px){.edit-sheet{inset:50% auto auto 50%;transform:translate(-50%,-50%);max-width:400px;border-radius:16px;max-height:90vh;animation:none}}@keyframes sheetUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.edit-sheet-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.edit-sheet-head h3{margin:0;font-size:16px;font-weight:700}.edit-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px}.edit-sheet .field{margin-bottom:12px}.weight-eval[data-status=ok]{border-color:#a7f3d0}.weight-eval[data-status=mid]{border-color:#fde68a}.weight-eval[data-status=warn]{border-color:#fecaca}.weight-eval__head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px}.weight-eval__head h3{margin:2px 0 0;font-size:16px;font-weight:700}.weight-eval__head .muted{font-size:11.5px;color:var(--muted);margin:3px 0 0}.eyebrow{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--primary);display:flex;align-items:center;gap:6px}.eyebrow i{font-size:11px}.weight-eval__badge{flex-shrink:0;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:700;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.weight-eval__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.weight-eval__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.weight-eval__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.weight-eval__detail{margin:0;font-size:13px;line-height:1.55;color:var(--text);text-align:justify;text-justify:inter-word;-webkit-hyphens:manual;hyphens:manual}.weight-chart-card .history-head{margin-bottom:10px}.weight-chart-card .history-head h3{font-size:14px}.weight-chart-card.weight-chart-card--compare .history-head--chart h3{margin-bottom:6px}.weight-chart-card.weight-chart-card--compare .chart-sub{margin:0 0 8px;font-size:13px;line-height:1.45;color:var(--muted)}.weight-chart-card.weight-chart-card--compare .chart-meta{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;background:var(--surface-2, rgba(0, 0, 0, .04));color:var(--text)}.weight-chart-card.weight-chart-card--compare .chart-meta__pill i{font-size:12px;opacity:.85}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 220px}.weight-chart-card.weight-chart-card--compare .compare-legend{display:flex;flex-wrap:wrap;gap:8px 14px;margin-bottom:10px;font-size:11.5px;color:var(--text)}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-item{display:inline-flex;align-items:center;gap:8px}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--actual{background:#5b6cff;box-shadow:0 0 0 2px #fff}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--height{background:#0ea5e9}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line{width:20px;height:3px;border-radius:2px;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line--median{background:#16a34a}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-band{width:20px;height:11px;border-radius:2px;flex-shrink:0;background:linear-gradient(180deg,#22c55e38,#22c55e0f);border:1px solid rgba(34,197,94,.35)}.weight-chart-card.weight-chart-card--compare .chart-meta--tight{margin-top:6px;gap:6px}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 180px;min-width:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth strong{font-weight:700;word-break:break-word}.chart-meta--tight{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-secondary-suite .weight-secondary-suite__head{margin-bottom:8px}.weight-secondary-suite .weight-secondary-suite__head h3{margin:0;font-size:14px;font-weight:800;display:flex;align-items:center;gap:6px;color:var(--text)}.weight-secondary-suite .weight-secondary-suite__head h3 i{color:var(--primary)}.weight-insights-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:720px){.weight-insights-grid{grid-template-columns:1fr}}.weight-subchart--full{grid-column:1/-1}.weight-subchart{min-width:0}.weight-subchart h4{margin:0 0 5px;font-size:12px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:5px}.weight-subchart h4 i{color:var(--primary);font-size:12px}.weight-subchart-svg .w-zero-line{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 3}.weight-subchart-svg .w-path--velocity{fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}.weight-subchart-svg .w-dot--velocity{fill:#fff;stroke:#14b8a6;stroke-width:2}.chart-scroll-x{overflow-x:auto;margin:0 -6px;padding:4px 6px}.chart-scroll-x::-webkit-scrollbar{height:6px}.chart-scroll-x::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:4px}.weight-svg-chart{display:block;min-width:100%;height:auto}.weight-svg-chart .grid line{stroke:#eef2f7;stroke-width:1}.weight-svg-chart .grid-label{font-size:11px;letter-spacing:.02em;fill:var(--muted)}.weight-svg-chart .w-area{stroke:none}.weight-svg-chart .w-path--actual{fill:none;stroke-width:2.75;stroke-linecap:round;stroke-linejoin:round}.weight-svg-chart .w-dot{fill:#5b6cff;stroke:#fff;stroke-width:2}.weight-svg-chart .w-dot--height{fill:#0ea5e9}.weight-svg-chart .w-dot-label{font-size:10px;font-weight:700;fill:var(--text)}.weight-svg-chart .x-label{font-size:9px;fill:var(--muted);letter-spacing:.04em}.weight-svg-chart .axis-x-baseline{stroke:var(--border-strong);stroke-width:1.25}.weight-svg-chart .x-label--rot{font-size:10.5px;font-weight:600;fill:#475569}.weight-svg-chart .x-label--tick{font-size:10px;font-weight:600}.weight-svg-chart.weight-svg-chart--compare .w-band-fill{stroke:none}.weight-svg-chart.weight-svg-chart--compare .w-path--ref-edge{fill:none;stroke:#94a3b8;stroke-width:1.15;stroke-dasharray:5 4;opacity:.9}.weight-svg-chart.weight-svg-chart--compare .w-path--median{fill:none;stroke:#16a34a;stroke-width:2.1;opacity:.95}.weight-svg-chart.weight-svg-chart--compare .w-path--height{stroke-width:2.8}.weight-svg-chart.weight-svg-chart--compare .x-label--age{font-size:10px;font-weight:600;fill:var(--text);letter-spacing:.01em}.weight-svg-chart.weight-svg-chart--compare .x-label--sub{font-size:8px;font-weight:400;fill:var(--muted-2);letter-spacing:.02em}.weight-svg-chart.weight-svg-chart--compare .x-label--small{font-size:9px;opacity:.92}.weight-svg-chart.weight-svg-chart--compare .x-tick-line{stroke:var(--border-strong);stroke-width:1}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:20px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 8px 24px #5b6cff73;transition:transform .15s,box-shadow .15s,background .15s}.fab i{font-size:18px;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:#ffffff2e}.fab .fab-label{white-space:nowrap}.fab:hover{background:var(--primary-2);transform:translateY(-2px);box-shadow:0 12px 28px #5b6cff8c}.fab:active{transform:translateY(0)}@media(max-width:480px){.fab{padding:14px}.fab .fab-label{display:none}.fab i{font-size:22px;width:28px;height:28px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px;animation:wovelayIn .2s ease-out}@media(max-width:640px){.dialog-overlay{padding:calc(env(safe-area-inset-top,0px) + 16px) 12px calc(env(safe-area-inset-bottom,0px) + 16px);align-items:flex-start}}@keyframes wovelayIn{0%{opacity:0}to{opacity:1}}.dialog{background:var(--surface);border-radius:20px;width:100%;max-width:560px;max-height:min(100vh - 40px,100dvh - 40px);overflow-y:auto;padding:24px;animation:wdialogIn .25s cubic-bezier(.4,0,.2,1);box-shadow:0 20px 60px #0f172a40}@media(max-width:640px){.dialog{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 32px);border-radius:16px}}@keyframes wdialogIn{0%{opacity:0;transform:translateY(20px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:18px}.dialog-head h2{margin:4px 0 0;font-size:20px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{background:#f1f3f7;border:none;width:36px;height:36px;border-radius:50%;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.log-form{display:flex;flex-direction:column;gap:14px}.weight-log-grid{grid-template-columns:1fr 1fr!important}@media(max-width:560px){.weight-log-grid{grid-template-columns:1fr!important}}.log-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.log-grid .field.full{grid-column:1/-1}.note-quick-tags{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;margin-top:4px;margin-bottom:4px}.note-quick-label{font-size:12px;font-weight:600;color:var(--muted);width:100%;margin-bottom:2px}@media(min-width:400px){.note-quick-label{width:auto;margin-bottom:0}}.note-chip{appearance:none;border:1px solid var(--border);background:#f8fafc;color:var(--text);font-size:12px;font-weight:600;font-family:inherit;padding:6px 12px;border-radius:999px;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.note-chip:hover{border-color:var(--primary);color:var(--primary);background:var(--primary-soft)}.note-chip.active{background:var(--primary);border-color:var(--primary);color:#fff}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:8px}.dialog-wide{max-width:720px;display:flex;flex-direction:column;padding:0;overflow:hidden}@media(max-width:640px){.dialog-wide{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 72px);border-radius:14px}}.dialog-wide .dialog-head{position:sticky;top:0;z-index:5;background:var(--surface);padding:16px 20px;margin:0;border-bottom:1px solid var(--border);-webkit-backdrop-filter:saturate(1.2) blur(6px);backdrop-filter:saturate(1.2) blur(6px)}@media(max-width:640px){.dialog-wide .dialog-head{padding:12px 14px}}.dialog-wide .dialog-close{width:40px;height:40px;font-size:16px;background:#f1f3f7;color:var(--text)}.dialog-wide .dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.who-table-btn{display:inline-flex;align-items:center;gap:6px;margin-top:12px;padding:8px 14px;font-size:12px;font-weight:600;color:var(--primary);background:var(--primary-soft);border:1px solid transparent;border-radius:8px;cursor:pointer;transition:all .15s ease}.who-table-btn i{font-size:14px}.who-table-btn:hover{background:var(--primary);color:#fff}.who-table-dialog .dialog-head h2{display:flex;align-items:center;gap:8px;font-size:17px;font-weight:700;margin:0;color:var(--text)}.who-table-legend{display:flex;flex-wrap:wrap;align-items:center;gap:12px 20px;padding:12px 20px;background:#f8fafc;border-bottom:1px solid var(--border);font-size:12px}@media(max-width:640px){.who-table-legend{padding:10px 14px;gap:8px 14px}}.who-legend-item{display:inline-flex;align-items:center;gap:5px;font-weight:500;color:var(--muted)}.who-legend-item i{font-size:11px}.who-legend-item--min,.who-legend-item--min i{color:#f59e0b}.who-legend-item--median,.who-legend-item--median i{color:#22c55e}.who-legend-item--max,.who-legend-item--max i{color:#3b82f6}.who-table-scroll{flex:1;overflow-y:auto;padding:0;max-height:420px}@media(max-width:640px){.who-table-scroll{max-height:320px}}.who-table{width:100%;border-collapse:collapse;font-size:13px}.who-table thead{position:sticky;top:0;z-index:2;background:var(--surface)}.who-table th{padding:10px 12px;text-align:center;font-weight:600;color:var(--text);border-bottom:2px solid var(--border);background:var(--surface)}.who-table th.who-th-age{text-align:left;padding-left:20px}.who-table th.who-th-median{background:#f0fdf4;color:#166534}.who-table th.who-th-merged{background:#f8fafc;color:var(--text);min-width:120px}.who-table th.who-th-current{background:#eef2ff;color:var(--primary)}.who-table td{padding:10px 12px;text-align:center;border-bottom:1px solid #f1f5f9;vertical-align:middle}.who-table tbody tr{transition:background .15s ease}.who-table tbody tr:hover{background:#f8fafc}.who-td-age{text-align:left!important;padding-left:20px!important}.who-age-label{display:block;font-weight:600;color:var(--text);font-size:13px}.who-th-title{display:block}.who-th-unit{display:block;margin-top:2px;font-size:10.5px;font-weight:600;color:var(--muted);line-height:1.15}.who-age-weeks{display:block;font-size:11px;color:var(--muted-2);margin-top:1px}.who-td-value{font-variant-numeric:tabular-nums;font-weight:500}.who-td-merged{text-align:left!important;min-width:120px}.who-range-line{display:flex;align-items:baseline;justify-content:center;gap:8px;line-height:1.3;border-radius:8px;padding:3px 8px}.who-range-line strong{font-weight:700;color:var(--text)}.who-range-line+.who-range-line{margin-top:2px}.who-range-line--min{background:#f59e0b24}.who-range-line--median{background:#22c55e24}.who-range-line--median strong{color:#16a34a}.who-range-line--max{background:#3b82f624}.who-td-min{color:#f59e0b}.who-td-median{color:#16a34a;font-weight:600;background:#f0fdf4}.who-td-max{color:#3b82f6}.who-td-current{background:#eef2ff}.who-td-current strong{color:var(--primary);font-weight:700;font-size:14px}.who-td-current .who-current-unit{color:var(--muted);font-size:11px;margin-left:2px}.who-td-current--stack{min-width:92px}.who-current-stack{display:flex;flex-direction:column;gap:2px}.who-current-row{display:flex;align-items:baseline;justify-content:center;gap:2px;border-radius:999px;border:1px solid transparent;padding:2px 8px}.who-current-row[data-level=ok]{background:#22c55e2e;border-color:#16a34a61}.who-current-row[data-level=ok] strong{color:#166534}.who-current-row[data-level=mid]{background:#f59e0b33;border-color:#b4530959}.who-current-row[data-level=mid] strong{color:#9a3412}.who-current-row[data-level=warn]{background:#ef44442e;border-color:#dc262659}.who-current-row[data-level=warn] strong{color:#b91c1c}.who-row-current{background:linear-gradient(90deg,#fef3c7,#fffbeb)!important}.who-row-current td:first-child{border-left:4px solid #f59e0b;padding-left:16px!important}.who-row-current .who-age-label{color:#92400e}.who-row-current .who-td-median,.who-row-current .who-td-current{background:#fef3c7}.who-row-current .who-td-current strong{color:#92400e}.who-row-current:hover{background:linear-gradient(90deg,#fde68a,#fef3c7)!important}.who-table-footer{padding:14px 20px;background:#f8fafc;border-top:1px solid var(--border)}@media(max-width:640px){.who-table-footer{padding:12px 14px}}.who-footer-note{display:flex;align-items:flex-start;gap:8px;margin:0;font-size:11px;color:var(--muted);line-height:1.5}.who-footer-note i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-footer-note--muted{margin-top:6px;font-size:10.5px;opacity:.9}.weight-screen-tabs{display:flex;gap:0;margin:0 0 10px;border-bottom:1px solid var(--border);background:var(--surface);border-radius:10px 10px 0 0;overflow:hidden}.weight-screen-tabs__btn{flex:1;display:inline-flex;flex-direction:row;align-items:center;justify-content:center;gap:4px;padding:8px 4px 7px;border:none;border-bottom:2px solid transparent;background:transparent;color:var(--muted);font-size:10px;font-weight:700;font-family:inherit;cursor:pointer;white-space:nowrap}.weight-screen-tabs__btn i{font-size:11px}.weight-screen-tabs__btn.active{color:var(--primary);border-bottom-color:var(--primary)}.weight-panel{display:flex;flex-direction:column;gap:10px}.weight-snapshot{border:1px solid var(--border);background:var(--surface);border-radius:0 0 10px 10px;overflow:hidden}.weight-snapshot__head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-bottom:1px solid var(--border)}.weight-snapshot__title{margin:0;font-size:11px;font-weight:700;color:var(--text)}.weight-snapshot__growth{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--bg);border-bottom:1px solid var(--border)}.weight-snapshot__growth-label{font-size:10px;font-weight:600;color:var(--muted);white-space:nowrap}.weight-snapshot__grid{display:grid;grid-template-columns:1fr 1fr 1fr}.weight-metric{padding:8px 6px;min-width:0}.weight-metric:not(:last-child){border-right:1px solid var(--border)}.weight-metric__label{display:flex;align-items:center;gap:4px;font-size:9px;font-weight:700;color:var(--muted);white-space:nowrap}.weight-metric__label i{font-size:10px}.weight-metric__value{margin-top:5px;font-size:18px;font-weight:800;line-height:1;color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap}.weight-metric__unit{font-size:10px;font-weight:600;color:var(--muted);margin-left:2px}.weight-metric__delta{margin-top:5px;font-size:9px;font-weight:700;color:var(--muted-2);white-space:nowrap;font-variant-numeric:tabular-nums}.weight-metric__delta--up{color:#047857}.weight-metric__delta--down{color:#dc2626}.weight-metric__tag{margin-left:3px;font-size:8px;font-weight:700;color:var(--muted-2)}.wg-toggle{display:inline-flex;gap:2px;padding:2px;border-radius:8px;border:1px solid color-mix(in srgb,var(--primary) 30%,var(--border-strong));background:color-mix(in srgb,var(--primary-soft) 65%,var(--bg))}.wg-toggle--growth{border-color:color-mix(in srgb,var(--primary) 38%,var(--border-strong));background:color-mix(in srgb,var(--primary-soft) 80%,#fff)}.wg-toggle__btn{border:none;border-radius:6px;padding:4px 9px;font-size:10px;font-weight:700;font-family:inherit;cursor:pointer;background:transparent;color:#64748b;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.wg-toggle__btn:hover:not(.active){color:var(--text);background:#ffffffb8}.wg-toggle__btn.active{background:var(--primary-2);color:#fff;box-shadow:0 1px 4px #4c5cf066}.wg-toggle__btn--icon{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;font-size:9px;white-space:nowrap}.wg-toggle__btn--icon i{font-size:10px}.wg-toggle__btn--icon.active i{color:#fff}.who-ref-panel{padding:0 0 8px}.who-ref-panel__head{margin-bottom:8px}.who-ref-panel__head h2{margin:0;font-size:15px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:6px}.who-ref-panel__head p{margin:2px 0 0;font-size:11px;color:var(--muted)}.who-table-scroll--inline{max-height:360px;border-radius:10px;border:1px solid var(--border)}.who-table--inline{table-layout:fixed}.who-table--inline .who-th-age{width:68px}.who-table--inline .who-th-icon{width:46px;text-align:center;font-size:10px;font-weight:700;color:var(--muted)}.who-table--inline .who-th-icon i{display:block;font-size:12px;color:var(--primary);margin-bottom:2px}.who-table--inline .who-th-current{width:76px}.who-legend-item{display:inline-flex;align-items:flex-start;gap:6px}.who-legend-item small{display:block;font-size:9px;font-weight:600;color:var(--muted);margin-top:1px}.who-footer-note--inline{margin:8px 0 0;font-size:10px}\n", "@charset \"UTF-8\";.timeline-cta{appearance:none;display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;background:linear-gradient(135deg,#fff,#fbfaff);border:1px solid var(--border);border-radius:12px;cursor:pointer;text-align:left;color:var(--text);transition:border-color .18s ease,box-shadow .18s ease,transform .18s ease}.timeline-cta:hover{border-color:#c4b5fd;box-shadow:0 6px 18px -8px #8b5cf666;transform:translateY(-1px)}.timeline-cta:active{transform:translateY(0)}.timeline-cta .tcta-emoji{flex-shrink:0;width:42px;height:42px;display:flex;align-items:center;justify-content:center;font-size:22px;line-height:1;background:linear-gradient(135deg,#f5f3ff,#fdf2f8);border-radius:10px;border:1px solid #ede9fe}.timeline-cta .tcta-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.timeline-cta .tcta-label{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:#7c3aed;letter-spacing:.5px;text-transform:uppercase}.timeline-cta .tcta-label i{font-size:10px}.timeline-cta .tcta-now{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.timeline-cta .tcta-progress{flex-shrink:0;padding:3px 8px;background:#ede9fe;color:#6d28d9;border-radius:999px;font-size:11px;font-weight:700;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;line-height:1.4}.timeline-cta>i{flex-shrink:0;color:var(--muted-2);font-size:13px;transition:color .18s ease,transform .18s ease}.timeline-cta:hover>i{color:#7c3aed;transform:translate(2px)}@media(max-width:640px){.timeline-cta{padding:10px 12px;gap:10px}.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.timeline-cta .tcta-progress{display:none}}.timeline-dialog .dialog-head .eyebrow{display:inline-flex;align-items:center;gap:6px}.timeline-dialog .dialog-head .eyebrow i{font-size:11px}.timeline-dialog .dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.timeline-dialog .dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.tl-legend{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:10px 20px;background:#fafbfc;border-bottom:1px solid var(--border);font-size:12px;color:var(--muted)}.tl-legend>span:not(.sep):not(.tl-legend-note){display:inline-flex;align-items:center;gap:6px}.tl-legend .sep{color:var(--border-strong)}.tl-legend .tl-legend-note{color:var(--muted-2);font-style:italic;margin-left:auto}@media(max-width:640px){.tl-legend .tl-legend-note{margin-left:0;flex-basis:100%;font-size:11px}}@media(max-width:640px){.tl-legend{gap:10px;padding:10px 14px}}.tl-dot{display:inline-block;width:10px;height:10px;border-radius:50%}.tl-dot.past{background:#cbd5e1}.tl-dot.current{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf633}.tl-dot.future{background:#fff;border:1.5px solid #cbd5e1}.tl-body{padding:18px 20px 8px}@media(max-width:640px){.tl-body{padding:12px 12px 6px}}.tl-axis{display:none}.tl-item{display:grid;grid-template-columns:56px 1fr;gap:14px;position:relative;padding-bottom:20px}@media(max-width:640px){.tl-item{grid-template-columns:44px 1fr;gap:10px;padding-bottom:14px}}.tl-item:before{content:\"\";position:absolute;left:27px;top:56px;bottom:0;width:2px;background:#e5e7eb}@media(max-width:640px){.tl-item:before{left:21px;top:44px}}.tl-item:last-child:before{display:none}.tl-item[data-accent=rose]{--tl-accent: #f43f5e;--tl-accent-soft: #fff1f2;--tl-accent-dim: #fecdd3}.tl-item[data-accent=peach]{--tl-accent: #fb923c;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=coral]{--tl-accent: #f97316;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=amber]{--tl-accent: #d97706;--tl-accent-soft: #fffbeb;--tl-accent-dim: #fde68a}.tl-item[data-accent=mint]{--tl-accent: #10b981;--tl-accent-soft: #ecfdf5;--tl-accent-dim: #a7f3d0}.tl-item[data-accent=sky]{--tl-accent: #0ea5e9;--tl-accent-soft: #f0f9ff;--tl-accent-dim: #bae6fd}.tl-item[data-accent=lavender]{--tl-accent: #8b5cf6;--tl-accent-soft: #f5f3ff;--tl-accent-dim: #ddd6fe}.tl-item[data-accent=plum]{--tl-accent: #c026d3;--tl-accent-soft: #fdf4ff;--tl-accent-dim: #f5d0fe}.tl-item[data-state=past] .tl-card{background:#fafbfc;border-color:#e5e7eb}.tl-item[data-state=past] .tl-tagline,.tl-item[data-state=past] .tl-period-label{color:var(--muted)}.tl-item[data-state=past] .tl-marker{background:#f3f4f6;border-color:#d1d5db}.tl-item[data-state=past] .tl-marker .tl-hero-emoji{filter:grayscale(.35) opacity(.8)}.tl-item[data-state=current] .tl-card{background:var(--tl-accent-soft, #f5f3ff);border-color:var(--tl-accent, #8b5cf6);box-shadow:0 4px 16px -6px color-mix(in srgb,var(--tl-accent) 30%,transparent)}.tl-item[data-state=current] .tl-marker{background:var(--tl-accent);border-color:var(--tl-accent)}.tl-item[data-state=current]:before{background:linear-gradient(180deg,var(--tl-accent) 0%,#e5e7eb 100%)}.tl-item[data-state=future] .tl-card{background:#fff}.tl-marker{position:relative;width:56px;height:56px;border-radius:50%;background:#fff;border:2.5px solid var(--tl-accent, #cbd5e1);display:flex;align-items:center;justify-content:center;transition:all .2s ease;z-index:2}@media(max-width:640px){.tl-marker{width:44px;height:44px;border-width:2px}}.tl-marker .tl-hero-emoji{font-size:26px;line-height:1}@media(max-width:640px){.tl-marker .tl-hero-emoji{font-size:20px}}.tl-marker .tl-check{position:absolute;bottom:-2px;right:-2px;width:20px;height:20px;background:#10b981;color:#fff;border:2px solid #ffffff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px}@media(max-width:640px){.tl-marker .tl-check{width:16px;height:16px;font-size:8px;border-width:1.5px}}.tl-marker .tl-now-pulse{position:absolute;inset:-5px;border-radius:50%;border:2px solid var(--tl-accent);animation:tl-pulse 2.2s ease-in-out infinite;pointer-events:none}@keyframes tl-pulse{0%,to{transform:scale(1);opacity:.5}50%{transform:scale(1.25);opacity:0}}.tl-card{min-width:0;background:#fff;border:1px solid var(--border);border-radius:12px;padding:14px 14px 12px;transition:background .2s ease,border-color .2s ease,box-shadow .2s ease}@media(max-width:640px){.tl-card{padding:12px 12px 10px;border-radius:10px}}.tl-card-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;flex-wrap:wrap;margin-bottom:10px}.tl-period{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.tl-period-label{font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.6px}.tl-tagline{font-size:14.5px;font-weight:700;color:var(--text);line-height:1.3;word-break:break-word}.tl-mood-badge{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:6px;font-size:11px;font-weight:600;line-height:1.3;white-space:nowrap;flex-shrink:0;max-width:100%;overflow:hidden;text-overflow:ellipsis}.tl-mood-badge i{font-size:9px;flex-shrink:0}@media(max-width:640px){.tl-mood-badge{font-size:10.5px;padding:3px 8px}}.tl-mood-badge[data-mood=happy]{background:#dcfce7;color:#15803d}.tl-mood-badge[data-mood=milestone]{background:#d1fae5;color:#047857}.tl-mood-badge[data-mood=calm]{background:#fef9c3;color:#854d0e}.tl-mood-badge[data-mood=growth]{background:#ffedd5;color:#c2410c}.tl-mood-badge[data-mood=leap]{background:#fee2e2;color:#b91c1c}.tl-stomach{display:grid;grid-template-columns:52px 1fr;align-items:center;gap:12px;padding:10px 12px;background:#fafbfc;border:1px solid var(--border);border-radius:10px;margin-bottom:10px}.tl-stomach-visual{position:relative;width:52px;height:52px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.tl-stomach-circle{position:absolute;top:50%;left:50%;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:color-mix(in srgb,var(--tl-accent, #8b5cf6) 15%,#ffffff);border:1.5px dashed color-mix(in srgb,var(--tl-accent, #8b5cf6) 40%,transparent)}.tl-stomach-emoji{position:relative;font-size:26px;line-height:1;z-index:1;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.tl-stomach-text{min-width:0}.tl-stomach-title{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:var(--text);line-height:1.4}.tl-stomach-title i{color:var(--tl-accent, var(--muted));font-size:12px;flex-shrink:0}.tl-stomach-vol{font-size:11.5px;color:var(--muted);margin-top:2px;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-weight:600}.tl-now-banner{display:flex;align-items:center;gap:7px;padding:8px 12px;background:linear-gradient(135deg,#8b5cf6,#ec4899);color:#fff;border-radius:8px;font-size:12.5px;font-weight:600;margin-bottom:10px}.tl-now-banner i{font-size:13px}.tl-skills{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:10px}.tl-skills-label{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--muted)}.tl-skills-label i{color:var(--tl-accent);font-size:10px}.tl-skill-chip{padding:3px 9px;background:var(--tl-accent-soft, #f3f4f6);color:var(--tl-accent, var(--text));border:1px solid color-mix(in srgb,var(--tl-accent, #cbd5e1) 35%,transparent);border-radius:999px;font-size:11px;font-weight:600;line-height:1.3}.tl-section{margin-top:12px}.tl-section:first-of-type{margin-top:8px}.tl-section-label{display:flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tl-section-label i{font-size:10px}.tl-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}.tl-list li{font-size:12.5px;line-height:1.55;color:var(--text);padding-left:14px;position:relative}.tl-list li:before{content:\"\";position:absolute;left:3px;top:8px;width:5px;height:5px;border-radius:50%;background:var(--tl-accent, var(--muted-2))}.tl-section-notes{background:color-mix(in srgb,var(--tl-accent, #f59e0b) 7%,#ffffff);padding:10px 12px;border-radius:0;border-left:3px solid var(--tl-accent, #f59e0b);margin-top:12px}.tl-section-notes .tl-section-label{margin-bottom:6px}.tl-section-notes .tl-list li:before{background:var(--tl-accent, #f59e0b)}.tl-care-grid{display:grid;grid-template-columns:1fr;gap:10px;margin-top:12px}.tl-section-care{margin-top:0;padding:10px 12px;border-radius:12px;border:1px solid var(--border);background:#fff}.tl-section-care .tl-section-label{margin-bottom:7px}.tl-section-care--todo{background:color-mix(in srgb,var(--mint, #10b981) 8%,#ffffff);border-color:color-mix(in srgb,var(--mint, #10b981) 24%,var(--border))}.tl-section-care--todo .tl-section-label{color:#047857}.tl-section-care--todo .tl-list li:before{background:#047857}.tl-section-care--avoid{background:color-mix(in srgb,var(--rose, #ef4444) 7%,#ffffff);border-color:color-mix(in srgb,var(--rose, #ef4444) 24%,var(--border))}.tl-section-care--avoid .tl-section-label{color:#b91c1c}.tl-section-care--avoid .tl-list li:before{background:#ef4444}.tl-section-care--watch{background:color-mix(in srgb,var(--amber, #f59e0b) 9%,#ffffff);border-color:color-mix(in srgb,var(--amber, #f59e0b) 28%,var(--border))}.tl-section-care--watch .tl-section-label{color:#b45309}.tl-section-care--watch .tl-list li:before{background:#f59e0b}.tl-sources{display:flex;gap:6px;padding:10px 20px 14px;font-size:11px;color:var(--muted-2);line-height:1.5;border-top:1px solid var(--border);background:#fafbfc}.tl-sources i{color:var(--muted);font-size:12px;margin-top:1px;flex-shrink:0}@media(max-width:640px){.tl-sources{padding:10px 14px 12px}}.tl-tabs{display:flex;gap:4px;padding:10px 20px 0;background:#fff;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-tabs{padding:8px 12px 0}}.tl-tab{appearance:none;background:transparent;border:none;border-bottom:2px solid transparent;padding:9px 14px 10px;cursor:pointer;display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:600;color:var(--muted);transition:color .15s ease,border-color .15s ease,background .15s ease;border-radius:8px 8px 0 0}.tl-tab i{font-size:12px}.tl-tab:hover{color:var(--text);background:#fafbfc}.tl-tab.active{color:#7c3aed;border-bottom-color:#7c3aed;background:#f5f3ff}@media(max-width:640px){.tl-tab{padding:8px 10px 9px;font-size:12.5px;gap:5px}.tl-tab i{font-size:11px}}.tl-chart-view{padding:14px 20px 20px}@media(max-width:640px){.tl-chart-view{padding:10px 12px 14px}}.tl-chart-mood-legend{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;padding:8px 10px;background:#fafbfc;border:1px solid var(--border);border-radius:8px}.tl-chart-scroll{overflow-x:auto;overflow-y:hidden;background:#fff;border:1px solid var(--border);border-radius:10px;padding:4px;scrollbar-width:thin}.tl-chart-scroll::-webkit-scrollbar{height:8px}.tl-chart-scroll::-webkit-scrollbar-track{background:#f3f4f6;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb{background:#c4b5fd;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb:hover{background:#8b5cf6}.tl-chart-svg{display:block;min-width:100%}.tl-chart-guide-label{font-size:10px;fill:var(--muted-2);font-weight:700;font-family:inherit}.tl-chart-guide-label[data-mood=happy]{fill:#15803d}.tl-chart-guide-label[data-mood=calm]{fill:#a16207}.tl-chart-guide-label[data-mood=growth]{fill:#c2410c}.tl-chart-guide-label[data-mood=leap]{fill:#b91c1c}.tl-chart-x-label{font-size:10.5px;fill:var(--muted);font-weight:600;font-family:inherit}.tl-chart-x-label.current{fill:#7c3aed;font-weight:700}.tl-chart-x-label.past{fill:var(--muted-2)}.tl-chart-node{cursor:pointer;transition:transform .15s ease}.tl-chart-node[data-mood=happy]{--tl-mood: #22c55e;--tl-mood-soft: #dcfce7;color:#22c55e}.tl-chart-node[data-mood=milestone]{--tl-mood: #10b981;--tl-mood-soft: #d1fae5;color:#10b981}.tl-chart-node[data-mood=calm]{--tl-mood: #eab308;--tl-mood-soft: #fef9c3;color:#eab308}.tl-chart-node[data-mood=growth]{--tl-mood: #f97316;--tl-mood-soft: #ffedd5;color:#f97316}.tl-chart-node[data-mood=leap]{--tl-mood: #ef4444;--tl-mood-soft: #fee2e2;color:#ef4444}.tl-chart-node .tl-chart-dot{fill:#fff;stroke:var(--tl-mood, #cbd5e1);stroke-width:2.5;transition:r .15s ease,stroke-width .15s ease}.tl-chart-node .tl-chart-emoji{font-size:14px;pointer-events:none;-webkit-user-select:none;user-select:none}.tl-chart-node:hover .tl-chart-dot{r:17;stroke-width:3}.tl-chart-node[data-state=past] .tl-chart-dot{fill:#f3f4f6;stroke:#cbd5e1}.tl-chart-node[data-state=past] .tl-chart-emoji{opacity:.6}.tl-chart-node[data-state=current] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current] .tl-chart-emoji{filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))}.tl-chart-node[data-selected=true] .tl-chart-dot{r:17;stroke-width:3;fill:var(--tl-mood-soft, #f5f3ff);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current][data-selected=true] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6)}.tl-chart-pulse{transform-origin:center;transform-box:fill-box;animation:tl-chart-pulse 2s ease-in-out infinite}@keyframes tl-chart-pulse{0%,to{opacity:.5;r:20}50%{opacity:0;r:28}}.tl-chart-detail{margin-top:16px;animation:tl-detail-fade .25s ease}.tl-item--detail{padding-bottom:0!important}.tl-item--detail:before{display:none!important}@keyframes tl-detail-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.timeline-dialog{padding:0!important;overflow:hidden!important;display:flex;flex-direction:column}.timeline-dialog .dialog-head{position:sticky;top:0;z-index:20;margin:0;padding:16px 20px 12px;background:#fff;border-bottom:1px solid var(--border);box-shadow:0 2px 8px -4px #0f172a0f}@media(max-width:640px){.timeline-dialog .dialog-head{padding:14px 14px 10px}}.timeline-dialog .tl-tabs{position:sticky;top:0;z-index:19;background:#fff;padding:6px 20px 0;border-top:none}@media(max-width:640px){.timeline-dialog .tl-tabs{padding:4px 12px 0}}.timeline-dialog .tl-chart-view,.timeline-dialog .tl-nav-view{flex:1;overflow-y:auto}.timeline-dialog .tl-sources{flex-shrink:0;position:sticky;bottom:0;background:#fff;z-index:18;border-top:1px solid var(--border)}.tl-nav-view{display:flex;flex-direction:column}.tl-nav-bar{display:grid;grid-template-columns:1fr auto 1fr;gap:8px;padding:12px 16px;background:#fafbfc;border-bottom:1px solid var(--border);align-items:center}@media(max-width:640px){.tl-nav-bar{padding:10px;gap:6px}}.tl-nav-btn{appearance:none;display:inline-flex;align-items:center;gap:6px;padding:8px 12px;background:#fff;border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:12.5px;font-weight:600;cursor:pointer;transition:all .15s ease;min-width:0;justify-self:start}.tl-nav-btn i{font-size:12px;color:var(--muted);flex-shrink:0}.tl-nav-btn .tl-nav-btn-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:110px}@media(max-width:640px){.tl-nav-btn .tl-nav-btn-label{display:none}}.tl-nav-btn:hover:not(:disabled){border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff;transform:translateY(-1px)}.tl-nav-btn:hover:not(:disabled) i{color:#7c3aed}.tl-nav-btn:disabled{opacity:.4;cursor:not-allowed}.tl-nav-btn--right{justify-self:end;flex-direction:row}@media(max-width:640px){.tl-nav-btn{padding:8px 10px}}.tl-nav-position{display:inline-flex;align-items:center;gap:8px;justify-self:center}.tl-nav-today{appearance:none;display:inline-flex;align-items:center;gap:5px;padding:6px 10px;background:#fff;border:1px solid var(--border);border-radius:999px;color:var(--muted);font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.tl-nav-today i{font-size:10px}.tl-nav-today:hover{border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff}.tl-nav-today.active{background:linear-gradient(135deg,#8b5cf6,#ec4899);border-color:transparent;color:#fff}.tl-nav-today.active:hover{color:#fff}.tl-nav-count{font-size:12px;font-weight:700;color:var(--muted);font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;padding:4px 9px;background:#fff;border:1px solid var(--border);border-radius:6px;white-space:nowrap}.tl-progress-rail{display:flex;align-items:center;gap:3px;padding:10px 16px 2px;overflow-x:auto;scrollbar-width:none}.tl-progress-rail::-webkit-scrollbar{display:none}@media(max-width:640px){.tl-progress-rail{padding:8px 10px 2px}}.tl-rail-dot{appearance:none;flex:1 1 0;min-width:8px;max-width:30px;height:6px;background:#e5e7eb;border:none;border-radius:3px;padding:0;cursor:pointer;transition:background .15s ease,transform .15s ease,height .15s ease}.tl-rail-dot[data-state=past]{background:#cbd5e1}.tl-rail-dot[data-state=current]{background:linear-gradient(90deg,#8b5cf6,#ec4899)}.tl-rail-dot[data-state=future]{background:#e5e7eb}.tl-rail-dot:hover{transform:scaleY(1.6)}.tl-rail-dot[data-selected=true]{height:10px;background:#7c3aed!important;box-shadow:0 0 0 2px #fff,0 0 0 3px #c4b5fd}.tl-nav-body{padding:16px 20px 12px}@media(max-width:640px){.tl-nav-body{padding:12px 12px 8px}}.tl-item--nav,.tl-item--detail{display:block!important;padding-bottom:0!important}.tl-item--nav:before,.tl-item--detail:before{display:none!important}.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{gap:10px}}.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:48px;height:48px;border-width:2px;flex-shrink:0}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:22px}.tl-item--nav .tl-marker .tl-check,.tl-item--detail .tl-marker .tl-check{width:18px;height:18px;font-size:9px;bottom:-2px;right:-2px;border-width:2px}@media(max-width:640px){.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:42px;height:42px}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:20px}}.tl-item--nav .tl-period,.tl-item--detail .tl-period{flex:1 1 120px;min-width:0}.tl-item--nav .tl-mood-badge,.tl-item--detail .tl-mood-badge{margin-left:auto}.tl-item--nav .tl-card,.tl-item--detail .tl-card{animation:tl-card-fade .3s ease;padding:14px}@media(max-width:640px){.tl-item--nav .tl-card,.tl-item--detail .tl-card{padding:12px}}@keyframes tl-card-fade{0%{opacity:0;transform:translate(6px)}to{opacity:1;transform:translate(0)}}.tl-chart-view{padding-top:14px}@media(max-width:640px){.tl-chart-view{padding-top:10px}}.timeline-dialog .dialog-head{align-items:center}.timeline-dialog .dialog-head h2{font-size:17px;margin:2px 0 0}.timeline-dialog .dialog-head .eyebrow{font-size:11px}.timeline-dialog .dialog-head .dialog-lead{margin-top:4px;font-size:12px}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i1.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],[formArray],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i1.NgForm, selector: "form:not([ngNoForm]):not([formGroup]):not([formArray]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: WeightHeaderComponent, selector: "app-weight-header", inputs: ["errorMsg", "successMsg", "birthDate", "currentTimelineMilestone", "timelineStats", "latestGrowthEval", "latestHeightEval", "sortedLogsDesc", "latestHeightLog", "showAssessment", "showWhoButton", "showTimeline"], outputs: ["openWhoTable", "openTimeline"] }, { kind: "component", type: WeightChartsComponent, selector: "app-weight-charts", inputs: ["weightVsStandardChart", "heightVsStandardChart", "weightTrendChart", "simpleTrendMode", "weightVelocityLineChart", "heightVelocityLineChart", "dashboardMode", "comparePeriod", "growthMetric"] }, { kind: "component", type: WeightHistoryListComponent, selector: "app-weight-history-list", inputs: ["loading", "logs", "expanded"], outputs: ["edit", "remove"] }, { kind: "pipe", type: i1$1.SlicePipe, name: "slice" }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
13755
14140
  }
13756
14141
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: WeightComponent, decorators: [{
13757
14142
  type: Component,
@@ -13761,7 +14146,7 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImpo
13761
14146
  WeightHeaderComponent,
13762
14147
  WeightChartsComponent,
13763
14148
  WeightHistoryListComponent,
13764
- ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"weight-page\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n (openWhoTable)=\"openWhoTableDialog()\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n <app-weight-charts\n [weightVsStandardChart]=\"weightVsStandardChart()\"\n [heightVsStandardChart]=\"heightVsStandardChart()\"\n [weightTrendChart]=\"weightTrendChart()\"\n [simpleTrendMode]=\"simpleTrendForCharts()\"\n [weightVelocityLineChart]=\"weightVelocityLineChart()\"\n />\n\n <app-weight-history-list\n [loading]=\"loading()\"\n [logs]=\"sortedLogsDesc()\"\n (edit)=\"openEdit($event)\"\n (remove)=\"deleteLog($event)\"\n />\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAddDialog()\"\n aria-label=\"Ghi nh\u1EADn c\u00E2n n\u1EB7ng\"\n title=\"Ghi nh\u1EADn c\u00E2n n\u1EB7ng m\u1EDBi\"\n >\n <i class=\"pi pi-plus\"></i>\n <span class=\"fab-label\">Ghi c\u00E2n</span>\n </button>\n\n <!-- Dialog th\u00EAm (gi\u1ED1ng c\u1EEF b\u00FA) -->\n @if (addDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeAddDialog()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">Nh\u1EADt k\u00FD c\u00E2n n\u1EB7ng</div>\n <h2><i class=\"pi pi-chart-line\"></i> Th\u00EAm l\u1EA7n c\u00E2n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAddDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <form class=\"log-form\" (ngSubmit)=\"submitAdd()\">\n <div class=\"log-grid weight-log-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y c\u00E2n</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"draft().date\"\n (input)=\"updateDraftDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n n\u1EB7ng (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 4,25\"\n [value]=\"draft().weightInput\"\n (input)=\"updateDraftWeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 63,5\"\n [value]=\"draft().heightInput\"\n (input)=\"updateDraftHeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"Tu\u1EF3 ch\u1ECDn \u2014 ho\u1EB7c ch\u1ECDn g\u1EE3i \u00FD b\u00EAn d\u01B0\u1EDBi\"\n [value]=\"draft().note\"\n (input)=\"updateDraftNote($any($event.target).value)\"\n />\n </label>\n </div>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(draft().note, t)\"\n (click)=\"toggleDraftNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAddDialog()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving() || loading()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n }\n\n @if (editingLog()) {\n <div class=\"edit-overlay\" (click)=\"cancelEdit()\"></div>\n }\n @if (editingLog()) {\n <aside class=\"edit-sheet\" role=\"dialog\" aria-modal=\"true\">\n <header class=\"edit-sheet-head\">\n <h3>S\u1EEDa ghi nh\u1EADn</h3>\n <button type=\"button\" class=\"btn icon ghost\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <form class=\"form\" (ngSubmit)=\"submitEdit()\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"editDraft().date\"\n (input)=\"updateEditDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().weightInput\"\n (input)=\"updateEditWeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().heightInput\"\n (input)=\"updateEditHeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n [value]=\"editDraft().note\"\n (input)=\"updateEditNote($any($event.target).value)\"\n />\n </label>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(editDraft().note, t)\"\n (click)=\"toggleEditNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"edit-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </form>\n </aside>\n }\n\n <!-- Timeline ph\u00E1t tri\u1EC3n -->\n @if (timelineDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeTimelineDialog()\">\n <div\n class=\"dialog dialog-wide timeline-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">\n <i class=\"pi pi-chart-line\"></i>\n D\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n\n </div>\n <h2>\uD83C\uDF31 H\u00E0nh tr\u00ECnh c\u1EE7a {{ babyName() || 'b\u00E9' }}</h2>\n @if (ageInDays() !== null) {\n <p class=\"dialog-lead\">\n {{ ageInDays() }} ng\u00E0y tu\u1ED5i \u00B7\n <span class=\"tl-head-stat\"\n >{{ timelineStats().past }}/{{ timelineStats().total }} c\u1ED9t m\u1ED1c \u0111\u00E3 qua</span\n >\n </p>\n }\n </div>\n <button\n type=\"button\"\n class=\"dialog-close\"\n (click)=\"closeTimelineDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <div class=\"tl-tabs\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'list'\"\n (click)=\"setTimelineView('list')\"\n >\n <i class=\"pi pi-list\"></i>\n <span>Danh s\u00E1ch</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'chart'\"\n (click)=\"setTimelineView('chart')\"\n >\n <i class=\"pi pi-chart-line\"></i>\n <span>Bi\u1EC3u \u0111\u1ED3 t\u00E2m tr\u1EA1ng</span>\n </button>\n </div>\n @if (timelineView() === 'chart') {\n <div class=\"tl-chart-view\">\n <div class=\"tl-chart-mood-legend\">\n <span class=\"tl-mood-badge\" data-mood=\"happy\">\n <i class=\"pi pi-heart-fill\"></i> Vui\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"milestone\">\n <i class=\"pi pi-star-fill\"></i> C\u1ED9t m\u1ED1c\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"calm\">\n <i class=\"pi pi-circle-fill\"></i> B\u00ECnh y\u00EAn\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"growth\">\n <i class=\"pi pi-arrow-up\"></i> T\u0103ng tr\u01B0\u1EDFng\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"leap\">\n <i class=\"pi pi-bolt\"></i> Leap/WW\n </span>\n </div>\n @if (timelineChartData(); as cd) {\n <div class=\"tl-chart-scroll\">\n <svg\n [attr.viewBox]=\"'0 0 ' + cd.W + ' ' + cd.H\"\n [attr.width]=\"cd.W\"\n [attr.height]=\"cd.H\"\n class=\"tl-chart-svg\"\n preserveAspectRatio=\"xMinYMid meet\"\n >\n <defs>\n <linearGradient id=\"tlMoodGradientW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.20\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" stop-opacity=\"0.14\" />\n <stop offset=\"100%\" stop-color=\"#ef4444\" stop-opacity=\"0.08\" />\n </linearGradient>\n <linearGradient id=\"tlMoodLineW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#16a34a\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" />\n <stop offset=\"100%\" stop-color=\"#dc2626\" />\n </linearGradient>\n </defs>\n <g class=\"tl-chart-guides\">\n @for (g of cd.moodGuides; track g) {\n <line\n [attr.x1]=\"cd.PAD_L\"\n [attr.x2]=\"cd.W - cd.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n stroke=\"#e5e7eb\"\n stroke-dasharray=\"2 4\"\n stroke-width=\"1\"\n />\n }\n @for (g of cd.moodGuides; track g) {\n <text\n [attr.x]=\"cd.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n [attr.data-mood]=\"g.mood\"\n text-anchor=\"end\"\n class=\"tl-chart-guide-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path [attr.d]=\"cd.areaPath\" fill=\"url(#tlMoodGradientW)\" />\n <path\n [attr.d]=\"cd.linePath\"\n fill=\"none\"\n stroke=\"url(#tlMoodLineW)\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <g class=\"tl-chart-x-labels\">\n @for (p of cd.points; track p) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"cd.PAD_T + cd.PLOT_H + 38\"\n text-anchor=\"middle\"\n class=\"tl-chart-x-label\"\n [class.past]=\"p.entry.state === 'past'\"\n [class.current]=\"p.entry.state === 'current'\"\n >\n {{ p.entry.milestone.periodLabel }}\n </text>\n }\n </g>\n <g class=\"tl-chart-nodes\">\n @for (p of cd.points; track p) {\n <g\n class=\"tl-chart-node\"\n [attr.data-state]=\"p.entry.state\"\n [attr.data-mood]=\"p.entry.milestone.mood\"\n [attr.data-accent]=\"p.entry.milestone.accent\"\n [attr.data-current]=\"p.entry.state === 'current' ? 'true' : null\"\n [attr.data-selected]=\"\n selectedTimelineId() === p.entry.milestone.id ? 'true' : null\n \"\n [attr.transform]=\"'translate(' + p.x + ',' + p.y + ')'\"\n (click)=\"selectTimelineNode(p.entry.milestone.id)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"\n p.entry.milestone.periodLabel + ' - ' + p.entry.milestone.tagline\n \"\n >\n @if (p.entry.state === 'current') {\n <circle\n class=\"tl-chart-pulse\"\n r=\"22\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n />\n }\n @if (selectedTimelineId() === p.entry.milestone.id) {\n <circle\n class=\"tl-chart-halo\"\n r=\"20\"\n fill=\"currentColor\"\n fill-opacity=\"0.18\"\n />\n }\n <circle class=\"tl-chart-dot\" r=\"15\" />\n <text\n class=\"tl-chart-emoji\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n y=\"1\"\n >\n {{ p.entry.milestone.heroEmoji }}\n </text>\n @if (p.entry.state === 'past') {\n <g transform=\"translate(11,-11)\">\n <circle r=\"6.5\" fill=\"#10b981\" />\n <text\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n fill=\"#ffffff\"\n font-size=\"8\"\n font-weight=\"700\"\n y=\"0.5\"\n >\n \u2713\n </text>\n </g>\n }\n </g>\n }\n </g>\n </svg>\n </div>\n }\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-chart-detail\">\n <article\n class=\"tl-item tl-item--detail\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n @if (timelineView() === 'list') {\n <div class=\"tl-nav-view\">\n <div class=\"tl-nav-bar\">\n <button\n type=\"button\"\n class=\"tl-nav-btn\"\n (click)=\"prevTimeline()\"\n [disabled]=\"selectedTimelineIndex() === 0\"\n aria-label=\"C\u1ED9t m\u1ED1c tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n @if (selectedTimelineIndex() > 0) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() - 1].milestone.periodLabel }}\n </span>\n }\n </button>\n <div class=\"tl-nav-position\">\n @if (currentTimelineMilestone()) {\n <button\n type=\"button\"\n class=\"tl-nav-today\"\n (click)=\"goToCurrentTimeline()\"\n [class.active]=\"\n selectedTimelineEntry() && selectedTimelineEntry()!.state === 'current'\n \"\n title=\"V\u1EC1 c\u1ED9t m\u1ED1c hi\u1EC7n t\u1EA1i\"\n >\n <i class=\"pi pi-compass\"></i>\n Hi\u1EC7n t\u1EA1i\n </button>\n }\n <span class=\"tl-nav-count\">\n {{ selectedTimelineIndex() + 1 }}/{{ timelineEntries().length }}\n </span>\n </div>\n <button\n type=\"button\"\n class=\"tl-nav-btn tl-nav-btn--right\"\n (click)=\"nextTimeline()\"\n [disabled]=\"selectedTimelineIndex() >= timelineEntries().length - 1\"\n aria-label=\"C\u1ED9t m\u1ED1c ti\u1EBFp theo\"\n >\n @if (selectedTimelineIndex() < timelineEntries().length - 1) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() + 1].milestone.periodLabel }}\n </span>\n }\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n <div class=\"tl-progress-rail\">\n @for (e of timelineEntries(); track e; let i = $index) {\n <button\n type=\"button\"\n class=\"tl-rail-dot\"\n [attr.data-state]=\"e.state\"\n [attr.data-selected]=\"selectedTimelineIndex() === i ? 'true' : null\"\n (click)=\"selectTimelineNode(e.milestone.id)\"\n [attr.aria-label]=\"e.milestone.periodLabel\"\n [title]=\"e.milestone.periodLabel + ' \u00B7 ' + e.milestone.tagline\"\n ></button>\n }\n </div>\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-nav-body\">\n <article\n class=\"tl-item tl-item--nav\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n <div class=\"tl-sources\">\n <i class=\"pi pi-info-circle\"></i>\n T\u1ED5ng h\u1EE3p t\u1EEB: AAP HealthyChildren, CDC Milestones, WHO, The Wonder Weeks (Plooij), Vinmec.\n </div>\n </div>\n </div>\n }\n\n <!-- WHO Standards Table Dialog -->\n @if (whoTableDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeWhoTableDialog()\">\n <div\n class=\"dialog dialog-wide who-table-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">\n <i class=\"pi pi-globe\"></i>\n WHO 0-24 th\u00E1ng\n </div>\n <h2>B\u1EA3ng c\u00E2n n\u1EB7ng & chi\u1EC1u cao theo tu\u1ED5i</h2>\n <p class=\"dialog-lead\">{{ gender() === 'girl' ? 'B\u00E9 g\u00E1i' : 'B\u00E9 trai' }} \u00B7 0-24 th\u00E1ng</p>\n </div>\n <button\n type=\"button\"\n class=\"dialog-close\"\n (click)=\"closeWhoTableDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <div class=\"who-table-legend\">\n <span class=\"who-legend-item who-legend-item--min\">\n <i class=\"pi pi-arrow-down\"></i> Min (\u22122SD)\n </span>\n <span class=\"who-legend-item who-legend-item--median\">\n <i class=\"pi pi-minus\"></i> Trung v\u1ECB (P50)\n </span>\n <span class=\"who-legend-item who-legend-item--max\">\n <i class=\"pi pi-arrow-up\"></i> Max (+2SD)\n </span>\n </div>\n <div class=\"who-table-scroll\">\n <table class=\"who-table\">\n <thead>\n <tr>\n <th class=\"who-th-age\">Tu\u1ED5i</th>\n <th class=\"who-th-value who-th-merged\">\n <span class=\"who-th-title\">C\u00E2n n\u1EB7ng</span>\n <span class=\"who-th-unit\">kg</span>\n </th>\n <th class=\"who-th-value who-th-merged\">\n <span class=\"who-th-title\">Chi\u1EC1u cao</span>\n <span class=\"who-th-unit\">cm</span>\n </th>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <th class=\"who-th-current\">B\u00E9</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of whoStandardTable(); track row) {\n <tr [class.who-row-current]=\"row.isCurrent\">\n <td class=\"who-td-age\">\n <span class=\"who-age-label\">{{ row.label }}</span>\n @if (row.weeks > 0) {\n <span class=\"who-age-weeks\">{{ row.weeks }} tu\u1EA7n</span>\n }\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatKg(row.minKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatKg(row.medianKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatKg(row.maxKg) }}</strong>\n </div>\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatCm(row.minCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatCm(row.medianCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatCm(row.maxCm) }}</strong>\n </div>\n </td>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <td class=\"who-td-current who-td-current--stack\">\n <div class=\"who-current-stack\">\n @if (row.currentWeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentWeightLevel(row)\"\n >\n <strong>{{ formatKg(row.currentWeight) }}</strong>\n <span class=\"who-current-unit\">kg</span>\n </div>\n }\n @if (row.currentHeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentHeightLevel(row)\"\n >\n <strong>{{ formatCm(row.currentHeight) }}</strong>\n <span class=\"who-current-unit\">cm</span>\n </div>\n }\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n <div class=\"who-table-footer\">\n <p class=\"who-footer-note\">\n <i class=\"pi pi-info-circle\"></i>\n M\u1ED1c <strong>-2SD</strong>: c\u00F3 th\u1EC3 g\u1EE3i \u00FD suy dinh d\u01B0\u1EE1ng, thi\u1EBFu ch\u1EA5t ho\u1EB7c th\u1EA5p c\u00F2i. M\u1ED1c\n <strong>+2SD</strong>: c\u00F3 th\u1EC3 g\u1EE3i \u00FD th\u1EEBa c\u00E2n, b\u00E9o ph\u00EC ho\u1EB7c r\u1EA5t cao.\n </p>\n <p class=\"who-footer-note who-footer-note--muted\">\n S\u1ED1 li\u1EC7u theo Chu\u1EA9n t\u0103ng tr\u01B0\u1EDFng tr\u1EBB em WHO 2024 (\u00E1p d\u1EE5ng t\u1EA1i Vi\u1EC7t Nam), ch\u1EC9 mang t\u00EDnh\n tham kh\u1EA3o v\u00E0 kh\u00F4ng thay cho t\u01B0 v\u1EA5n y t\u1EBF.\n </p>\n @if (whoReferenceRows()[0]; as r0) {\n <p class=\"who-footer-note who-footer-note--muted\">\n P50 tham kh\u1EA3o: s\u01A1 sinh ~{{ formatKg(r0.weightMedianKg) }} kg,\n {{ formatCm(r0.heightMedianCm) }} cm.\n </p>\n }\n </div>\n </div>\n </div>\n }\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--border-strong: #d1d5db;--primary: #5b6cff;--primary-2: #4c5cf0;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--rose: #ef4444;--rose-soft: #fee2e2}.weight-page{max-width:920px;margin:0 auto;padding:12px 16px calc(132px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:4px}.weight-page>.card,.weight-page>.weight-secondary-suite{padding:14px;margin-bottom:11px;border-radius:14px}.weight-page>.timeline-cta{margin-bottom:11px;padding:10px 12px;gap:10px;border-radius:12px}.weight-page>.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.weight-page>.timeline-cta .tcta-now{font-size:12.5px}.weight-alerts-block{padding-top:10px;margin-bottom:2px}.weight-inline-hint{display:flex;align-items:flex-start;gap:8px;margin:0 0 8px;padding:0;font-size:12px;line-height:1.45;color:var(--muted)}.weight-inline-hint i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-ref-cta{display:inline-flex;align-items:center;gap:8px;margin:0 0 10px;padding:8px 14px;border-radius:10px;border:1px solid var(--border);background:#fff;color:var(--text);font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;transition:border-color .15s,background .15s,color .15s}.who-ref-cta i{color:var(--primary)}.who-ref-cta:hover{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.card{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:20px;margin-bottom:16px}.card-header{margin-bottom:16px}.card-header h2{margin:0 0 6px;font-size:17px;font-weight:700;display:flex;align-items:center;gap:8px;color:var(--text)}.card-header h2 i{color:var(--primary)}.card-header p{margin:0;font-size:13px;color:var(--muted);line-height:1.45}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:10px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.form.weight-form{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:end}@media(max-width:640px){.form.weight-form{grid-template-columns:1fr}}.form.weight-form .field--grow{grid-column:1/-1}@media(min-width:641px){.form.weight-form .field--grow{grid-column:span 2}}.form.weight-form .btn-primary{grid-column:1/-1;justify-self:start}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit;background:#fff;color:var(--text)}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:10px 16px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer;transition:background .15s,transform .12s}.btn:disabled{opacity:.55;cursor:not-allowed}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost:hover:not(:disabled){color:var(--primary);border-color:var(--primary)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon{padding:8px}.btn:active:not(:disabled){transform:scale(.98)}.history-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.history-head h3{margin:0;font-size:15px;font-weight:700}.history-head .muted{font-size:12px;color:var(--muted)}.empty-mini{font-size:13px;color:var(--muted);padding:12px 0}.weight-list{list-style:none;margin:0;padding:0}.weight-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:12px 0;border-bottom:1px solid var(--border)}.weight-row:last-child{border-bottom:none}.weight-row-main{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 12px;min-width:0}.w-date{font-weight:600;font-size:14px;color:var(--text)}.w-kg{font-size:15px;font-weight:700;color:var(--primary)}.w-height{font-size:14px;font-weight:700;color:#0ea5e9}.w-note{font-size:13px;color:var(--muted);flex-basis:100%}.weight-row-actions{display:flex;gap:4px;flex-shrink:0}.weight-page .weight-history .history-head{margin-bottom:8px}.weight-page .weight-history .history-head h3{font-size:14px}.weight-page .weight-history .weight-row{padding:9px 0}.weight-page .weight-history .empty-mini{padding:8px 0;font-size:12.5px}.edit-overlay{position:fixed;inset:0;background:#0f172a59;z-index:120}.edit-sheet{position:fixed;left:0;right:0;bottom:0;z-index:121;max-height:85vh;overflow:auto;background:var(--surface);border-radius:18px 18px 0 0;padding:16px 20px calc(20px + env(safe-area-inset-bottom,0px));box-shadow:0 -12px 40px #0f172a1f;animation:sheetUp .22s ease-out}@media(min-width:640px){.edit-sheet{inset:50% auto auto 50%;transform:translate(-50%,-50%);max-width:400px;border-radius:16px;max-height:90vh;animation:none}}@keyframes sheetUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.edit-sheet-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.edit-sheet-head h3{margin:0;font-size:16px;font-weight:700}.edit-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px}.edit-sheet .field{margin-bottom:12px}.weight-eval[data-status=ok]{border-color:#a7f3d0}.weight-eval[data-status=mid]{border-color:#fde68a}.weight-eval[data-status=warn]{border-color:#fecaca}.weight-eval__head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px}.weight-eval__head h3{margin:2px 0 0;font-size:16px;font-weight:700}.weight-eval__head .muted{font-size:11.5px;color:var(--muted);margin:3px 0 0}.eyebrow{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--primary);display:flex;align-items:center;gap:6px}.eyebrow i{font-size:11px}.weight-eval__badge{flex-shrink:0;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:700;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.weight-eval__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.weight-eval__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.weight-eval__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.weight-eval__detail{margin:0;font-size:13px;line-height:1.55;color:var(--text);text-align:justify;text-justify:inter-word;-webkit-hyphens:manual;hyphens:manual}.weight-chart-card .history-head{margin-bottom:10px}.weight-chart-card .history-head h3{font-size:14px}.weight-chart-card.weight-chart-card--compare .history-head--chart h3{margin-bottom:6px}.weight-chart-card.weight-chart-card--compare .chart-sub{margin:0 0 8px;font-size:13px;line-height:1.45;color:var(--muted)}.weight-chart-card.weight-chart-card--compare .chart-meta{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;background:var(--surface-2, rgba(0, 0, 0, .04));color:var(--text)}.weight-chart-card.weight-chart-card--compare .chart-meta__pill i{font-size:12px;opacity:.85}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 220px}.weight-chart-card.weight-chart-card--compare .compare-legend{display:flex;flex-wrap:wrap;gap:8px 14px;margin-bottom:10px;font-size:11.5px;color:var(--text)}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-item{display:inline-flex;align-items:center;gap:8px}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--actual{background:#5b6cff;box-shadow:0 0 0 2px #fff}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--height{background:#0ea5e9}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line{width:20px;height:3px;border-radius:2px;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line--median{background:#16a34a}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-band{width:20px;height:11px;border-radius:2px;flex-shrink:0;background:linear-gradient(180deg,#22c55e38,#22c55e0f);border:1px solid rgba(34,197,94,.35)}.weight-chart-card.weight-chart-card--compare .chart-meta--tight{margin-top:6px;gap:6px}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 180px;min-width:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth strong{font-weight:700;word-break:break-word}.chart-meta--tight{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-secondary-suite .weight-secondary-suite__head{margin-bottom:8px}.weight-secondary-suite .weight-secondary-suite__head h3{margin:0;font-size:14px;font-weight:800;display:flex;align-items:center;gap:6px;color:var(--text)}.weight-secondary-suite .weight-secondary-suite__head h3 i{color:var(--primary)}.weight-insights-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:720px){.weight-insights-grid{grid-template-columns:1fr}}.weight-subchart--full{grid-column:1/-1}.weight-subchart{min-width:0}.weight-subchart h4{margin:0 0 5px;font-size:12px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:5px}.weight-subchart h4 i{color:var(--primary);font-size:12px}.weight-subchart-svg .w-zero-line{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 3}.weight-subchart-svg .w-path--velocity{fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}.weight-subchart-svg .w-dot--velocity{fill:#fff;stroke:#14b8a6;stroke-width:2}.chart-scroll-x{overflow-x:auto;margin:0 -6px;padding:4px 6px}.chart-scroll-x::-webkit-scrollbar{height:6px}.chart-scroll-x::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:4px}.weight-svg-chart{display:block;min-width:100%;height:auto}.weight-svg-chart .grid line{stroke:#eef2f7;stroke-width:1}.weight-svg-chart .grid-label{font-size:11px;letter-spacing:.02em;fill:var(--muted)}.weight-svg-chart .w-area{stroke:none}.weight-svg-chart .w-path--actual{fill:none;stroke-width:2.75;stroke-linecap:round;stroke-linejoin:round}.weight-svg-chart .w-dot{fill:#5b6cff;stroke:#fff;stroke-width:2}.weight-svg-chart .w-dot--height{fill:#0ea5e9}.weight-svg-chart .w-dot-label{font-size:10px;font-weight:700;fill:var(--text)}.weight-svg-chart .x-label{font-size:9px;fill:var(--muted);letter-spacing:.04em}.weight-svg-chart .axis-x-baseline{stroke:var(--border-strong);stroke-width:1.25}.weight-svg-chart .x-label--rot{font-size:10.5px;font-weight:600;fill:#475569}.weight-svg-chart .x-label--tick{font-size:10px;font-weight:600}.weight-svg-chart.weight-svg-chart--compare .w-band-fill{stroke:none}.weight-svg-chart.weight-svg-chart--compare .w-path--ref-edge{fill:none;stroke:#94a3b8;stroke-width:1.15;stroke-dasharray:5 4;opacity:.9}.weight-svg-chart.weight-svg-chart--compare .w-path--median{fill:none;stroke:#16a34a;stroke-width:2.1;opacity:.95}.weight-svg-chart.weight-svg-chart--compare .w-path--height{stroke-width:2.8}.weight-svg-chart.weight-svg-chart--compare .x-label--age{font-size:10px;font-weight:600;fill:var(--text);letter-spacing:.01em}.weight-svg-chart.weight-svg-chart--compare .x-label--sub{font-size:8px;font-weight:400;fill:var(--muted-2);letter-spacing:.02em}.weight-svg-chart.weight-svg-chart--compare .x-label--small{font-size:9px;opacity:.92}.weight-svg-chart.weight-svg-chart--compare .x-tick-line{stroke:var(--border-strong);stroke-width:1}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:20px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 8px 24px #5b6cff73;transition:transform .15s,box-shadow .15s,background .15s}.fab i{font-size:18px;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:#ffffff2e}.fab .fab-label{white-space:nowrap}.fab:hover{background:var(--primary-2);transform:translateY(-2px);box-shadow:0 12px 28px #5b6cff8c}.fab:active{transform:translateY(0)}@media(max-width:480px){.fab{padding:14px}.fab .fab-label{display:none}.fab i{font-size:22px;width:28px;height:28px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px;animation:wovelayIn .2s ease-out}@media(max-width:640px){.dialog-overlay{padding:calc(env(safe-area-inset-top,0px) + 16px) 12px calc(env(safe-area-inset-bottom,0px) + 16px);align-items:flex-start}}@keyframes wovelayIn{0%{opacity:0}to{opacity:1}}.dialog{background:var(--surface);border-radius:20px;width:100%;max-width:560px;max-height:min(100vh - 40px,100dvh - 40px);overflow-y:auto;padding:24px;animation:wdialogIn .25s cubic-bezier(.4,0,.2,1);box-shadow:0 20px 60px #0f172a40}@media(max-width:640px){.dialog{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 32px);border-radius:16px}}@keyframes wdialogIn{0%{opacity:0;transform:translateY(20px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:18px}.dialog-head h2{margin:4px 0 0;font-size:20px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{background:#f1f3f7;border:none;width:36px;height:36px;border-radius:50%;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.log-form{display:flex;flex-direction:column;gap:14px}.weight-log-grid{grid-template-columns:1fr 1fr!important}@media(max-width:560px){.weight-log-grid{grid-template-columns:1fr!important}}.log-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.log-grid .field.full{grid-column:1/-1}.note-quick-tags{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;margin-top:4px;margin-bottom:4px}.note-quick-label{font-size:12px;font-weight:600;color:var(--muted);width:100%;margin-bottom:2px}@media(min-width:400px){.note-quick-label{width:auto;margin-bottom:0}}.note-chip{appearance:none;border:1px solid var(--border);background:#f8fafc;color:var(--text);font-size:12px;font-weight:600;font-family:inherit;padding:6px 12px;border-radius:999px;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.note-chip:hover{border-color:var(--primary);color:var(--primary);background:var(--primary-soft)}.note-chip.active{background:var(--primary);border-color:var(--primary);color:#fff}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:8px}.dialog-wide{max-width:720px;display:flex;flex-direction:column;padding:0;overflow:hidden}@media(max-width:640px){.dialog-wide{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 72px);border-radius:14px}}.dialog-wide .dialog-head{position:sticky;top:0;z-index:5;background:var(--surface);padding:16px 20px;margin:0;border-bottom:1px solid var(--border);-webkit-backdrop-filter:saturate(1.2) blur(6px);backdrop-filter:saturate(1.2) blur(6px)}@media(max-width:640px){.dialog-wide .dialog-head{padding:12px 14px}}.dialog-wide .dialog-close{width:40px;height:40px;font-size:16px;background:#f1f3f7;color:var(--text)}.dialog-wide .dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.who-table-btn{display:inline-flex;align-items:center;gap:6px;margin-top:12px;padding:8px 14px;font-size:12px;font-weight:600;color:var(--primary);background:var(--primary-soft);border:1px solid transparent;border-radius:8px;cursor:pointer;transition:all .15s ease}.who-table-btn i{font-size:14px}.who-table-btn:hover{background:var(--primary);color:#fff}.who-table-dialog .dialog-head h2{display:flex;align-items:center;gap:8px;font-size:17px;font-weight:700;margin:0;color:var(--text)}.who-table-legend{display:flex;flex-wrap:wrap;align-items:center;gap:12px 20px;padding:12px 20px;background:#f8fafc;border-bottom:1px solid var(--border);font-size:12px}@media(max-width:640px){.who-table-legend{padding:10px 14px;gap:8px 14px}}.who-legend-item{display:inline-flex;align-items:center;gap:5px;font-weight:500;color:var(--muted)}.who-legend-item i{font-size:11px}.who-legend-item--min,.who-legend-item--min i{color:#f59e0b}.who-legend-item--median,.who-legend-item--median i{color:#22c55e}.who-legend-item--max,.who-legend-item--max i{color:#3b82f6}.who-table-scroll{flex:1;overflow-y:auto;padding:0;max-height:420px}@media(max-width:640px){.who-table-scroll{max-height:320px}}.who-table{width:100%;border-collapse:collapse;font-size:13px}.who-table thead{position:sticky;top:0;z-index:2;background:var(--surface)}.who-table th{padding:10px 12px;text-align:center;font-weight:600;color:var(--text);border-bottom:2px solid var(--border);background:var(--surface)}.who-table th.who-th-age{text-align:left;padding-left:20px}.who-table th.who-th-median{background:#f0fdf4;color:#166534}.who-table th.who-th-merged{background:#f8fafc;color:var(--text);min-width:120px}.who-table th.who-th-current{background:#eef2ff;color:var(--primary)}.who-table td{padding:10px 12px;text-align:center;border-bottom:1px solid #f1f5f9;vertical-align:middle}.who-table tbody tr{transition:background .15s ease}.who-table tbody tr:hover{background:#f8fafc}.who-td-age{text-align:left!important;padding-left:20px!important}.who-age-label{display:block;font-weight:600;color:var(--text);font-size:13px}.who-th-title{display:block}.who-th-unit{display:block;margin-top:2px;font-size:10.5px;font-weight:600;color:var(--muted);line-height:1.15}.who-age-weeks{display:block;font-size:11px;color:var(--muted-2);margin-top:1px}.who-td-value{font-variant-numeric:tabular-nums;font-weight:500}.who-td-merged{text-align:left!important;min-width:120px}.who-range-line{display:flex;align-items:baseline;justify-content:center;gap:8px;line-height:1.3;border-radius:8px;padding:3px 8px}.who-range-line strong{font-weight:700;color:var(--text)}.who-range-line+.who-range-line{margin-top:2px}.who-range-line--min{background:#f59e0b24}.who-range-line--median{background:#22c55e24}.who-range-line--median strong{color:#16a34a}.who-range-line--max{background:#3b82f624}.who-td-min{color:#f59e0b}.who-td-median{color:#16a34a;font-weight:600;background:#f0fdf4}.who-td-max{color:#3b82f6}.who-td-current{background:#eef2ff}.who-td-current strong{color:var(--primary);font-weight:700;font-size:14px}.who-td-current .who-current-unit{color:var(--muted);font-size:11px;margin-left:2px}.who-td-current--stack{min-width:92px}.who-current-stack{display:flex;flex-direction:column;gap:2px}.who-current-row{display:flex;align-items:baseline;justify-content:center;gap:2px;border-radius:999px;border:1px solid transparent;padding:2px 8px}.who-current-row[data-level=ok]{background:#22c55e2e;border-color:#16a34a61}.who-current-row[data-level=ok] strong{color:#166534}.who-current-row[data-level=mid]{background:#f59e0b33;border-color:#b4530959}.who-current-row[data-level=mid] strong{color:#9a3412}.who-current-row[data-level=warn]{background:#ef44442e;border-color:#dc262659}.who-current-row[data-level=warn] strong{color:#b91c1c}.who-row-current{background:linear-gradient(90deg,#fef3c7,#fffbeb)!important}.who-row-current td:first-child{border-left:4px solid #f59e0b;padding-left:16px!important}.who-row-current .who-age-label{color:#92400e}.who-row-current .who-td-median,.who-row-current .who-td-current{background:#fef3c7}.who-row-current .who-td-current strong{color:#92400e}.who-row-current:hover{background:linear-gradient(90deg,#fde68a,#fef3c7)!important}.who-table-footer{padding:14px 20px;background:#f8fafc;border-top:1px solid var(--border)}@media(max-width:640px){.who-table-footer{padding:12px 14px}}.who-footer-note{display:flex;align-items:flex-start;gap:8px;margin:0;font-size:11px;color:var(--muted);line-height:1.5}.who-footer-note i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-footer-note--muted{margin-top:6px;font-size:10.5px;opacity:.9}\n", "@charset \"UTF-8\";.timeline-cta{appearance:none;display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;background:linear-gradient(135deg,#fff,#fbfaff);border:1px solid var(--border);border-radius:12px;cursor:pointer;text-align:left;color:var(--text);transition:border-color .18s ease,box-shadow .18s ease,transform .18s ease}.timeline-cta:hover{border-color:#c4b5fd;box-shadow:0 6px 18px -8px #8b5cf666;transform:translateY(-1px)}.timeline-cta:active{transform:translateY(0)}.timeline-cta .tcta-emoji{flex-shrink:0;width:42px;height:42px;display:flex;align-items:center;justify-content:center;font-size:22px;line-height:1;background:linear-gradient(135deg,#f5f3ff,#fdf2f8);border-radius:10px;border:1px solid #ede9fe}.timeline-cta .tcta-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.timeline-cta .tcta-label{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:#7c3aed;letter-spacing:.5px;text-transform:uppercase}.timeline-cta .tcta-label i{font-size:10px}.timeline-cta .tcta-now{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.timeline-cta .tcta-progress{flex-shrink:0;padding:3px 8px;background:#ede9fe;color:#6d28d9;border-radius:999px;font-size:11px;font-weight:700;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;line-height:1.4}.timeline-cta>i{flex-shrink:0;color:var(--muted-2);font-size:13px;transition:color .18s ease,transform .18s ease}.timeline-cta:hover>i{color:#7c3aed;transform:translate(2px)}@media(max-width:640px){.timeline-cta{padding:10px 12px;gap:10px}.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.timeline-cta .tcta-progress{display:none}}.timeline-dialog .dialog-head .eyebrow{display:inline-flex;align-items:center;gap:6px}.timeline-dialog .dialog-head .eyebrow i{font-size:11px}.timeline-dialog .dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.timeline-dialog .dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.tl-legend{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:10px 20px;background:#fafbfc;border-bottom:1px solid var(--border);font-size:12px;color:var(--muted)}.tl-legend>span:not(.sep):not(.tl-legend-note){display:inline-flex;align-items:center;gap:6px}.tl-legend .sep{color:var(--border-strong)}.tl-legend .tl-legend-note{color:var(--muted-2);font-style:italic;margin-left:auto}@media(max-width:640px){.tl-legend .tl-legend-note{margin-left:0;flex-basis:100%;font-size:11px}}@media(max-width:640px){.tl-legend{gap:10px;padding:10px 14px}}.tl-dot{display:inline-block;width:10px;height:10px;border-radius:50%}.tl-dot.past{background:#cbd5e1}.tl-dot.current{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf633}.tl-dot.future{background:#fff;border:1.5px solid #cbd5e1}.tl-body{padding:18px 20px 8px}@media(max-width:640px){.tl-body{padding:12px 12px 6px}}.tl-axis{display:none}.tl-item{display:grid;grid-template-columns:56px 1fr;gap:14px;position:relative;padding-bottom:20px}@media(max-width:640px){.tl-item{grid-template-columns:44px 1fr;gap:10px;padding-bottom:14px}}.tl-item:before{content:\"\";position:absolute;left:27px;top:56px;bottom:0;width:2px;background:#e5e7eb}@media(max-width:640px){.tl-item:before{left:21px;top:44px}}.tl-item:last-child:before{display:none}.tl-item[data-accent=rose]{--tl-accent: #f43f5e;--tl-accent-soft: #fff1f2;--tl-accent-dim: #fecdd3}.tl-item[data-accent=peach]{--tl-accent: #fb923c;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=coral]{--tl-accent: #f97316;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=amber]{--tl-accent: #d97706;--tl-accent-soft: #fffbeb;--tl-accent-dim: #fde68a}.tl-item[data-accent=mint]{--tl-accent: #10b981;--tl-accent-soft: #ecfdf5;--tl-accent-dim: #a7f3d0}.tl-item[data-accent=sky]{--tl-accent: #0ea5e9;--tl-accent-soft: #f0f9ff;--tl-accent-dim: #bae6fd}.tl-item[data-accent=lavender]{--tl-accent: #8b5cf6;--tl-accent-soft: #f5f3ff;--tl-accent-dim: #ddd6fe}.tl-item[data-accent=plum]{--tl-accent: #c026d3;--tl-accent-soft: #fdf4ff;--tl-accent-dim: #f5d0fe}.tl-item[data-state=past] .tl-card{background:#fafbfc;border-color:#e5e7eb}.tl-item[data-state=past] .tl-tagline,.tl-item[data-state=past] .tl-period-label{color:var(--muted)}.tl-item[data-state=past] .tl-marker{background:#f3f4f6;border-color:#d1d5db}.tl-item[data-state=past] .tl-marker .tl-hero-emoji{filter:grayscale(.35) opacity(.8)}.tl-item[data-state=current] .tl-card{background:var(--tl-accent-soft, #f5f3ff);border-color:var(--tl-accent, #8b5cf6);box-shadow:0 4px 16px -6px color-mix(in srgb,var(--tl-accent) 30%,transparent)}.tl-item[data-state=current] .tl-marker{background:var(--tl-accent);border-color:var(--tl-accent)}.tl-item[data-state=current]:before{background:linear-gradient(180deg,var(--tl-accent) 0%,#e5e7eb 100%)}.tl-item[data-state=future] .tl-card{background:#fff}.tl-marker{position:relative;width:56px;height:56px;border-radius:50%;background:#fff;border:2.5px solid var(--tl-accent, #cbd5e1);display:flex;align-items:center;justify-content:center;transition:all .2s ease;z-index:2}@media(max-width:640px){.tl-marker{width:44px;height:44px;border-width:2px}}.tl-marker .tl-hero-emoji{font-size:26px;line-height:1}@media(max-width:640px){.tl-marker .tl-hero-emoji{font-size:20px}}.tl-marker .tl-check{position:absolute;bottom:-2px;right:-2px;width:20px;height:20px;background:#10b981;color:#fff;border:2px solid #ffffff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px}@media(max-width:640px){.tl-marker .tl-check{width:16px;height:16px;font-size:8px;border-width:1.5px}}.tl-marker .tl-now-pulse{position:absolute;inset:-5px;border-radius:50%;border:2px solid var(--tl-accent);animation:tl-pulse 2.2s ease-in-out infinite;pointer-events:none}@keyframes tl-pulse{0%,to{transform:scale(1);opacity:.5}50%{transform:scale(1.25);opacity:0}}.tl-card{min-width:0;background:#fff;border:1px solid var(--border);border-radius:12px;padding:14px 14px 12px;transition:background .2s ease,border-color .2s ease,box-shadow .2s ease}@media(max-width:640px){.tl-card{padding:12px 12px 10px;border-radius:10px}}.tl-card-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;flex-wrap:wrap;margin-bottom:10px}.tl-period{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.tl-period-label{font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.6px}.tl-tagline{font-size:14.5px;font-weight:700;color:var(--text);line-height:1.3;word-break:break-word}.tl-mood-badge{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:6px;font-size:11px;font-weight:600;line-height:1.3;white-space:nowrap;flex-shrink:0;max-width:100%;overflow:hidden;text-overflow:ellipsis}.tl-mood-badge i{font-size:9px;flex-shrink:0}@media(max-width:640px){.tl-mood-badge{font-size:10.5px;padding:3px 8px}}.tl-mood-badge[data-mood=happy]{background:#dcfce7;color:#15803d}.tl-mood-badge[data-mood=milestone]{background:#d1fae5;color:#047857}.tl-mood-badge[data-mood=calm]{background:#fef9c3;color:#854d0e}.tl-mood-badge[data-mood=growth]{background:#ffedd5;color:#c2410c}.tl-mood-badge[data-mood=leap]{background:#fee2e2;color:#b91c1c}.tl-stomach{display:grid;grid-template-columns:52px 1fr;align-items:center;gap:12px;padding:10px 12px;background:#fafbfc;border:1px solid var(--border);border-radius:10px;margin-bottom:10px}.tl-stomach-visual{position:relative;width:52px;height:52px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.tl-stomach-circle{position:absolute;top:50%;left:50%;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:color-mix(in srgb,var(--tl-accent, #8b5cf6) 15%,#ffffff);border:1.5px dashed color-mix(in srgb,var(--tl-accent, #8b5cf6) 40%,transparent)}.tl-stomach-emoji{position:relative;font-size:26px;line-height:1;z-index:1;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.tl-stomach-text{min-width:0}.tl-stomach-title{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:var(--text);line-height:1.4}.tl-stomach-title i{color:var(--tl-accent, var(--muted));font-size:12px;flex-shrink:0}.tl-stomach-vol{font-size:11.5px;color:var(--muted);margin-top:2px;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-weight:600}.tl-now-banner{display:flex;align-items:center;gap:7px;padding:8px 12px;background:linear-gradient(135deg,#8b5cf6,#ec4899);color:#fff;border-radius:8px;font-size:12.5px;font-weight:600;margin-bottom:10px}.tl-now-banner i{font-size:13px}.tl-skills{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:10px}.tl-skills-label{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--muted)}.tl-skills-label i{color:var(--tl-accent);font-size:10px}.tl-skill-chip{padding:3px 9px;background:var(--tl-accent-soft, #f3f4f6);color:var(--tl-accent, var(--text));border:1px solid color-mix(in srgb,var(--tl-accent, #cbd5e1) 35%,transparent);border-radius:999px;font-size:11px;font-weight:600;line-height:1.3}.tl-section{margin-top:12px}.tl-section:first-of-type{margin-top:8px}.tl-section-label{display:flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tl-section-label i{font-size:10px}.tl-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}.tl-list li{font-size:12.5px;line-height:1.55;color:var(--text);padding-left:14px;position:relative}.tl-list li:before{content:\"\";position:absolute;left:3px;top:8px;width:5px;height:5px;border-radius:50%;background:var(--tl-accent, var(--muted-2))}.tl-section-notes{background:color-mix(in srgb,var(--tl-accent, #f59e0b) 7%,#ffffff);padding:10px 12px;border-radius:0;border-left:3px solid var(--tl-accent, #f59e0b);margin-top:12px}.tl-section-notes .tl-section-label{margin-bottom:6px}.tl-section-notes .tl-list li:before{background:var(--tl-accent, #f59e0b)}.tl-care-grid{display:grid;grid-template-columns:1fr;gap:10px;margin-top:12px}.tl-section-care{margin-top:0;padding:10px 12px;border-radius:12px;border:1px solid var(--border);background:#fff}.tl-section-care .tl-section-label{margin-bottom:7px}.tl-section-care--todo{background:color-mix(in srgb,var(--mint, #10b981) 8%,#ffffff);border-color:color-mix(in srgb,var(--mint, #10b981) 24%,var(--border))}.tl-section-care--todo .tl-section-label{color:#047857}.tl-section-care--todo .tl-list li:before{background:#047857}.tl-section-care--avoid{background:color-mix(in srgb,var(--rose, #ef4444) 7%,#ffffff);border-color:color-mix(in srgb,var(--rose, #ef4444) 24%,var(--border))}.tl-section-care--avoid .tl-section-label{color:#b91c1c}.tl-section-care--avoid .tl-list li:before{background:#ef4444}.tl-section-care--watch{background:color-mix(in srgb,var(--amber, #f59e0b) 9%,#ffffff);border-color:color-mix(in srgb,var(--amber, #f59e0b) 28%,var(--border))}.tl-section-care--watch .tl-section-label{color:#b45309}.tl-section-care--watch .tl-list li:before{background:#f59e0b}.tl-sources{display:flex;gap:6px;padding:10px 20px 14px;font-size:11px;color:var(--muted-2);line-height:1.5;border-top:1px solid var(--border);background:#fafbfc}.tl-sources i{color:var(--muted);font-size:12px;margin-top:1px;flex-shrink:0}@media(max-width:640px){.tl-sources{padding:10px 14px 12px}}.tl-tabs{display:flex;gap:4px;padding:10px 20px 0;background:#fff;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-tabs{padding:8px 12px 0}}.tl-tab{appearance:none;background:transparent;border:none;border-bottom:2px solid transparent;padding:9px 14px 10px;cursor:pointer;display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:600;color:var(--muted);transition:color .15s ease,border-color .15s ease,background .15s ease;border-radius:8px 8px 0 0}.tl-tab i{font-size:12px}.tl-tab:hover{color:var(--text);background:#fafbfc}.tl-tab.active{color:#7c3aed;border-bottom-color:#7c3aed;background:#f5f3ff}@media(max-width:640px){.tl-tab{padding:8px 10px 9px;font-size:12.5px;gap:5px}.tl-tab i{font-size:11px}}.tl-chart-view{padding:14px 20px 20px}@media(max-width:640px){.tl-chart-view{padding:10px 12px 14px}}.tl-chart-mood-legend{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;padding:8px 10px;background:#fafbfc;border:1px solid var(--border);border-radius:8px}.tl-chart-scroll{overflow-x:auto;overflow-y:hidden;background:#fff;border:1px solid var(--border);border-radius:10px;padding:4px;scrollbar-width:thin}.tl-chart-scroll::-webkit-scrollbar{height:8px}.tl-chart-scroll::-webkit-scrollbar-track{background:#f3f4f6;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb{background:#c4b5fd;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb:hover{background:#8b5cf6}.tl-chart-svg{display:block;min-width:100%}.tl-chart-guide-label{font-size:10px;fill:var(--muted-2);font-weight:700;font-family:inherit}.tl-chart-guide-label[data-mood=happy]{fill:#15803d}.tl-chart-guide-label[data-mood=calm]{fill:#a16207}.tl-chart-guide-label[data-mood=growth]{fill:#c2410c}.tl-chart-guide-label[data-mood=leap]{fill:#b91c1c}.tl-chart-x-label{font-size:10.5px;fill:var(--muted);font-weight:600;font-family:inherit}.tl-chart-x-label.current{fill:#7c3aed;font-weight:700}.tl-chart-x-label.past{fill:var(--muted-2)}.tl-chart-node{cursor:pointer;transition:transform .15s ease}.tl-chart-node[data-mood=happy]{--tl-mood: #22c55e;--tl-mood-soft: #dcfce7;color:#22c55e}.tl-chart-node[data-mood=milestone]{--tl-mood: #10b981;--tl-mood-soft: #d1fae5;color:#10b981}.tl-chart-node[data-mood=calm]{--tl-mood: #eab308;--tl-mood-soft: #fef9c3;color:#eab308}.tl-chart-node[data-mood=growth]{--tl-mood: #f97316;--tl-mood-soft: #ffedd5;color:#f97316}.tl-chart-node[data-mood=leap]{--tl-mood: #ef4444;--tl-mood-soft: #fee2e2;color:#ef4444}.tl-chart-node .tl-chart-dot{fill:#fff;stroke:var(--tl-mood, #cbd5e1);stroke-width:2.5;transition:r .15s ease,stroke-width .15s ease}.tl-chart-node .tl-chart-emoji{font-size:14px;pointer-events:none;-webkit-user-select:none;user-select:none}.tl-chart-node:hover .tl-chart-dot{r:17;stroke-width:3}.tl-chart-node[data-state=past] .tl-chart-dot{fill:#f3f4f6;stroke:#cbd5e1}.tl-chart-node[data-state=past] .tl-chart-emoji{opacity:.6}.tl-chart-node[data-state=current] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current] .tl-chart-emoji{filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))}.tl-chart-node[data-selected=true] .tl-chart-dot{r:17;stroke-width:3;fill:var(--tl-mood-soft, #f5f3ff);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current][data-selected=true] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6)}.tl-chart-pulse{transform-origin:center;transform-box:fill-box;animation:tl-chart-pulse 2s ease-in-out infinite}@keyframes tl-chart-pulse{0%,to{opacity:.5;r:20}50%{opacity:0;r:28}}.tl-chart-detail{margin-top:16px;animation:tl-detail-fade .25s ease}.tl-item--detail{padding-bottom:0!important}.tl-item--detail:before{display:none!important}@keyframes tl-detail-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.timeline-dialog{padding:0!important;overflow:hidden!important;display:flex;flex-direction:column}.timeline-dialog .dialog-head{position:sticky;top:0;z-index:20;margin:0;padding:16px 20px 12px;background:#fff;border-bottom:1px solid var(--border);box-shadow:0 2px 8px -4px #0f172a0f}@media(max-width:640px){.timeline-dialog .dialog-head{padding:14px 14px 10px}}.timeline-dialog .tl-tabs{position:sticky;top:0;z-index:19;background:#fff;padding:6px 20px 0;border-top:none}@media(max-width:640px){.timeline-dialog .tl-tabs{padding:4px 12px 0}}.timeline-dialog .tl-chart-view,.timeline-dialog .tl-nav-view{flex:1;overflow-y:auto}.timeline-dialog .tl-sources{flex-shrink:0;position:sticky;bottom:0;background:#fff;z-index:18;border-top:1px solid var(--border)}.tl-nav-view{display:flex;flex-direction:column}.tl-nav-bar{display:grid;grid-template-columns:1fr auto 1fr;gap:8px;padding:12px 16px;background:#fafbfc;border-bottom:1px solid var(--border);align-items:center}@media(max-width:640px){.tl-nav-bar{padding:10px;gap:6px}}.tl-nav-btn{appearance:none;display:inline-flex;align-items:center;gap:6px;padding:8px 12px;background:#fff;border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:12.5px;font-weight:600;cursor:pointer;transition:all .15s ease;min-width:0;justify-self:start}.tl-nav-btn i{font-size:12px;color:var(--muted);flex-shrink:0}.tl-nav-btn .tl-nav-btn-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:110px}@media(max-width:640px){.tl-nav-btn .tl-nav-btn-label{display:none}}.tl-nav-btn:hover:not(:disabled){border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff;transform:translateY(-1px)}.tl-nav-btn:hover:not(:disabled) i{color:#7c3aed}.tl-nav-btn:disabled{opacity:.4;cursor:not-allowed}.tl-nav-btn--right{justify-self:end;flex-direction:row}@media(max-width:640px){.tl-nav-btn{padding:8px 10px}}.tl-nav-position{display:inline-flex;align-items:center;gap:8px;justify-self:center}.tl-nav-today{appearance:none;display:inline-flex;align-items:center;gap:5px;padding:6px 10px;background:#fff;border:1px solid var(--border);border-radius:999px;color:var(--muted);font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.tl-nav-today i{font-size:10px}.tl-nav-today:hover{border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff}.tl-nav-today.active{background:linear-gradient(135deg,#8b5cf6,#ec4899);border-color:transparent;color:#fff}.tl-nav-today.active:hover{color:#fff}.tl-nav-count{font-size:12px;font-weight:700;color:var(--muted);font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;padding:4px 9px;background:#fff;border:1px solid var(--border);border-radius:6px;white-space:nowrap}.tl-progress-rail{display:flex;align-items:center;gap:3px;padding:10px 16px 2px;overflow-x:auto;scrollbar-width:none}.tl-progress-rail::-webkit-scrollbar{display:none}@media(max-width:640px){.tl-progress-rail{padding:8px 10px 2px}}.tl-rail-dot{appearance:none;flex:1 1 0;min-width:8px;max-width:30px;height:6px;background:#e5e7eb;border:none;border-radius:3px;padding:0;cursor:pointer;transition:background .15s ease,transform .15s ease,height .15s ease}.tl-rail-dot[data-state=past]{background:#cbd5e1}.tl-rail-dot[data-state=current]{background:linear-gradient(90deg,#8b5cf6,#ec4899)}.tl-rail-dot[data-state=future]{background:#e5e7eb}.tl-rail-dot:hover{transform:scaleY(1.6)}.tl-rail-dot[data-selected=true]{height:10px;background:#7c3aed!important;box-shadow:0 0 0 2px #fff,0 0 0 3px #c4b5fd}.tl-nav-body{padding:16px 20px 12px}@media(max-width:640px){.tl-nav-body{padding:12px 12px 8px}}.tl-item--nav,.tl-item--detail{display:block!important;padding-bottom:0!important}.tl-item--nav:before,.tl-item--detail:before{display:none!important}.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{gap:10px}}.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:48px;height:48px;border-width:2px;flex-shrink:0}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:22px}.tl-item--nav .tl-marker .tl-check,.tl-item--detail .tl-marker .tl-check{width:18px;height:18px;font-size:9px;bottom:-2px;right:-2px;border-width:2px}@media(max-width:640px){.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:42px;height:42px}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:20px}}.tl-item--nav .tl-period,.tl-item--detail .tl-period{flex:1 1 120px;min-width:0}.tl-item--nav .tl-mood-badge,.tl-item--detail .tl-mood-badge{margin-left:auto}.tl-item--nav .tl-card,.tl-item--detail .tl-card{animation:tl-card-fade .3s ease;padding:14px}@media(max-width:640px){.tl-item--nav .tl-card,.tl-item--detail .tl-card{padding:12px}}@keyframes tl-card-fade{0%{opacity:0;transform:translate(6px)}to{opacity:1;transform:translate(0)}}.tl-chart-view{padding-top:14px}@media(max-width:640px){.tl-chart-view{padding-top:10px}}.timeline-dialog .dialog-head{align-items:center}.timeline-dialog .dialog-head h2{font-size:17px;margin:2px 0 0}.timeline-dialog .dialog-head .eyebrow{font-size:11px}.timeline-dialog .dialog-head .dialog-lead{margin-top:4px;font-size:12px}\n"] }]
14149
+ ], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"weight-page\">\n <nav class=\"weight-screen-tabs\" role=\"tablist\" aria-label=\"Th\u1EC3 tr\u1EA1ng\">\n @for (tab of screenTabs; track tab.id) {\n <button\n type=\"button\"\n role=\"tab\"\n class=\"weight-screen-tabs__btn\"\n [class.active]=\"screenTab() === tab.id\"\n [attr.aria-selected]=\"screenTab() === tab.id\"\n (click)=\"setScreenTab(tab.id)\"\n >\n <i class=\"pi\" [ngClass]=\"tab.icon\"></i>\n <span>{{ tab.label }}</span>\n </button>\n }\n </nav>\n\n @if (screenTab() === 'dashboard') {\n <div class=\"weight-panel\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"false\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"true\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n @if (growthSnapshot(); as snap) {\n <section class=\"weight-snapshot\" aria-label=\"T\u00F3m t\u1EAFt s\u1ED1 li\u1EC7u\">\n <div class=\"weight-snapshot__head\">\n <h3 class=\"weight-snapshot__title\">T\u00F3m t\u1EAFt s\u1ED1 li\u1EC7u</h3>\n <div class=\"weight-snapshot__toggles\">\n <div class=\"wg-toggle\" role=\"group\" aria-label=\"So s\u00E1nh k\u1EF3\">\n <button\n type=\"button\"\n class=\"wg-toggle__btn\"\n [class.active]=\"comparePeriod() === 'week'\"\n (click)=\"setComparePeriod('week')\"\n >\n Tu\u1EA7n\n </button>\n <button\n type=\"button\"\n class=\"wg-toggle__btn\"\n [class.active]=\"comparePeriod() === 'month'\"\n (click)=\"setComparePeriod('month')\"\n >\n Th\u00E1ng\n </button>\n </div>\n </div>\n </div>\n <div class=\"weight-snapshot__growth\">\n <span class=\"weight-snapshot__growth-label\">Xem theo</span>\n <div class=\"wg-toggle wg-toggle--growth\" role=\"group\" aria-label=\"Ch\u1EC9 s\u1ED1 t\u0103ng tr\u01B0\u1EDFng\">\n <button\n type=\"button\"\n class=\"wg-toggle__btn wg-toggle__btn--icon\"\n [class.active]=\"growthMetric() === 'weight'\"\n (click)=\"setGrowthMetric('weight')\"\n title=\"C\u00E2n n\u1EB7ng\"\n >\n <i class=\"pi pi-chart-bar\"></i>\n <span>C\u00E2n n\u1EB7ng</span>\n </button>\n <button\n type=\"button\"\n class=\"wg-toggle__btn wg-toggle__btn--icon\"\n [class.active]=\"growthMetric() === 'height'\"\n (click)=\"setGrowthMetric('height')\"\n title=\"Chi\u1EC1u cao\"\n >\n <i class=\"pi pi-arrows-v\"></i>\n <span>Chi\u1EC1u cao</span>\n </button>\n </div>\n </div>\n <div class=\"weight-snapshot__grid\">\n <div class=\"weight-metric\">\n <div class=\"weight-metric__label\">\n <i class=\"pi pi-chart-bar\"></i>\n C\u00E2n n\u1EB7ng\n </div>\n <div class=\"weight-metric__value\">\n {{ formatKg(snap.weight.value ?? 0) }}\n <span class=\"weight-metric__unit\">kg</span>\n </div>\n <div\n class=\"weight-metric__delta\"\n [class.weight-metric__delta--down]=\"snap.weight.delta < 0\"\n [class.weight-metric__delta--up]=\"snap.weight.delta > 0\"\n >\n {{ formatSignedDelta(snap.weight.delta, 'kg') }} ({{ formatSignedPercent(snap.weight.pct) }})\n <span class=\"weight-metric__tag\" [title]=\"snap.tip\">{{ snap.tag }}</span>\n </div>\n </div>\n <div class=\"weight-metric\">\n <div class=\"weight-metric__label\">\n <i class=\"pi pi-arrows-v\"></i>\n Chi\u1EC1u cao\n </div>\n <div class=\"weight-metric__value\">\n @if (snap.height.value !== null) {\n {{ formatCm(snap.height.value) }}\n <span class=\"weight-metric__unit\">cm</span>\n } @else {\n <span class=\"weight-metric__unit\">\u2014</span>\n }\n </div>\n <div\n class=\"weight-metric__delta\"\n [class.weight-metric__delta--down]=\"snap.height.delta < 0\"\n [class.weight-metric__delta--up]=\"snap.height.delta > 0\"\n >\n {{ formatSignedDelta(snap.height.delta, 'cm') }}\n <span class=\"weight-metric__tag\" [title]=\"snap.tip\">{{ snap.tag }}</span>\n </div>\n </div>\n <div class=\"weight-metric\">\n <div class=\"weight-metric__label\">\n <i class=\"pi pi-bolt\"></i>\n {{ snap.velocity.label }}\n </div>\n <div class=\"weight-metric__value\">\n @if (snap.velocity.value !== null) {\n {{ snap.velocity.value }}\n <span class=\"weight-metric__unit\">{{ snap.velocity.unit }}</span>\n } @else {\n <span class=\"weight-metric__unit\">\u2014</span>\n }\n </div>\n <div\n class=\"weight-metric__delta\"\n [class.weight-metric__delta--down]=\"snap.velocity.delta < 0\"\n [class.weight-metric__delta--up]=\"snap.velocity.delta > 0\"\n >\n {{ formatSignedDelta(snap.velocity.delta, snap.velocity.suffix) }}\n <span class=\"weight-metric__tag\" [title]=\"snap.tip\">{{ snap.tag }}</span>\n </div>\n </div>\n </div>\n </section>\n }\n\n <app-weight-charts\n [dashboardMode]=\"true\"\n [comparePeriod]=\"comparePeriod()\"\n [growthMetric]=\"growthMetric()\"\n [weightVsStandardChart]=\"weightVsStandardChart()\"\n [heightVsStandardChart]=\"heightVsStandardChart()\"\n [weightTrendChart]=\"weightTrendChart()\"\n [simpleTrendMode]=\"simpleTrendForCharts()\"\n [weightVelocityLineChart]=\"weightVelocityLineChart()\"\n [heightVelocityLineChart]=\"heightVelocityLineChart()\"\n />\n </div>\n }\n\n @if (screenTab() === 'assessment') {\n <div class=\"weight-panel\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"true\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"false\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n </div>\n }\n\n @if (screenTab() === 'history') {\n <div class=\"weight-panel\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"false\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"false\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n <app-weight-history-list\n [loading]=\"loading()\"\n [logs]=\"sortedLogsDesc()\"\n [expanded]=\"true\"\n (edit)=\"openEdit($event)\"\n (remove)=\"deleteLog($event)\"\n />\n </div>\n }\n\n @if (screenTab() === 'reference') {\n <div class=\"weight-panel weight-panel--reference\" role=\"tabpanel\">\n <app-weight-header\n [errorMsg]=\"errorMsg()\"\n [successMsg]=\"successMsg()\"\n [birthDate]=\"birthDate()\"\n [currentTimelineMilestone]=\"currentTimelineMilestone()\"\n [timelineStats]=\"timelineStats()\"\n [latestGrowthEval]=\"latestGrowthEval()\"\n [latestHeightEval]=\"latestHeightEval()\"\n [sortedLogsDesc]=\"sortedLogsDesc()\"\n [latestHeightLog]=\"latestHeightLog()\"\n [showAssessment]=\"false\"\n [showWhoButton]=\"false\"\n [showTimeline]=\"false\"\n (openTimeline)=\"openTimelineDialog()\"\n />\n\n <section class=\"who-ref-panel\">\n <header class=\"who-ref-panel__head\">\n <h2><i class=\"pi pi-table\"></i> B\u1EA3ng so s\u00E1nh theo tu\u1ED5i</h2>\n <p>{{ gender() === 'girl' ? 'B\u00E9 g\u00E1i' : 'B\u00E9 trai' }} \u00B7 0\u201324 th\u00E1ng</p>\n </header>\n <div class=\"who-table-legend\">\n <span class=\"who-legend-item who-legend-item--min\">\n <i class=\"pi pi-arrow-down\"></i>\n <span>\n <strong>Min (\u22122SD)</strong>\n <small>h\u01A1i g\u1EA7y \u00B7 h\u01A1i th\u1EA5p</small>\n </span>\n </span>\n <span class=\"who-legend-item who-legend-item--median\">\n <i class=\"pi pi-minus\"></i>\n <span>\n <strong>Trung v\u1ECB (P50)</strong>\n <small>b\u00ECnh th\u01B0\u1EDDng</small>\n </span>\n </span>\n <span class=\"who-legend-item who-legend-item--max\">\n <i class=\"pi pi-arrow-up\"></i>\n <span>\n <strong>Max (+2SD)</strong>\n <small>h\u01A1i n\u1EB7ng \u00B7 h\u01A1i cao</small>\n </span>\n </span>\n </div>\n <div class=\"who-table-scroll who-table-scroll--inline\">\n <table class=\"who-table who-table--inline\">\n <thead>\n <tr>\n <th class=\"who-th-age\">Tu\u1ED5i</th>\n <th class=\"who-th-value who-th-icon\">\n <i class=\"pi pi-chart-bar\" aria-hidden=\"true\"></i>\n <span>(kg)</span>\n </th>\n <th class=\"who-th-value who-th-icon\">\n <i class=\"pi pi-arrows-v\" aria-hidden=\"true\"></i>\n <span>(cm)</span>\n </th>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <th class=\"who-th-current\">B\u00E9</th>\n }\n </tr>\n </thead>\n <tbody>\n @for (row of whoStandardTable(); track row) {\n <tr [class.who-row-current]=\"row.isCurrent\">\n <td class=\"who-td-age\">\n <span class=\"who-age-label\">{{ row.label }}</span>\n @if (row.weeks > 0) {\n <span class=\"who-age-weeks\">{{ row.weeks }} tu\u1EA7n</span>\n }\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatKg(row.minKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatKg(row.medianKg) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatKg(row.maxKg) }}</strong>\n </div>\n </td>\n <td class=\"who-td-value who-td-merged\">\n <div class=\"who-range-line who-range-line--min\">\n <strong>{{ formatCm(row.minCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--median\">\n <strong>{{ formatCm(row.medianCm) }}</strong>\n </div>\n <div class=\"who-range-line who-range-line--max\">\n <strong>{{ formatCm(row.maxCm) }}</strong>\n </div>\n </td>\n @if (sortedLogsDesc().length > 0 || latestHeightLog()) {\n <td class=\"who-td-current who-td-current--stack\">\n <div class=\"who-current-stack\">\n @if (row.currentWeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentWeightLevel(row)\"\n >\n <strong>{{ formatKg(row.currentWeight) }}</strong>\n <span class=\"who-current-unit\">kg</span>\n </div>\n }\n @if (row.currentHeight !== null) {\n <div\n class=\"who-current-row\"\n [attr.data-level]=\"whoCurrentHeightLevel(row)\"\n >\n <strong>{{ formatCm(row.currentHeight) }}</strong>\n <span class=\"who-current-unit\">cm</span>\n </div>\n }\n </div>\n </td>\n }\n </tr>\n }\n </tbody>\n </table>\n </div>\n <p class=\"who-footer-note who-footer-note--inline\">\n <i class=\"pi pi-info-circle\"></i>\n M\u1ED1c <strong>\u22122SD</strong> / <strong>+2SD</strong> ch\u1EC9 mang t\u00EDnh tham kh\u1EA3o.\n </p>\n </section>\n </div>\n }\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAddDialog()\"\n aria-label=\"Th\u00EAm l\u1EA7n c\u00E2n\"\n title=\"Th\u00EAm l\u1EA7n c\u00E2n\"\n >\n <i class=\"pi pi-plus\"></i>\n <span class=\"fab-label\">Th\u00EAm l\u1EA7n c\u00E2n</span>\n </button>\n\n <!-- Dialog th\u00EAm (gi\u1ED1ng c\u1EEF b\u00FA) -->\n @if (addDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeAddDialog()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">Nh\u1EADt k\u00FD c\u00E2n n\u1EB7ng</div>\n <h2><i class=\"pi pi-chart-line\"></i> Th\u00EAm l\u1EA7n c\u00E2n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAddDialog()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <form class=\"log-form\" (ngSubmit)=\"submitAdd()\">\n <div class=\"log-grid weight-log-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y c\u00E2n</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"draft().date\"\n (input)=\"updateDraftDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n n\u1EB7ng (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 4,25\"\n [value]=\"draft().weightInput\"\n (input)=\"updateDraftWeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n placeholder=\"VD: 63,5\"\n [value]=\"draft().heightInput\"\n (input)=\"updateDraftHeight($any($event.target).value)\"\n autocomplete=\"off\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"Tu\u1EF3 ch\u1ECDn \u2014 ho\u1EB7c ch\u1ECDn g\u1EE3i \u00FD b\u00EAn d\u01B0\u1EDBi\"\n [value]=\"draft().note\"\n (input)=\"updateDraftNote($any($event.target).value)\"\n />\n </label>\n </div>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(draft().note, t)\"\n (click)=\"toggleDraftNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAddDialog()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving() || loading()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </form>\n </div>\n </div>\n }\n\n @if (editingLog()) {\n <div class=\"edit-overlay\" (click)=\"cancelEdit()\"></div>\n }\n @if (editingLog()) {\n <aside class=\"edit-sheet\" role=\"dialog\" aria-modal=\"true\">\n <header class=\"edit-sheet-head\">\n <h3>S\u1EEDa ghi nh\u1EADn</h3>\n <button type=\"button\" class=\"btn icon ghost\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </header>\n <form class=\"form\" (ngSubmit)=\"submitEdit()\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y</span>\n <input\n type=\"date\"\n class=\"input\"\n [value]=\"editDraft().date\"\n (input)=\"updateEditDate($any($event.target).value)\"\n [max]=\"maxBirthDate\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">C\u00E2n (kg)</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().weightInput\"\n (input)=\"updateEditWeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Chi\u1EC1u cao (cm) \u00B7 tu\u1EF3 ch\u1ECDn</span>\n <input\n type=\"text\"\n class=\"input\"\n inputmode=\"decimal\"\n [value]=\"editDraft().heightInput\"\n (input)=\"updateEditHeight($any($event.target).value)\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Ghi ch\u00FA c\u00E2n n\u1EB7ng</span>\n <input\n type=\"text\"\n class=\"input\"\n [value]=\"editDraft().note\"\n (input)=\"updateEditNote($any($event.target).value)\"\n />\n </label>\n <div class=\"note-quick-tags\">\n <span class=\"note-quick-label\">Ghi nhanh:</span>\n @for (t of noteQuickTags; track t) {\n <button\n type=\"button\"\n class=\"note-chip\"\n [class.active]=\"noteHasTag(editDraft().note, t)\"\n (click)=\"toggleEditNoteTag(t)\"\n >\n {{ t }}\n </button>\n }\n </div>\n <div class=\"edit-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button type=\"submit\" class=\"btn btn-primary\" [disabled]=\"saving()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </form>\n </aside>\n }\n\n <!-- Timeline ph\u00E1t tri\u1EC3n -->\n @if (timelineDialogOpen()) {\n <div class=\"dialog-overlay\" (click)=\"closeTimelineDialog()\">\n <div\n class=\"dialog dialog-wide timeline-dialog\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <div class=\"dialog-head\">\n <div>\n <div class=\"eyebrow\">\n <i class=\"pi pi-chart-line\"></i>\n D\u00F2ng th\u1EDDi gian ph\u00E1t tri\u1EC3n\n </div>\n <h2>\uD83C\uDF31 H\u00E0nh tr\u00ECnh c\u1EE7a {{ babyName() || 'b\u00E9' }}</h2>\n @if (ageInDays() !== null) {\n <p class=\"dialog-lead\">\n {{ ageInDays() }} ng\u00E0y tu\u1ED5i \u00B7\n <span class=\"tl-head-stat\"\n >{{ timelineStats().past }}/{{ timelineStats().total }} c\u1ED9t m\u1ED1c \u0111\u00E3 qua</span\n >\n </p>\n }\n </div>\n <button\n type=\"button\"\n class=\"dialog-close\"\n (click)=\"closeTimelineDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n <div class=\"tl-tabs\" role=\"tablist\">\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'list'\"\n (click)=\"setTimelineView('list')\"\n >\n <i class=\"pi pi-list\"></i>\n <span>Danh s\u00E1ch</span>\n </button>\n <button\n type=\"button\"\n role=\"tab\"\n class=\"tl-tab\"\n [class.active]=\"timelineView() === 'chart'\"\n (click)=\"setTimelineView('chart')\"\n >\n <i class=\"pi pi-chart-line\"></i>\n <span>Bi\u1EC3u \u0111\u1ED3 t\u00E2m tr\u1EA1ng</span>\n </button>\n </div>\n @if (timelineView() === 'chart') {\n <div class=\"tl-chart-view\">\n <div class=\"tl-chart-mood-legend\">\n <span class=\"tl-mood-badge\" data-mood=\"happy\">\n <i class=\"pi pi-heart-fill\"></i> Vui\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"milestone\">\n <i class=\"pi pi-star-fill\"></i> C\u1ED9t m\u1ED1c\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"calm\">\n <i class=\"pi pi-circle-fill\"></i> B\u00ECnh y\u00EAn\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"growth\">\n <i class=\"pi pi-arrow-up\"></i> T\u0103ng tr\u01B0\u1EDFng\n </span>\n <span class=\"tl-mood-badge\" data-mood=\"leap\">\n <i class=\"pi pi-bolt\"></i> Leap/WW\n </span>\n </div>\n @if (timelineChartData(); as cd) {\n <div class=\"tl-chart-scroll\">\n <svg\n [attr.viewBox]=\"'0 0 ' + cd.W + ' ' + cd.H\"\n [attr.width]=\"cd.W\"\n [attr.height]=\"cd.H\"\n class=\"tl-chart-svg\"\n preserveAspectRatio=\"xMinYMid meet\"\n >\n <defs>\n <linearGradient id=\"tlMoodGradientW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#22c55e\" stop-opacity=\"0.20\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" stop-opacity=\"0.14\" />\n <stop offset=\"100%\" stop-color=\"#ef4444\" stop-opacity=\"0.08\" />\n </linearGradient>\n <linearGradient id=\"tlMoodLineW\" x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n <stop offset=\"0%\" stop-color=\"#16a34a\" />\n <stop offset=\"45%\" stop-color=\"#eab308\" />\n <stop offset=\"100%\" stop-color=\"#dc2626\" />\n </linearGradient>\n </defs>\n <g class=\"tl-chart-guides\">\n @for (g of cd.moodGuides; track g) {\n <line\n [attr.x1]=\"cd.PAD_L\"\n [attr.x2]=\"cd.W - cd.PAD_R\"\n [attr.y1]=\"g.y\"\n [attr.y2]=\"g.y\"\n stroke=\"#e5e7eb\"\n stroke-dasharray=\"2 4\"\n stroke-width=\"1\"\n />\n }\n @for (g of cd.moodGuides; track g) {\n <text\n [attr.x]=\"cd.PAD_L - 6\"\n [attr.y]=\"g.y + 3\"\n [attr.data-mood]=\"g.mood\"\n text-anchor=\"end\"\n class=\"tl-chart-guide-label\"\n >\n {{ g.label }}\n </text>\n }\n </g>\n <path [attr.d]=\"cd.areaPath\" fill=\"url(#tlMoodGradientW)\" />\n <path\n [attr.d]=\"cd.linePath\"\n fill=\"none\"\n stroke=\"url(#tlMoodLineW)\"\n stroke-width=\"2.5\"\n stroke-linecap=\"round\"\n stroke-linejoin=\"round\"\n />\n <g class=\"tl-chart-x-labels\">\n @for (p of cd.points; track p) {\n <text\n [attr.x]=\"p.x\"\n [attr.y]=\"cd.PAD_T + cd.PLOT_H + 38\"\n text-anchor=\"middle\"\n class=\"tl-chart-x-label\"\n [class.past]=\"p.entry.state === 'past'\"\n [class.current]=\"p.entry.state === 'current'\"\n >\n {{ p.entry.milestone.periodLabel }}\n </text>\n }\n </g>\n <g class=\"tl-chart-nodes\">\n @for (p of cd.points; track p) {\n <g\n class=\"tl-chart-node\"\n [attr.data-state]=\"p.entry.state\"\n [attr.data-mood]=\"p.entry.milestone.mood\"\n [attr.data-accent]=\"p.entry.milestone.accent\"\n [attr.data-current]=\"p.entry.state === 'current' ? 'true' : null\"\n [attr.data-selected]=\"\n selectedTimelineId() === p.entry.milestone.id ? 'true' : null\n \"\n [attr.transform]=\"'translate(' + p.x + ',' + p.y + ')'\"\n (click)=\"selectTimelineNode(p.entry.milestone.id)\"\n role=\"button\"\n tabindex=\"0\"\n [attr.aria-label]=\"\n p.entry.milestone.periodLabel + ' - ' + p.entry.milestone.tagline\n \"\n >\n @if (p.entry.state === 'current') {\n <circle\n class=\"tl-chart-pulse\"\n r=\"22\"\n fill=\"none\"\n stroke=\"currentColor\"\n stroke-width=\"2\"\n />\n }\n @if (selectedTimelineId() === p.entry.milestone.id) {\n <circle\n class=\"tl-chart-halo\"\n r=\"20\"\n fill=\"currentColor\"\n fill-opacity=\"0.18\"\n />\n }\n <circle class=\"tl-chart-dot\" r=\"15\" />\n <text\n class=\"tl-chart-emoji\"\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n y=\"1\"\n >\n {{ p.entry.milestone.heroEmoji }}\n </text>\n @if (p.entry.state === 'past') {\n <g transform=\"translate(11,-11)\">\n <circle r=\"6.5\" fill=\"#10b981\" />\n <text\n text-anchor=\"middle\"\n dominant-baseline=\"central\"\n fill=\"#ffffff\"\n font-size=\"8\"\n font-weight=\"700\"\n y=\"0.5\"\n >\n \u2713\n </text>\n </g>\n }\n </g>\n }\n </g>\n </svg>\n </div>\n }\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-chart-detail\">\n <article\n class=\"tl-item tl-item--detail\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n @if (timelineView() === 'list') {\n <div class=\"tl-nav-view\">\n <div class=\"tl-nav-bar\">\n <button\n type=\"button\"\n class=\"tl-nav-btn\"\n (click)=\"prevTimeline()\"\n [disabled]=\"selectedTimelineIndex() === 0\"\n aria-label=\"C\u1ED9t m\u1ED1c tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n @if (selectedTimelineIndex() > 0) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() - 1].milestone.periodLabel }}\n </span>\n }\n </button>\n <div class=\"tl-nav-position\">\n @if (currentTimelineMilestone()) {\n <button\n type=\"button\"\n class=\"tl-nav-today\"\n (click)=\"goToCurrentTimeline()\"\n [class.active]=\"\n selectedTimelineEntry() && selectedTimelineEntry()!.state === 'current'\n \"\n title=\"V\u1EC1 c\u1ED9t m\u1ED1c hi\u1EC7n t\u1EA1i\"\n >\n <i class=\"pi pi-compass\"></i>\n Hi\u1EC7n t\u1EA1i\n </button>\n }\n <span class=\"tl-nav-count\">\n {{ selectedTimelineIndex() + 1 }}/{{ timelineEntries().length }}\n </span>\n </div>\n <button\n type=\"button\"\n class=\"tl-nav-btn tl-nav-btn--right\"\n (click)=\"nextTimeline()\"\n [disabled]=\"selectedTimelineIndex() >= timelineEntries().length - 1\"\n aria-label=\"C\u1ED9t m\u1ED1c ti\u1EBFp theo\"\n >\n @if (selectedTimelineIndex() < timelineEntries().length - 1) {\n <span class=\"tl-nav-btn-label\">\n {{ timelineEntries()[selectedTimelineIndex() + 1].milestone.periodLabel }}\n </span>\n }\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n <div class=\"tl-progress-rail\">\n @for (e of timelineEntries(); track e; let i = $index) {\n <button\n type=\"button\"\n class=\"tl-rail-dot\"\n [attr.data-state]=\"e.state\"\n [attr.data-selected]=\"selectedTimelineIndex() === i ? 'true' : null\"\n (click)=\"selectTimelineNode(e.milestone.id)\"\n [attr.aria-label]=\"e.milestone.periodLabel\"\n [title]=\"e.milestone.periodLabel + ' \u00B7 ' + e.milestone.tagline\"\n ></button>\n }\n </div>\n @if (selectedTimelineEntry(); as sel) {\n <div class=\"tl-nav-body\">\n <article\n class=\"tl-item tl-item--nav\"\n [attr.data-accent]=\"sel.milestone.accent\"\n [attr.data-mood]=\"sel.milestone.mood\"\n [attr.data-state]=\"sel.state\"\n >\n <div class=\"tl-card\">\n <header class=\"tl-card-head\">\n <div class=\"tl-marker\">\n <span class=\"tl-hero-emoji\">{{ sel.milestone.heroEmoji }}</span>\n @if (sel.state === 'past') {\n <i class=\"pi pi-check tl-check\"></i>\n }\n @if (sel.state === 'current') {\n <span class=\"tl-now-pulse\"></span>\n }\n </div>\n <div class=\"tl-period\">\n <span class=\"tl-period-label\">{{ sel.milestone.periodLabel }}</span>\n <span class=\"tl-tagline\">{{ sel.milestone.tagline }}</span>\n </div>\n <span class=\"tl-mood-badge\" [attr.data-mood]=\"sel.milestone.mood\">\n <i\n class=\"pi\"\n [class.pi-heart-fill]=\"sel.milestone.mood === 'happy'\"\n [class.pi-circle-fill]=\"sel.milestone.mood === 'calm'\"\n [class.pi-arrow-up]=\"sel.milestone.mood === 'growth'\"\n [class.pi-bolt]=\"sel.milestone.mood === 'leap'\"\n [class.pi-star-fill]=\"sel.milestone.mood === 'milestone'\"\n ></i>\n {{ sel.milestone.moodLabel }}\n </span>\n </header>\n <div class=\"tl-stomach\">\n <div class=\"tl-stomach-visual\">\n <span\n class=\"tl-stomach-circle\"\n [style.transform]=\"'scale(' + sel.milestone.stomach.scale + ')'\"\n ></span>\n <span class=\"tl-stomach-emoji\">{{ sel.milestone.stomach.emoji }}</span>\n </div>\n <div class=\"tl-stomach-text\">\n <div class=\"tl-stomach-title\">\n <i class=\"pi pi-apple\"></i>\n D\u1EA1 d\u00E0y b\u1EB1ng {{ sel.milestone.stomach.compareTo }}\n </div>\n <div class=\"tl-stomach-vol\">{{ sel.milestone.stomach.volumeMl }}</div>\n </div>\n </div>\n @if (sel.state === 'current') {\n <div class=\"tl-now-banner\">\n <i class=\"pi pi-compass\"></i>\n B\u00E9 c\u1EE7a b\u1EA1n \u0111ang \u1EDF \u0111\u00E2y \uD83C\uDFAF\n </div>\n }\n @if (sel.milestone.skills?.length) {\n <div class=\"tl-skills\">\n <span class=\"tl-skills-label\">\n <i class=\"pi pi-sparkles\"></i> K\u1EF9 n\u0103ng:\n </span>\n @for (s of sel.milestone.skills; track s) {\n <span class=\"tl-skill-chip\">{{ s }}</span>\n }\n </div>\n }\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-lightbulb\"></i> \u0110i\u1EC1u th\u00FA v\u1ECB\n </div>\n <ul class=\"tl-list\">\n @for (f of sel.milestone.funFacts; track f) {\n <li>{{ f }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-bookmark-fill\"></i> Ba m\u1EB9 l\u01B0u \u00FD\n </div>\n <ul class=\"tl-list\">\n @for (n of sel.milestone.parentNotes | slice: 0 : 2; track n) {\n <li>\n {{ n }}\n </li>\n }\n </ul>\n </div>\n @if (selectedTimelineCareGuide(); as care) {\n <div class=\"tl-care-grid\">\n <div class=\"tl-section tl-section-care tl-section-care--todo\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-check-circle\"></i> Todo list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.todos; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--avoid\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-ban\"></i> Not-to-do list\n </div>\n <ul class=\"tl-list\">\n @for (item of care.notToDo; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n <div class=\"tl-section tl-section-care tl-section-care--watch\">\n <div class=\"tl-section-label\">\n <i class=\"pi pi-exclamation-triangle\"></i> H\u1ECFi b\u00E1c s\u0129 n\u1EBFu\n </div>\n <ul class=\"tl-list\">\n @for (item of care.watchFor; track item) {\n <li>{{ item }}</li>\n }\n </ul>\n </div>\n </div>\n }\n </div>\n </article>\n </div>\n }\n </div>\n }\n <div class=\"tl-sources\">\n <i class=\"pi pi-info-circle\"></i>\n T\u1ED5ng h\u1EE3p t\u1EEB: AAP HealthyChildren, CDC Milestones, WHO, The Wonder Weeks (Plooij), Vinmec.\n </div>\n </div>\n </div>\n }\n\n</div>\n", styles: ["@charset \"UTF-8\";:host{display:block;font-family:-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Helvetica Neue,Arial,sans-serif;--bg: #f6f7fb;--surface: #ffffff;--border: #e6e8ef;--border-strong: #d1d5db;--primary: #5b6cff;--primary-2: #4c5cf0;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--muted-2: #94a3b8;--rose: #ef4444;--rose-soft: #fee2e2}.weight-page{max-width:920px;margin:0 auto;padding:12px 16px calc(132px + env(safe-area-inset-bottom,0px));display:flex;flex-direction:column;gap:4px}.weight-page>.card,.weight-page>.weight-secondary-suite{padding:14px;margin-bottom:11px;border-radius:14px}.weight-page>.timeline-cta{margin-bottom:11px;padding:10px 12px;gap:10px;border-radius:12px}.weight-page>.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.weight-page>.timeline-cta .tcta-now{font-size:12.5px}.weight-alerts-block{padding-top:10px;margin-bottom:2px}.weight-inline-hint{display:flex;align-items:flex-start;gap:8px;margin:0 0 8px;padding:0;font-size:12px;line-height:1.45;color:var(--muted)}.weight-inline-hint i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-ref-cta{display:inline-flex;align-items:center;gap:8px;margin:0 0 10px;padding:8px 14px;border-radius:10px;border:1px solid var(--border);background:#fff;color:var(--text);font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;transition:border-color .15s,background .15s,color .15s}.who-ref-cta i{color:var(--primary)}.who-ref-cta:hover{border-color:var(--primary);background:var(--primary-soft);color:var(--primary)}.card{background:var(--surface);border:1px solid var(--border);border-radius:16px;padding:20px;margin-bottom:16px}.card-header{margin-bottom:16px}.card-header h2{margin:0 0 6px;font-size:17px;font-weight:700;display:flex;align-items:center;gap:8px;color:var(--text)}.card-header h2 i{color:var(--primary)}.card-header p{margin:0;font-size:13px;color:var(--muted);line-height:1.45}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:10px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.form.weight-form{display:grid;grid-template-columns:1fr 1fr;gap:12px;align-items:end}@media(max-width:640px){.form.weight-form{grid-template-columns:1fr}}.form.weight-form .field--grow{grid-column:1/-1}@media(min-width:641px){.form.weight-form .field--grow{grid-column:span 2}}.form.weight-form .btn-primary{grid-column:1/-1;justify-self:start}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit;background:#fff;color:var(--text)}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:10px 16px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer;transition:background .15s,transform .12s}.btn:disabled{opacity:.55;cursor:not-allowed}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost:hover:not(:disabled){color:var(--primary);border-color:var(--primary)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon{padding:8px}.btn:active:not(:disabled){transform:scale(.98)}.history-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:12px}.history-head h3{margin:0;font-size:15px;font-weight:700}.history-head .muted{font-size:12px;color:var(--muted)}.empty-mini{font-size:13px;color:var(--muted);padding:12px 0}.weight-list{list-style:none;margin:0;padding:0}.weight-row{display:flex;align-items:flex-start;justify-content:space-between;gap:10px;padding:12px 0;border-bottom:1px solid var(--border)}.weight-row:last-child{border-bottom:none}.weight-row-main{display:flex;flex-wrap:wrap;align-items:baseline;gap:6px 12px;min-width:0}.w-date{font-weight:600;font-size:14px;color:var(--text)}.w-kg{font-size:15px;font-weight:700;color:var(--primary)}.w-height{font-size:14px;font-weight:700;color:#0ea5e9}.w-note{font-size:13px;color:var(--muted);flex-basis:100%}.weight-row-actions{display:flex;gap:4px;flex-shrink:0}.weight-page .weight-history .history-head{margin-bottom:8px}.weight-page .weight-history .history-head h3{font-size:14px}.weight-page .weight-history .weight-row{padding:9px 0}.weight-page .weight-history .empty-mini{padding:8px 0;font-size:12.5px}.edit-overlay{position:fixed;inset:0;background:#0f172a59;z-index:120}.edit-sheet{position:fixed;left:0;right:0;bottom:0;z-index:121;max-height:85vh;overflow:auto;background:var(--surface);border-radius:18px 18px 0 0;padding:16px 20px calc(20px + env(safe-area-inset-bottom,0px));box-shadow:0 -12px 40px #0f172a1f;animation:sheetUp .22s ease-out}@media(min-width:640px){.edit-sheet{inset:50% auto auto 50%;transform:translate(-50%,-50%);max-width:400px;border-radius:16px;max-height:90vh;animation:none}}@keyframes sheetUp{0%{transform:translateY(100%)}to{transform:translateY(0)}}.edit-sheet-head{display:flex;align-items:center;justify-content:space-between;margin-bottom:14px}.edit-sheet-head h3{margin:0;font-size:16px;font-weight:700}.edit-actions{display:flex;gap:10px;justify-content:flex-end;margin-top:16px}.edit-sheet .field{margin-bottom:12px}.weight-eval[data-status=ok]{border-color:#a7f3d0}.weight-eval[data-status=mid]{border-color:#fde68a}.weight-eval[data-status=warn]{border-color:#fecaca}.weight-eval__head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:8px}.weight-eval__head h3{margin:2px 0 0;font-size:16px;font-weight:700}.weight-eval__head .muted{font-size:11.5px;color:var(--muted);margin:3px 0 0}.eyebrow{font-size:10.5px;font-weight:700;text-transform:uppercase;letter-spacing:.4px;color:var(--primary);display:flex;align-items:center;gap:6px}.eyebrow i{font-size:11px}.weight-eval__badge{flex-shrink:0;padding:6px 12px;border-radius:999px;font-size:12px;font-weight:700;background:#ecfdf5;color:#047857;border:1px solid #a7f3d0}.weight-eval__badge[data-status=mid]{background:#fffbeb;color:#b45309;border-color:#fde68a}.weight-eval__badge[data-status=warn]{background:#fef2f2;color:#b91c1c;border-color:#fecaca}.weight-eval__badge[data-status=neutral]{background:#f1f5f9;color:var(--muted);border-color:var(--border)}.weight-eval__detail{margin:0;font-size:13px;line-height:1.55;color:var(--text);text-align:justify;text-justify:inter-word;-webkit-hyphens:manual;hyphens:manual}.weight-chart-card .history-head{margin-bottom:10px}.weight-chart-card .history-head h3{font-size:14px}.weight-chart-card.weight-chart-card--compare .history-head--chart h3{margin-bottom:6px}.weight-chart-card.weight-chart-card--compare .chart-sub{margin:0 0 8px;font-size:13px;line-height:1.45;color:var(--muted)}.weight-chart-card.weight-chart-card--compare .chart-meta{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;border-radius:999px;font-size:12px;background:var(--surface-2, rgba(0, 0, 0, .04));color:var(--text)}.weight-chart-card.weight-chart-card--compare .chart-meta__pill i{font-size:12px;opacity:.85}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 220px}.weight-chart-card.weight-chart-card--compare .compare-legend{display:flex;flex-wrap:wrap;gap:8px 14px;margin-bottom:10px;font-size:11.5px;color:var(--text)}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-item{display:inline-flex;align-items:center;gap:8px}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot{width:10px;height:10px;border-radius:50%;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--actual{background:#5b6cff;box-shadow:0 0 0 2px #fff}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-dot--height{background:#0ea5e9}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line{width:20px;height:3px;border-radius:2px;flex-shrink:0}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-line--median{background:#16a34a}.weight-chart-card.weight-chart-card--compare .compare-legend .cl-band{width:20px;height:11px;border-radius:2px;flex-shrink:0;background:linear-gradient(180deg,#22c55e38,#22c55e0f);border:1px solid rgba(34,197,94,.35)}.weight-chart-card.weight-chart-card--compare .chart-meta--tight{margin-top:6px;gap:6px}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth{flex:1 1 180px;min-width:0}.weight-chart-card.weight-chart-card--compare .chart-meta__pill--birth strong{font-weight:700;word-break:break-word}.chart-meta--tight{display:flex;flex-wrap:wrap;gap:8px;margin:0}.weight-secondary-suite .weight-secondary-suite__head{margin-bottom:8px}.weight-secondary-suite .weight-secondary-suite__head h3{margin:0;font-size:14px;font-weight:800;display:flex;align-items:center;gap:6px;color:var(--text)}.weight-secondary-suite .weight-secondary-suite__head h3 i{color:var(--primary)}.weight-insights-grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:10px}@media(max-width:720px){.weight-insights-grid{grid-template-columns:1fr}}.weight-subchart--full{grid-column:1/-1}.weight-subchart{min-width:0}.weight-subchart h4{margin:0 0 5px;font-size:12px;font-weight:800;color:var(--text);display:flex;align-items:center;gap:5px}.weight-subchart h4 i{color:var(--primary);font-size:12px}.weight-subchart-svg .w-zero-line{stroke:#94a3b8;stroke-width:1.1;stroke-dasharray:5 3}.weight-subchart-svg .w-path--velocity{fill:none;stroke-width:2.4;stroke-linecap:round;stroke-linejoin:round}.weight-subchart-svg .w-dot--velocity{fill:#fff;stroke:#14b8a6;stroke-width:2}.chart-scroll-x{overflow-x:auto;margin:0 -6px;padding:4px 6px}.chart-scroll-x::-webkit-scrollbar{height:6px}.chart-scroll-x::-webkit-scrollbar-thumb{background:#cbd5e1;border-radius:4px}.weight-svg-chart{display:block;min-width:100%;height:auto}.weight-svg-chart .grid line{stroke:#eef2f7;stroke-width:1}.weight-svg-chart .grid-label{font-size:11px;letter-spacing:.02em;fill:var(--muted)}.weight-svg-chart .w-area{stroke:none}.weight-svg-chart .w-path--actual{fill:none;stroke-width:2.75;stroke-linecap:round;stroke-linejoin:round}.weight-svg-chart .w-dot{fill:#5b6cff;stroke:#fff;stroke-width:2}.weight-svg-chart .w-dot--height{fill:#0ea5e9}.weight-svg-chart .w-dot-label{font-size:10px;font-weight:700;fill:var(--text)}.weight-svg-chart .x-label{font-size:9px;fill:var(--muted);letter-spacing:.04em}.weight-svg-chart .axis-x-baseline{stroke:var(--border-strong);stroke-width:1.25}.weight-svg-chart .x-label--rot{font-size:10.5px;font-weight:600;fill:#475569}.weight-svg-chart .x-label--tick{font-size:10px;font-weight:600}.weight-svg-chart.weight-svg-chart--compare .w-band-fill{stroke:none}.weight-svg-chart.weight-svg-chart--compare .w-path--ref-edge{fill:none;stroke:#94a3b8;stroke-width:1.15;stroke-dasharray:5 4;opacity:.9}.weight-svg-chart.weight-svg-chart--compare .w-path--median{fill:none;stroke:#16a34a;stroke-width:2.1;opacity:.95}.weight-svg-chart.weight-svg-chart--compare .w-path--height{stroke-width:2.8}.weight-svg-chart.weight-svg-chart--compare .x-label--age{font-size:10px;font-weight:600;fill:var(--text);letter-spacing:.01em}.weight-svg-chart.weight-svg-chart--compare .x-label--sub{font-size:8px;font-weight:400;fill:var(--muted-2);letter-spacing:.02em}.weight-svg-chart.weight-svg-chart--compare .x-label--small{font-size:9px;opacity:.92}.weight-svg-chart.weight-svg-chart--compare .x-tick-line{stroke:var(--border-strong);stroke-width:1}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:20px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;cursor:pointer;box-shadow:0 8px 24px #5b6cff73;transition:transform .15s,box-shadow .15s,background .15s}.fab i{font-size:18px;display:inline-flex;align-items:center;justify-content:center;width:24px;height:24px;border-radius:50%;background:#ffffff2e}.fab .fab-label{white-space:nowrap}.fab:hover{background:var(--primary-2);transform:translateY(-2px);box-shadow:0 12px 28px #5b6cff8c}.fab:active{transform:translateY(0)}@media(max-width:480px){.fab{padding:14px}.fab .fab-label{display:none}.fab i{font-size:22px;width:28px;height:28px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:20px;animation:wovelayIn .2s ease-out}@media(max-width:640px){.dialog-overlay{padding:calc(env(safe-area-inset-top,0px) + 16px) 12px calc(env(safe-area-inset-bottom,0px) + 16px);align-items:flex-start}}@keyframes wovelayIn{0%{opacity:0}to{opacity:1}}.dialog{background:var(--surface);border-radius:20px;width:100%;max-width:560px;max-height:min(100vh - 40px,100dvh - 40px);overflow-y:auto;padding:24px;animation:wdialogIn .25s cubic-bezier(.4,0,.2,1);box-shadow:0 20px 60px #0f172a40}@media(max-width:640px){.dialog{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 32px);border-radius:16px}}@keyframes wdialogIn{0%{opacity:0;transform:translateY(20px) scale(.96)}to{opacity:1;transform:translateY(0) scale(1)}}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:12px;margin-bottom:18px}.dialog-head h2{margin:4px 0 0;font-size:20px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{background:#f1f3f7;border:none;width:36px;height:36px;border-radius:50%;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.log-form{display:flex;flex-direction:column;gap:14px}.weight-log-grid{grid-template-columns:1fr 1fr!important}@media(max-width:560px){.weight-log-grid{grid-template-columns:1fr!important}}.log-grid{display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:12px}.log-grid .field.full{grid-column:1/-1}.note-quick-tags{display:flex;flex-wrap:wrap;align-items:center;gap:8px 10px;margin-top:4px;margin-bottom:4px}.note-quick-label{font-size:12px;font-weight:600;color:var(--muted);width:100%;margin-bottom:2px}@media(min-width:400px){.note-quick-label{width:auto;margin-bottom:0}}.note-chip{appearance:none;border:1px solid var(--border);background:#f8fafc;color:var(--text);font-size:12px;font-weight:600;font-family:inherit;padding:6px 12px;border-radius:999px;cursor:pointer;transition:background .15s,border-color .15s,color .15s}.note-chip:hover{border-color:var(--primary);color:var(--primary);background:var(--primary-soft)}.note-chip.active{background:var(--primary);border-color:var(--primary);color:#fff}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:8px}.dialog-wide{max-width:720px;display:flex;flex-direction:column;padding:0;overflow:hidden}@media(max-width:640px){.dialog-wide{max-height:calc(100dvh - env(safe-area-inset-top,0px) - env(safe-area-inset-bottom,0px) - 72px);border-radius:14px}}.dialog-wide .dialog-head{position:sticky;top:0;z-index:5;background:var(--surface);padding:16px 20px;margin:0;border-bottom:1px solid var(--border);-webkit-backdrop-filter:saturate(1.2) blur(6px);backdrop-filter:saturate(1.2) blur(6px)}@media(max-width:640px){.dialog-wide .dialog-head{padding:12px 14px}}.dialog-wide .dialog-close{width:40px;height:40px;font-size:16px;background:#f1f3f7;color:var(--text)}.dialog-wide .dialog-close:hover{background:var(--rose-soft);color:var(--rose)}.dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.who-table-btn{display:inline-flex;align-items:center;gap:6px;margin-top:12px;padding:8px 14px;font-size:12px;font-weight:600;color:var(--primary);background:var(--primary-soft);border:1px solid transparent;border-radius:8px;cursor:pointer;transition:all .15s ease}.who-table-btn i{font-size:14px}.who-table-btn:hover{background:var(--primary);color:#fff}.who-table-dialog .dialog-head h2{display:flex;align-items:center;gap:8px;font-size:17px;font-weight:700;margin:0;color:var(--text)}.who-table-legend{display:flex;flex-wrap:wrap;align-items:center;gap:12px 20px;padding:12px 20px;background:#f8fafc;border-bottom:1px solid var(--border);font-size:12px}@media(max-width:640px){.who-table-legend{padding:10px 14px;gap:8px 14px}}.who-legend-item{display:inline-flex;align-items:center;gap:5px;font-weight:500;color:var(--muted)}.who-legend-item i{font-size:11px}.who-legend-item--min,.who-legend-item--min i{color:#f59e0b}.who-legend-item--median,.who-legend-item--median i{color:#22c55e}.who-legend-item--max,.who-legend-item--max i{color:#3b82f6}.who-table-scroll{flex:1;overflow-y:auto;padding:0;max-height:420px}@media(max-width:640px){.who-table-scroll{max-height:320px}}.who-table{width:100%;border-collapse:collapse;font-size:13px}.who-table thead{position:sticky;top:0;z-index:2;background:var(--surface)}.who-table th{padding:10px 12px;text-align:center;font-weight:600;color:var(--text);border-bottom:2px solid var(--border);background:var(--surface)}.who-table th.who-th-age{text-align:left;padding-left:20px}.who-table th.who-th-median{background:#f0fdf4;color:#166534}.who-table th.who-th-merged{background:#f8fafc;color:var(--text);min-width:120px}.who-table th.who-th-current{background:#eef2ff;color:var(--primary)}.who-table td{padding:10px 12px;text-align:center;border-bottom:1px solid #f1f5f9;vertical-align:middle}.who-table tbody tr{transition:background .15s ease}.who-table tbody tr:hover{background:#f8fafc}.who-td-age{text-align:left!important;padding-left:20px!important}.who-age-label{display:block;font-weight:600;color:var(--text);font-size:13px}.who-th-title{display:block}.who-th-unit{display:block;margin-top:2px;font-size:10.5px;font-weight:600;color:var(--muted);line-height:1.15}.who-age-weeks{display:block;font-size:11px;color:var(--muted-2);margin-top:1px}.who-td-value{font-variant-numeric:tabular-nums;font-weight:500}.who-td-merged{text-align:left!important;min-width:120px}.who-range-line{display:flex;align-items:baseline;justify-content:center;gap:8px;line-height:1.3;border-radius:8px;padding:3px 8px}.who-range-line strong{font-weight:700;color:var(--text)}.who-range-line+.who-range-line{margin-top:2px}.who-range-line--min{background:#f59e0b24}.who-range-line--median{background:#22c55e24}.who-range-line--median strong{color:#16a34a}.who-range-line--max{background:#3b82f624}.who-td-min{color:#f59e0b}.who-td-median{color:#16a34a;font-weight:600;background:#f0fdf4}.who-td-max{color:#3b82f6}.who-td-current{background:#eef2ff}.who-td-current strong{color:var(--primary);font-weight:700;font-size:14px}.who-td-current .who-current-unit{color:var(--muted);font-size:11px;margin-left:2px}.who-td-current--stack{min-width:92px}.who-current-stack{display:flex;flex-direction:column;gap:2px}.who-current-row{display:flex;align-items:baseline;justify-content:center;gap:2px;border-radius:999px;border:1px solid transparent;padding:2px 8px}.who-current-row[data-level=ok]{background:#22c55e2e;border-color:#16a34a61}.who-current-row[data-level=ok] strong{color:#166534}.who-current-row[data-level=mid]{background:#f59e0b33;border-color:#b4530959}.who-current-row[data-level=mid] strong{color:#9a3412}.who-current-row[data-level=warn]{background:#ef44442e;border-color:#dc262659}.who-current-row[data-level=warn] strong{color:#b91c1c}.who-row-current{background:linear-gradient(90deg,#fef3c7,#fffbeb)!important}.who-row-current td:first-child{border-left:4px solid #f59e0b;padding-left:16px!important}.who-row-current .who-age-label{color:#92400e}.who-row-current .who-td-median,.who-row-current .who-td-current{background:#fef3c7}.who-row-current .who-td-current strong{color:#92400e}.who-row-current:hover{background:linear-gradient(90deg,#fde68a,#fef3c7)!important}.who-table-footer{padding:14px 20px;background:#f8fafc;border-top:1px solid var(--border)}@media(max-width:640px){.who-table-footer{padding:12px 14px}}.who-footer-note{display:flex;align-items:flex-start;gap:8px;margin:0;font-size:11px;color:var(--muted);line-height:1.5}.who-footer-note i{color:var(--primary);margin-top:2px;flex-shrink:0}.who-footer-note--muted{margin-top:6px;font-size:10.5px;opacity:.9}.weight-screen-tabs{display:flex;gap:0;margin:0 0 10px;border-bottom:1px solid var(--border);background:var(--surface);border-radius:10px 10px 0 0;overflow:hidden}.weight-screen-tabs__btn{flex:1;display:inline-flex;flex-direction:row;align-items:center;justify-content:center;gap:4px;padding:8px 4px 7px;border:none;border-bottom:2px solid transparent;background:transparent;color:var(--muted);font-size:10px;font-weight:700;font-family:inherit;cursor:pointer;white-space:nowrap}.weight-screen-tabs__btn i{font-size:11px}.weight-screen-tabs__btn.active{color:var(--primary);border-bottom-color:var(--primary)}.weight-panel{display:flex;flex-direction:column;gap:10px}.weight-snapshot{border:1px solid var(--border);background:var(--surface);border-radius:0 0 10px 10px;overflow:hidden}.weight-snapshot__head{display:flex;align-items:center;justify-content:space-between;gap:8px;padding:8px 10px;border-bottom:1px solid var(--border)}.weight-snapshot__title{margin:0;font-size:11px;font-weight:700;color:var(--text)}.weight-snapshot__growth{display:flex;align-items:center;gap:8px;padding:7px 10px;background:var(--bg);border-bottom:1px solid var(--border)}.weight-snapshot__growth-label{font-size:10px;font-weight:600;color:var(--muted);white-space:nowrap}.weight-snapshot__grid{display:grid;grid-template-columns:1fr 1fr 1fr}.weight-metric{padding:8px 6px;min-width:0}.weight-metric:not(:last-child){border-right:1px solid var(--border)}.weight-metric__label{display:flex;align-items:center;gap:4px;font-size:9px;font-weight:700;color:var(--muted);white-space:nowrap}.weight-metric__label i{font-size:10px}.weight-metric__value{margin-top:5px;font-size:18px;font-weight:800;line-height:1;color:var(--text);font-variant-numeric:tabular-nums;white-space:nowrap}.weight-metric__unit{font-size:10px;font-weight:600;color:var(--muted);margin-left:2px}.weight-metric__delta{margin-top:5px;font-size:9px;font-weight:700;color:var(--muted-2);white-space:nowrap;font-variant-numeric:tabular-nums}.weight-metric__delta--up{color:#047857}.weight-metric__delta--down{color:#dc2626}.weight-metric__tag{margin-left:3px;font-size:8px;font-weight:700;color:var(--muted-2)}.wg-toggle{display:inline-flex;gap:2px;padding:2px;border-radius:8px;border:1px solid color-mix(in srgb,var(--primary) 30%,var(--border-strong));background:color-mix(in srgb,var(--primary-soft) 65%,var(--bg))}.wg-toggle--growth{border-color:color-mix(in srgb,var(--primary) 38%,var(--border-strong));background:color-mix(in srgb,var(--primary-soft) 80%,#fff)}.wg-toggle__btn{border:none;border-radius:6px;padding:4px 9px;font-size:10px;font-weight:700;font-family:inherit;cursor:pointer;background:transparent;color:#64748b;transition:background .12s ease,color .12s ease,box-shadow .12s ease}.wg-toggle__btn:hover:not(.active){color:var(--text);background:#ffffffb8}.wg-toggle__btn.active{background:var(--primary-2);color:#fff;box-shadow:0 1px 4px #4c5cf066}.wg-toggle__btn--icon{display:inline-flex;align-items:center;gap:4px;padding:4px 8px;font-size:9px;white-space:nowrap}.wg-toggle__btn--icon i{font-size:10px}.wg-toggle__btn--icon.active i{color:#fff}.who-ref-panel{padding:0 0 8px}.who-ref-panel__head{margin-bottom:8px}.who-ref-panel__head h2{margin:0;font-size:15px;font-weight:700;color:var(--text);display:flex;align-items:center;gap:6px}.who-ref-panel__head p{margin:2px 0 0;font-size:11px;color:var(--muted)}.who-table-scroll--inline{max-height:360px;border-radius:10px;border:1px solid var(--border)}.who-table--inline{table-layout:fixed}.who-table--inline .who-th-age{width:68px}.who-table--inline .who-th-icon{width:46px;text-align:center;font-size:10px;font-weight:700;color:var(--muted)}.who-table--inline .who-th-icon i{display:block;font-size:12px;color:var(--primary);margin-bottom:2px}.who-table--inline .who-th-current{width:76px}.who-legend-item{display:inline-flex;align-items:flex-start;gap:6px}.who-legend-item small{display:block;font-size:9px;font-weight:600;color:var(--muted);margin-top:1px}.who-footer-note--inline{margin:8px 0 0;font-size:10px}\n", "@charset \"UTF-8\";.timeline-cta{appearance:none;display:flex;align-items:center;gap:12px;width:100%;padding:12px 14px;background:linear-gradient(135deg,#fff,#fbfaff);border:1px solid var(--border);border-radius:12px;cursor:pointer;text-align:left;color:var(--text);transition:border-color .18s ease,box-shadow .18s ease,transform .18s ease}.timeline-cta:hover{border-color:#c4b5fd;box-shadow:0 6px 18px -8px #8b5cf666;transform:translateY(-1px)}.timeline-cta:active{transform:translateY(0)}.timeline-cta .tcta-emoji{flex-shrink:0;width:42px;height:42px;display:flex;align-items:center;justify-content:center;font-size:22px;line-height:1;background:linear-gradient(135deg,#f5f3ff,#fdf2f8);border-radius:10px;border:1px solid #ede9fe}.timeline-cta .tcta-body{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1}.timeline-cta .tcta-label{display:inline-flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:#7c3aed;letter-spacing:.5px;text-transform:uppercase}.timeline-cta .tcta-label i{font-size:10px}.timeline-cta .tcta-now{font-size:13px;font-weight:600;color:var(--text);white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.timeline-cta .tcta-progress{flex-shrink:0;padding:3px 8px;background:#ede9fe;color:#6d28d9;border-radius:999px;font-size:11px;font-weight:700;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;line-height:1.4}.timeline-cta>i{flex-shrink:0;color:var(--muted-2);font-size:13px;transition:color .18s ease,transform .18s ease}.timeline-cta:hover>i{color:#7c3aed;transform:translate(2px)}@media(max-width:640px){.timeline-cta{padding:10px 12px;gap:10px}.timeline-cta .tcta-emoji{width:38px;height:38px;font-size:20px}.timeline-cta .tcta-progress{display:none}}.timeline-dialog .dialog-head .eyebrow{display:inline-flex;align-items:center;gap:6px}.timeline-dialog .dialog-head .eyebrow i{font-size:11px}.timeline-dialog .dialog-lead{margin:6px 0 0;color:var(--muted);font-size:13px;line-height:1.5}.timeline-dialog .dialog-lead .tl-head-stat{color:#7c3aed;font-weight:600}.tl-legend{display:flex;align-items:center;gap:14px;flex-wrap:wrap;padding:10px 20px;background:#fafbfc;border-bottom:1px solid var(--border);font-size:12px;color:var(--muted)}.tl-legend>span:not(.sep):not(.tl-legend-note){display:inline-flex;align-items:center;gap:6px}.tl-legend .sep{color:var(--border-strong)}.tl-legend .tl-legend-note{color:var(--muted-2);font-style:italic;margin-left:auto}@media(max-width:640px){.tl-legend .tl-legend-note{margin-left:0;flex-basis:100%;font-size:11px}}@media(max-width:640px){.tl-legend{gap:10px;padding:10px 14px}}.tl-dot{display:inline-block;width:10px;height:10px;border-radius:50%}.tl-dot.past{background:#cbd5e1}.tl-dot.current{background:#8b5cf6;box-shadow:0 0 0 3px #8b5cf633}.tl-dot.future{background:#fff;border:1.5px solid #cbd5e1}.tl-body{padding:18px 20px 8px}@media(max-width:640px){.tl-body{padding:12px 12px 6px}}.tl-axis{display:none}.tl-item{display:grid;grid-template-columns:56px 1fr;gap:14px;position:relative;padding-bottom:20px}@media(max-width:640px){.tl-item{grid-template-columns:44px 1fr;gap:10px;padding-bottom:14px}}.tl-item:before{content:\"\";position:absolute;left:27px;top:56px;bottom:0;width:2px;background:#e5e7eb}@media(max-width:640px){.tl-item:before{left:21px;top:44px}}.tl-item:last-child:before{display:none}.tl-item[data-accent=rose]{--tl-accent: #f43f5e;--tl-accent-soft: #fff1f2;--tl-accent-dim: #fecdd3}.tl-item[data-accent=peach]{--tl-accent: #fb923c;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=coral]{--tl-accent: #f97316;--tl-accent-soft: #fff7ed;--tl-accent-dim: #fed7aa}.tl-item[data-accent=amber]{--tl-accent: #d97706;--tl-accent-soft: #fffbeb;--tl-accent-dim: #fde68a}.tl-item[data-accent=mint]{--tl-accent: #10b981;--tl-accent-soft: #ecfdf5;--tl-accent-dim: #a7f3d0}.tl-item[data-accent=sky]{--tl-accent: #0ea5e9;--tl-accent-soft: #f0f9ff;--tl-accent-dim: #bae6fd}.tl-item[data-accent=lavender]{--tl-accent: #8b5cf6;--tl-accent-soft: #f5f3ff;--tl-accent-dim: #ddd6fe}.tl-item[data-accent=plum]{--tl-accent: #c026d3;--tl-accent-soft: #fdf4ff;--tl-accent-dim: #f5d0fe}.tl-item[data-state=past] .tl-card{background:#fafbfc;border-color:#e5e7eb}.tl-item[data-state=past] .tl-tagline,.tl-item[data-state=past] .tl-period-label{color:var(--muted)}.tl-item[data-state=past] .tl-marker{background:#f3f4f6;border-color:#d1d5db}.tl-item[data-state=past] .tl-marker .tl-hero-emoji{filter:grayscale(.35) opacity(.8)}.tl-item[data-state=current] .tl-card{background:var(--tl-accent-soft, #f5f3ff);border-color:var(--tl-accent, #8b5cf6);box-shadow:0 4px 16px -6px color-mix(in srgb,var(--tl-accent) 30%,transparent)}.tl-item[data-state=current] .tl-marker{background:var(--tl-accent);border-color:var(--tl-accent)}.tl-item[data-state=current]:before{background:linear-gradient(180deg,var(--tl-accent) 0%,#e5e7eb 100%)}.tl-item[data-state=future] .tl-card{background:#fff}.tl-marker{position:relative;width:56px;height:56px;border-radius:50%;background:#fff;border:2.5px solid var(--tl-accent, #cbd5e1);display:flex;align-items:center;justify-content:center;transition:all .2s ease;z-index:2}@media(max-width:640px){.tl-marker{width:44px;height:44px;border-width:2px}}.tl-marker .tl-hero-emoji{font-size:26px;line-height:1}@media(max-width:640px){.tl-marker .tl-hero-emoji{font-size:20px}}.tl-marker .tl-check{position:absolute;bottom:-2px;right:-2px;width:20px;height:20px;background:#10b981;color:#fff;border:2px solid #ffffff;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:10px}@media(max-width:640px){.tl-marker .tl-check{width:16px;height:16px;font-size:8px;border-width:1.5px}}.tl-marker .tl-now-pulse{position:absolute;inset:-5px;border-radius:50%;border:2px solid var(--tl-accent);animation:tl-pulse 2.2s ease-in-out infinite;pointer-events:none}@keyframes tl-pulse{0%,to{transform:scale(1);opacity:.5}50%{transform:scale(1.25);opacity:0}}.tl-card{min-width:0;background:#fff;border:1px solid var(--border);border-radius:12px;padding:14px 14px 12px;transition:background .2s ease,border-color .2s ease,box-shadow .2s ease}@media(max-width:640px){.tl-card{padding:12px 12px 10px;border-radius:10px}}.tl-card-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;flex-wrap:wrap;margin-bottom:10px}.tl-period{display:flex;flex-direction:column;gap:2px;min-width:0;flex:1 1 auto}.tl-period-label{font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.6px}.tl-tagline{font-size:14.5px;font-weight:700;color:var(--text);line-height:1.3;word-break:break-word}.tl-mood-badge{display:inline-flex;align-items:center;gap:5px;padding:4px 9px;border-radius:6px;font-size:11px;font-weight:600;line-height:1.3;white-space:nowrap;flex-shrink:0;max-width:100%;overflow:hidden;text-overflow:ellipsis}.tl-mood-badge i{font-size:9px;flex-shrink:0}@media(max-width:640px){.tl-mood-badge{font-size:10.5px;padding:3px 8px}}.tl-mood-badge[data-mood=happy]{background:#dcfce7;color:#15803d}.tl-mood-badge[data-mood=milestone]{background:#d1fae5;color:#047857}.tl-mood-badge[data-mood=calm]{background:#fef9c3;color:#854d0e}.tl-mood-badge[data-mood=growth]{background:#ffedd5;color:#c2410c}.tl-mood-badge[data-mood=leap]{background:#fee2e2;color:#b91c1c}.tl-stomach{display:grid;grid-template-columns:52px 1fr;align-items:center;gap:12px;padding:10px 12px;background:#fafbfc;border:1px solid var(--border);border-radius:10px;margin-bottom:10px}.tl-stomach-visual{position:relative;width:52px;height:52px;display:flex;align-items:center;justify-content:center;flex-shrink:0}.tl-stomach-circle{position:absolute;top:50%;left:50%;width:44px;height:44px;margin:-22px 0 0 -22px;border-radius:50%;background:color-mix(in srgb,var(--tl-accent, #8b5cf6) 15%,#ffffff);border:1.5px dashed color-mix(in srgb,var(--tl-accent, #8b5cf6) 40%,transparent)}.tl-stomach-emoji{position:relative;font-size:26px;line-height:1;z-index:1;filter:drop-shadow(0 1px 2px rgba(0,0,0,.1))}.tl-stomach-text{min-width:0}.tl-stomach-title{display:flex;align-items:center;gap:6px;font-size:12.5px;font-weight:600;color:var(--text);line-height:1.4}.tl-stomach-title i{color:var(--tl-accent, var(--muted));font-size:12px;flex-shrink:0}.tl-stomach-vol{font-size:11.5px;color:var(--muted);margin-top:2px;font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;font-weight:600}.tl-now-banner{display:flex;align-items:center;gap:7px;padding:8px 12px;background:linear-gradient(135deg,#8b5cf6,#ec4899);color:#fff;border-radius:8px;font-size:12.5px;font-weight:600;margin-bottom:10px}.tl-now-banner i{font-size:13px}.tl-skills{display:flex;flex-wrap:wrap;gap:6px;align-items:center;margin-bottom:10px}.tl-skills-label{display:inline-flex;align-items:center;gap:4px;font-size:11px;font-weight:600;color:var(--muted)}.tl-skills-label i{color:var(--tl-accent);font-size:10px}.tl-skill-chip{padding:3px 9px;background:var(--tl-accent-soft, #f3f4f6);color:var(--tl-accent, var(--text));border:1px solid color-mix(in srgb,var(--tl-accent, #cbd5e1) 35%,transparent);border-radius:999px;font-size:11px;font-weight:600;line-height:1.3}.tl-section{margin-top:12px}.tl-section:first-of-type{margin-top:8px}.tl-section-label{display:flex;align-items:center;gap:5px;font-size:10.5px;font-weight:700;color:var(--tl-accent, var(--muted));text-transform:uppercase;letter-spacing:.5px;margin-bottom:6px}.tl-section-label i{font-size:10px}.tl-list{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:5px}.tl-list li{font-size:12.5px;line-height:1.55;color:var(--text);padding-left:14px;position:relative}.tl-list li:before{content:\"\";position:absolute;left:3px;top:8px;width:5px;height:5px;border-radius:50%;background:var(--tl-accent, var(--muted-2))}.tl-section-notes{background:color-mix(in srgb,var(--tl-accent, #f59e0b) 7%,#ffffff);padding:10px 12px;border-radius:0;border-left:3px solid var(--tl-accent, #f59e0b);margin-top:12px}.tl-section-notes .tl-section-label{margin-bottom:6px}.tl-section-notes .tl-list li:before{background:var(--tl-accent, #f59e0b)}.tl-care-grid{display:grid;grid-template-columns:1fr;gap:10px;margin-top:12px}.tl-section-care{margin-top:0;padding:10px 12px;border-radius:12px;border:1px solid var(--border);background:#fff}.tl-section-care .tl-section-label{margin-bottom:7px}.tl-section-care--todo{background:color-mix(in srgb,var(--mint, #10b981) 8%,#ffffff);border-color:color-mix(in srgb,var(--mint, #10b981) 24%,var(--border))}.tl-section-care--todo .tl-section-label{color:#047857}.tl-section-care--todo .tl-list li:before{background:#047857}.tl-section-care--avoid{background:color-mix(in srgb,var(--rose, #ef4444) 7%,#ffffff);border-color:color-mix(in srgb,var(--rose, #ef4444) 24%,var(--border))}.tl-section-care--avoid .tl-section-label{color:#b91c1c}.tl-section-care--avoid .tl-list li:before{background:#ef4444}.tl-section-care--watch{background:color-mix(in srgb,var(--amber, #f59e0b) 9%,#ffffff);border-color:color-mix(in srgb,var(--amber, #f59e0b) 28%,var(--border))}.tl-section-care--watch .tl-section-label{color:#b45309}.tl-section-care--watch .tl-list li:before{background:#f59e0b}.tl-sources{display:flex;gap:6px;padding:10px 20px 14px;font-size:11px;color:var(--muted-2);line-height:1.5;border-top:1px solid var(--border);background:#fafbfc}.tl-sources i{color:var(--muted);font-size:12px;margin-top:1px;flex-shrink:0}@media(max-width:640px){.tl-sources{padding:10px 14px 12px}}.tl-tabs{display:flex;gap:4px;padding:10px 20px 0;background:#fff;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-tabs{padding:8px 12px 0}}.tl-tab{appearance:none;background:transparent;border:none;border-bottom:2px solid transparent;padding:9px 14px 10px;cursor:pointer;display:inline-flex;align-items:center;gap:7px;font-size:13px;font-weight:600;color:var(--muted);transition:color .15s ease,border-color .15s ease,background .15s ease;border-radius:8px 8px 0 0}.tl-tab i{font-size:12px}.tl-tab:hover{color:var(--text);background:#fafbfc}.tl-tab.active{color:#7c3aed;border-bottom-color:#7c3aed;background:#f5f3ff}@media(max-width:640px){.tl-tab{padding:8px 10px 9px;font-size:12.5px;gap:5px}.tl-tab i{font-size:11px}}.tl-chart-view{padding:14px 20px 20px}@media(max-width:640px){.tl-chart-view{padding:10px 12px 14px}}.tl-chart-mood-legend{display:flex;flex-wrap:wrap;gap:6px;margin-bottom:12px;padding:8px 10px;background:#fafbfc;border:1px solid var(--border);border-radius:8px}.tl-chart-scroll{overflow-x:auto;overflow-y:hidden;background:#fff;border:1px solid var(--border);border-radius:10px;padding:4px;scrollbar-width:thin}.tl-chart-scroll::-webkit-scrollbar{height:8px}.tl-chart-scroll::-webkit-scrollbar-track{background:#f3f4f6;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb{background:#c4b5fd;border-radius:4px}.tl-chart-scroll::-webkit-scrollbar-thumb:hover{background:#8b5cf6}.tl-chart-svg{display:block;min-width:100%}.tl-chart-guide-label{font-size:10px;fill:var(--muted-2);font-weight:700;font-family:inherit}.tl-chart-guide-label[data-mood=happy]{fill:#15803d}.tl-chart-guide-label[data-mood=calm]{fill:#a16207}.tl-chart-guide-label[data-mood=growth]{fill:#c2410c}.tl-chart-guide-label[data-mood=leap]{fill:#b91c1c}.tl-chart-x-label{font-size:10.5px;fill:var(--muted);font-weight:600;font-family:inherit}.tl-chart-x-label.current{fill:#7c3aed;font-weight:700}.tl-chart-x-label.past{fill:var(--muted-2)}.tl-chart-node{cursor:pointer;transition:transform .15s ease}.tl-chart-node[data-mood=happy]{--tl-mood: #22c55e;--tl-mood-soft: #dcfce7;color:#22c55e}.tl-chart-node[data-mood=milestone]{--tl-mood: #10b981;--tl-mood-soft: #d1fae5;color:#10b981}.tl-chart-node[data-mood=calm]{--tl-mood: #eab308;--tl-mood-soft: #fef9c3;color:#eab308}.tl-chart-node[data-mood=growth]{--tl-mood: #f97316;--tl-mood-soft: #ffedd5;color:#f97316}.tl-chart-node[data-mood=leap]{--tl-mood: #ef4444;--tl-mood-soft: #fee2e2;color:#ef4444}.tl-chart-node .tl-chart-dot{fill:#fff;stroke:var(--tl-mood, #cbd5e1);stroke-width:2.5;transition:r .15s ease,stroke-width .15s ease}.tl-chart-node .tl-chart-emoji{font-size:14px;pointer-events:none;-webkit-user-select:none;user-select:none}.tl-chart-node:hover .tl-chart-dot{r:17;stroke-width:3}.tl-chart-node[data-state=past] .tl-chart-dot{fill:#f3f4f6;stroke:#cbd5e1}.tl-chart-node[data-state=past] .tl-chart-emoji{opacity:.6}.tl-chart-node[data-state=current] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current] .tl-chart-emoji{filter:drop-shadow(0 1px 1px rgba(0,0,0,.3))}.tl-chart-node[data-selected=true] .tl-chart-dot{r:17;stroke-width:3;fill:var(--tl-mood-soft, #f5f3ff);stroke:var(--tl-mood, #8b5cf6)}.tl-chart-node[data-state=current][data-selected=true] .tl-chart-dot{fill:var(--tl-mood, #8b5cf6)}.tl-chart-pulse{transform-origin:center;transform-box:fill-box;animation:tl-chart-pulse 2s ease-in-out infinite}@keyframes tl-chart-pulse{0%,to{opacity:.5;r:20}50%{opacity:0;r:28}}.tl-chart-detail{margin-top:16px;animation:tl-detail-fade .25s ease}.tl-item--detail{padding-bottom:0!important}.tl-item--detail:before{display:none!important}@keyframes tl-detail-fade{0%{opacity:0;transform:translateY(4px)}to{opacity:1;transform:translateY(0)}}.timeline-dialog{padding:0!important;overflow:hidden!important;display:flex;flex-direction:column}.timeline-dialog .dialog-head{position:sticky;top:0;z-index:20;margin:0;padding:16px 20px 12px;background:#fff;border-bottom:1px solid var(--border);box-shadow:0 2px 8px -4px #0f172a0f}@media(max-width:640px){.timeline-dialog .dialog-head{padding:14px 14px 10px}}.timeline-dialog .tl-tabs{position:sticky;top:0;z-index:19;background:#fff;padding:6px 20px 0;border-top:none}@media(max-width:640px){.timeline-dialog .tl-tabs{padding:4px 12px 0}}.timeline-dialog .tl-chart-view,.timeline-dialog .tl-nav-view{flex:1;overflow-y:auto}.timeline-dialog .tl-sources{flex-shrink:0;position:sticky;bottom:0;background:#fff;z-index:18;border-top:1px solid var(--border)}.tl-nav-view{display:flex;flex-direction:column}.tl-nav-bar{display:grid;grid-template-columns:1fr auto 1fr;gap:8px;padding:12px 16px;background:#fafbfc;border-bottom:1px solid var(--border);align-items:center}@media(max-width:640px){.tl-nav-bar{padding:10px;gap:6px}}.tl-nav-btn{appearance:none;display:inline-flex;align-items:center;gap:6px;padding:8px 12px;background:#fff;border:1px solid var(--border);border-radius:8px;color:var(--text);font-size:12.5px;font-weight:600;cursor:pointer;transition:all .15s ease;min-width:0;justify-self:start}.tl-nav-btn i{font-size:12px;color:var(--muted);flex-shrink:0}.tl-nav-btn .tl-nav-btn-label{white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:110px}@media(max-width:640px){.tl-nav-btn .tl-nav-btn-label{display:none}}.tl-nav-btn:hover:not(:disabled){border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff;transform:translateY(-1px)}.tl-nav-btn:hover:not(:disabled) i{color:#7c3aed}.tl-nav-btn:disabled{opacity:.4;cursor:not-allowed}.tl-nav-btn--right{justify-self:end;flex-direction:row}@media(max-width:640px){.tl-nav-btn{padding:8px 10px}}.tl-nav-position{display:inline-flex;align-items:center;gap:8px;justify-self:center}.tl-nav-today{appearance:none;display:inline-flex;align-items:center;gap:5px;padding:6px 10px;background:#fff;border:1px solid var(--border);border-radius:999px;color:var(--muted);font-size:11.5px;font-weight:600;cursor:pointer;transition:all .15s ease}.tl-nav-today i{font-size:10px}.tl-nav-today:hover{border-color:#c4b5fd;color:#7c3aed;background:#f5f3ff}.tl-nav-today.active{background:linear-gradient(135deg,#8b5cf6,#ec4899);border-color:transparent;color:#fff}.tl-nav-today.active:hover{color:#fff}.tl-nav-count{font-size:12px;font-weight:700;color:var(--muted);font-family:ui-monospace,SF Mono,Menlo,Consolas,monospace;padding:4px 9px;background:#fff;border:1px solid var(--border);border-radius:6px;white-space:nowrap}.tl-progress-rail{display:flex;align-items:center;gap:3px;padding:10px 16px 2px;overflow-x:auto;scrollbar-width:none}.tl-progress-rail::-webkit-scrollbar{display:none}@media(max-width:640px){.tl-progress-rail{padding:8px 10px 2px}}.tl-rail-dot{appearance:none;flex:1 1 0;min-width:8px;max-width:30px;height:6px;background:#e5e7eb;border:none;border-radius:3px;padding:0;cursor:pointer;transition:background .15s ease,transform .15s ease,height .15s ease}.tl-rail-dot[data-state=past]{background:#cbd5e1}.tl-rail-dot[data-state=current]{background:linear-gradient(90deg,#8b5cf6,#ec4899)}.tl-rail-dot[data-state=future]{background:#e5e7eb}.tl-rail-dot:hover{transform:scaleY(1.6)}.tl-rail-dot[data-selected=true]{height:10px;background:#7c3aed!important;box-shadow:0 0 0 2px #fff,0 0 0 3px #c4b5fd}.tl-nav-body{padding:16px 20px 12px}@media(max-width:640px){.tl-nav-body{padding:12px 12px 8px}}.tl-item--nav,.tl-item--detail{display:block!important;padding-bottom:0!important}.tl-item--nav:before,.tl-item--detail:before{display:none!important}.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{display:flex;align-items:center;gap:12px;flex-wrap:wrap;margin-bottom:12px;padding-bottom:12px;border-bottom:1px solid var(--border)}@media(max-width:640px){.tl-item--nav .tl-card-head,.tl-item--detail .tl-card-head{gap:10px}}.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:48px;height:48px;border-width:2px;flex-shrink:0}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:22px}.tl-item--nav .tl-marker .tl-check,.tl-item--detail .tl-marker .tl-check{width:18px;height:18px;font-size:9px;bottom:-2px;right:-2px;border-width:2px}@media(max-width:640px){.tl-item--nav .tl-marker,.tl-item--detail .tl-marker{width:42px;height:42px}.tl-item--nav .tl-marker .tl-hero-emoji,.tl-item--detail .tl-marker .tl-hero-emoji{font-size:20px}}.tl-item--nav .tl-period,.tl-item--detail .tl-period{flex:1 1 120px;min-width:0}.tl-item--nav .tl-mood-badge,.tl-item--detail .tl-mood-badge{margin-left:auto}.tl-item--nav .tl-card,.tl-item--detail .tl-card{animation:tl-card-fade .3s ease;padding:14px}@media(max-width:640px){.tl-item--nav .tl-card,.tl-item--detail .tl-card{padding:12px}}@keyframes tl-card-fade{0%{opacity:0;transform:translate(6px)}to{opacity:1;transform:translate(0)}}.tl-chart-view{padding-top:14px}@media(max-width:640px){.tl-chart-view{padding-top:10px}}.timeline-dialog .dialog-head{align-items:center}.timeline-dialog .dialog-head h2{font-size:17px;margin:2px 0 0}.timeline-dialog .dialog-head .eyebrow{font-size:11px}.timeline-dialog .dialog-head .dialog-lead{margin-top:4px;font-size:12px}\n"] }]
13765
14150
  }], ctorParameters: () => [], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], birthDate: [{ type: i0.Input, args: [{ isSignal: true, alias: "birthDate", required: false }] }], gender: [{ type: i0.Input, args: [{ isSignal: true, alias: "gender", required: false }] }], babyName: [{ type: i0.Input, args: [{ isSignal: true, alias: "babyName", required: false }] }], onTimelineKey: [{
13766
14151
  type: HostListener,
13767
14152
  args: ['document:keydown', ['$event']]
@@ -16139,11 +16524,11 @@ class FeedingScheduleComponent {
16139
16524
  this.editDraft.update((d) => ({ ...d, ...partial }));
16140
16525
  }
16141
16526
  static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FeedingScheduleComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
16142
- static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: FeedingScheduleComponent, isStandalone: true, selector: "app-feeding-schedule", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "window:scroll": "onPlaceMenuViewportChange()", "window:resize": "onPlaceMenuViewportChange()" } }, ngImport: i0, template: "<div class=\"schedule-page\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n\n <div class=\"schedule-topbar\">\n <button type=\"button\" class=\"btn btn-primary sm\" (click)=\"openCalendarDialog()\">\n <i class=\"pi pi-calendar\"></i>\n L\u1ECBch th\u00E1ng\n </button>\n </div>\n\n @if (!loading() || hasFutureEvents()) {\n @for (section of futureSections(); track trackSection($index, section)) {\n <section\n class=\"schedule-section\"\n [attr.data-section]=\"section.id\"\n [hidden]=\"section.eventCount === 0\"\n >\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"section.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ section.title }}</h2>\n <p class=\"schedule-section__sub\">{{ section.subtitle }}</p>\n </div>\n @if (section.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(section); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: false }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(section)) {\n <button\n type=\"button\"\n class=\"schedule-view-more\"\n (click)=\"toggleSectionExpand(section.id)\"\n >\n @if (!isSectionExpanded(section.id)) {\n Xem th\u00EAm {{ hiddenEventCount(section) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(section.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n }\n\n @if (!loading() && !hasFutureEvents()) {\n <section class=\"schedule-empty\">\n <i class=\"pi pi-calendar-plus\"></i>\n <p>Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n s\u1EAFp t\u1EDBi.</p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </section>\n }\n\n @if (pastSection(); as past) {\n <section class=\"schedule-section schedule-section--past\">\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"past.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ past.title }}</h2>\n @if (past.subtitle) {\n <p class=\"schedule-section__sub\">{{ past.subtitle }}</p>\n }\n </div>\n @if (past.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(past); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: true }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(past)) {\n <button type=\"button\" class=\"schedule-view-more\" (click)=\"toggleSectionExpand(past.id)\">\n @if (!isSectionExpanded(past.id)) {\n Xem th\u00EAm {{ hiddenEventCount(past) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(past.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n\n <ng-template #eventCardTpl let-ev let-isPast=\"isPast\">\n <article\n class=\"ev-card\"\n [class.ev-card--past]=\"isPast\"\n [attr.data-type]=\"getEventTypeClass(ev)\"\n >\n <div class=\"ev-card__time-col\">\n <span class=\"ev-card__hour\">{{ formatTime(ev.time) }}</span>\n </div>\n <div class=\"ev-card__accent-bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"ev-card__body\">\n <h3 class=\"ev-card__title\">{{ ev.title }}</h3>\n @if (ev.place) {\n <p class=\"ev-card__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n <span>{{ ev.place }}</span>\n </p>\n }\n @if (ev.note) {\n <div class=\"ev-card__content-wrap\">\n <span class=\"ev-card__content-label\">N\u1ED9i dung</span>\n <p class=\"ev-card__content\">{{ ev.note }}</p>\n </div>\n }\n </div>\n @if (!isPast && ev.rowIndex) {\n <div class=\"ev-card__actions\">\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only\"\n (click)=\"openEdit(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"S\u1EEDa\"\n aria-label=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only danger\"\n (click)=\"deleteEv(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"Xo\u00E1\"\n aria-label=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n }\n </article>\n </ng-template>\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAdd()\"\n aria-label=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n title=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span class=\"fab-label\">Th\u00EAm s\u1EF1 ki\u1EC7n</span>\n </button>\n\n @if (calendarDialogOpen()) {\n <div class=\"calendar-sheet-backdrop\" (click)=\"closeCalendarDialog()\">\n <div\n class=\"calendar-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Drag handle -->\n <div class=\"calendar-sheet__handle\"></div>\n\n <!-- Header -->\n <div class=\"calendar-sheet__header\">\n <div class=\"calendar-sheet__header-left\">\n <div class=\"calendar-sheet__icon\">\n <i class=\"pi pi-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"calendar-sheet__title\">L\u1ECBch th\u00E1ng</span>\n </div>\n <button\n type=\"button\"\n class=\"calendar-sheet__close\"\n (click)=\"closeCalendarDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <!-- Month nav + grid (swipeable) -->\n <div\n class=\"calendar-sheet__grid-area\"\n (touchstart)=\"onCalendarTouchStart($event)\"\n (touchmove)=\"onCalendarTouchMove($event)\"\n (touchend)=\"onCalendarTouchEnd()\"\n >\n <!-- Month / year nav row -->\n <div class=\"calendar-mnav\">\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarPrevMonth()\"\n aria-label=\"Th\u00E1ng tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n </button>\n\n <div\n class=\"calendar-mnav__center\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n <div class=\"calendar-mnav__selects\">\n <select\n class=\"calendar-mnav__select\"\n [ngModel]=\"calendarCursor().getMonth()\"\n (ngModelChange)=\"calendarChangeMonth(+$event)\"\n aria-label=\"Ch\u1ECDn th\u00E1ng\"\n >\n <option [ngValue]=\"0\">Th\u00E1ng 1</option>\n <option [ngValue]=\"1\">Th\u00E1ng 2</option>\n <option [ngValue]=\"2\">Th\u00E1ng 3</option>\n <option [ngValue]=\"3\">Th\u00E1ng 4</option>\n <option [ngValue]=\"4\">Th\u00E1ng 5</option>\n <option [ngValue]=\"5\">Th\u00E1ng 6</option>\n <option [ngValue]=\"6\">Th\u00E1ng 7</option>\n <option [ngValue]=\"7\">Th\u00E1ng 8</option>\n <option [ngValue]=\"8\">Th\u00E1ng 9</option>\n <option [ngValue]=\"9\">Th\u00E1ng 10</option>\n <option [ngValue]=\"10\">Th\u00E1ng 11</option>\n <option [ngValue]=\"11\">Th\u00E1ng 12</option>\n </select>\n <select\n class=\"calendar-mnav__select calendar-mnav__select--year\"\n [ngModel]=\"calendarCursor().getFullYear()\"\n (ngModelChange)=\"calendarChangeYear(+$event)\"\n aria-label=\"Ch\u1ECDn n\u0103m\"\n >\n @for (year of calendarYearOptions(); track year) {\n <option [ngValue]=\"year\">{{ year }}</option>\n }\n </select>\n @if (!isViewingCurrentMonth()) {\n <button\n type=\"button\"\n class=\"calendar-mnav__today\"\n (click)=\"calendarGoToToday()\"\n title=\"V\u1EC1 h\u00F4m nay\"\n >\n <i class=\"pi pi-home\"></i>\n <span>H\u00F4m nay</span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarNextMonth()\"\n aria-label=\"Th\u00E1ng sau\"\n >\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"calendar-wd-row\">\n @for (wd of calendarWeekdays; track wd; let i = $index) {\n <span [class.is-sat]=\"i === 5\" [class.is-sun]=\"i === 6\">{{ wd }}</span>\n }\n </div>\n\n <!-- Day cells -->\n <div\n class=\"calendar-day-grid\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n @for (c of monthCalendarCells(); track c.dateIso; let i = $index) {\n <button\n type=\"button\"\n class=\"cal-cell\"\n [class.is-out-month]=\"!c.inMonth\"\n [class.is-today]=\"c.isToday\"\n [class.is-selected]=\"selectedCalendarDate() === c.dateIso\"\n (click)=\"selectCalendarDate(c.dateIso)\"\n >\n <span\n class=\"cal-cell__day\"\n [class.is-sat]=\"i % 7 === 5\"\n [class.is-sun]=\"i % 7 === 6\"\n >\n {{ c.day }}\n </span>\n @if (c.eventCount > 0) {\n <span class=\"cal-cell__dots\">\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n @if (c.eventCount > 1) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n @if (c.eventCount > 2) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n </span>\n }\n </button>\n }\n </div>\n </div>\n\n <div class=\"calendar-sheet__divider\"></div>\n\n <!-- Events panel -->\n <div class=\"calendar-sheet__events\">\n <div class=\"calendar-sheet__events-head\">\n <div>\n <p class=\"calendar-sheet__events-date\">\n {{ formatEventDate(selectedCalendarDate()) }}\n </p>\n <p class=\"calendar-sheet__events-count\">\n @if (selectedCalendarEvents().length > 0) {\n {{ selectedCalendarEvents().length }} s\u1EF1 ki\u1EC7n\n } @else {\n Kh\u00F4ng c\u00F3 s\u1EF1 ki\u1EC7n\n }\n </p>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-primary sm\"\n (click)=\"openAddFromCalendar()\"\n [disabled]=\"!selectedCalendarDate()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm\n </button>\n </div>\n\n <div class=\"calendar-sheet__events-list\">\n @for (ev of selectedCalendarEvents(); track trackEvent($index, ev)) {\n <article class=\"cal-ev-row\" [attr.data-type]=\"getEventTypeClass(ev)\">\n <div class=\"cal-ev-row__time\">{{ ev.time }}</div>\n <div class=\"cal-ev-row__bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"cal-ev-row__body\">\n <p class=\"cal-ev-row__title\">{{ ev.title }}</p>\n @if (ev.place) {\n <p class=\"cal-ev-row__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n {{ ev.place }}\n </p>\n }\n </div>\n </article>\n } @empty {\n @if (selectedCalendarDate()) {\n <div class=\"cal-empty-day\">\n <i class=\"pi pi-calendar-plus cal-empty-day__icon\" aria-hidden=\"true\"></i>\n <p class=\"cal-empty-day__label\">Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n</p>\n <p class=\"cal-empty-day__sub\">{{ formatEventDate(selectedCalendarDate()) }}</p>\n <button\n type=\"button\"\n class=\"btn btn-primary sm cal-empty-day__btn\"\n (click)=\"openAddFromCalendar()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </div>\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n\n @if (addOpen()) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"closeAdd()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-plus-circle\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAdd()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (draft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"evd\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateDraft({ time: $event })\"\n name=\"evt\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn s\u1EF1 ki\u1EC7n *</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"VD: Ti\u00EAm ch\u1EE7ng 6in1\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"evti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateDraft({ note: $event })\"\n name=\"evn\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputAdd\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'add', placeInputAdd)\"\n (focus)=\"openPlacePicker('add', placeInputAdd)\"\n (blur)=\"onPlaceBlur('add')\"\n name=\"evp\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAdd()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving()\" (click)=\"submitAdd()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </div>\n </div>\n }\n\n @if (editing(); as ed) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"cancelEdit()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-pencil\"></i> S\u1EEDa s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (editDraft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateEditDraft({ date: $event })\"\n name=\"eed\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateEditDraft({ time: $event })\"\n name=\"eet\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn *</span>\n <input\n type=\"text\"\n class=\"input\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateEditDraft({ title: $event })\"\n name=\"eeti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateEditDraft({ note: $event })\"\n name=\"een\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputEdit\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'edit', placeInputEdit)\"\n (focus)=\"openPlacePicker('edit', placeInputEdit)\"\n (blur)=\"onPlaceBlur('edit')\"\n name=\"eep\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"saving()\"\n (click)=\"submitEdit()\"\n >\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n\n@if (placeMenuView(); as vm) {\n <ul\n class=\"place-combobox__menu place-combobox__menu--portal\"\n role=\"listbox\"\n [style.top.px]=\"vm.pos.top\"\n [style.left.px]=\"vm.pos.left\"\n [style.width.px]=\"vm.pos.width\"\n (mousedown)=\"$event.preventDefault()\"\n >\n @for (place of vm.places; track place) {\n <li>\n <button\n type=\"button\"\n class=\"place-combobox__option\"\n (mousedown)=\"selectPlace(place, vm.ctx)\"\n >\n {{ place }}\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;--surface: #ffffff;--border: #e6e8ef;--border-soft: #eef1f6;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--rose: #ef4444}.schedule-page{max-width:640px;margin:0 auto;padding:8px 16px calc(120px + env(safe-area-inset-bottom,0px))}.schedule-topbar{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin:4px 0 12px}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:12px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.schedule-section{margin-bottom:40px;padding:16px 14px 18px;border-radius:16px;border:1px solid transparent}.schedule-section[hidden]{display:none!important}.schedule-section[data-section=today]{background:linear-gradient(180deg,#ecfdf5,#f7fef9);border-color:#a7f3d0}.schedule-section[data-section=today] .schedule-section__head{border-bottom-color:#a7f3d0}.schedule-section[data-section=soon]{background:linear-gradient(180deg,#fffbeb,#fffdf7);border-color:#fde68a}.schedule-section[data-section=soon] .schedule-section__head{border-bottom-color:#fde68a}.schedule-section[data-section=nextMonth]{background:linear-gradient(180deg,#eff6ff,#f8fbff);border-color:#bfdbfe}.schedule-section[data-section=nextMonth] .schedule-section__head{border-bottom-color:#bfdbfe}.schedule-section[data-section=later]{background:linear-gradient(180deg,#f5f3ff,#faf9ff);border-color:#ddd6fe}.schedule-section[data-section=later] .schedule-section__head{border-bottom-color:#ddd6fe}.schedule-section[data-section=today] .schedule-section__icon{background:linear-gradient(135deg,#bbf7d0,#86efac);color:#15803d}.schedule-section[data-section=soon] .schedule-section__icon{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#b45309}.schedule-section[data-section=nextMonth] .schedule-section__icon{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1d4ed8}.schedule-section[data-section=later] .schedule-section__icon{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:#6d28d9}.schedule-section--past{background:linear-gradient(180deg,#f8fafc,#fcfdfe);border-color:#e2e8f0}.schedule-section--past .schedule-section__head{border-bottom-color:#e2e8f0}.schedule-section--past .schedule-section__icon{background:#f1f5f9;color:var(--muted)}.schedule-section__head{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding:0 2px 14px;border-bottom:2px solid var(--border-soft)}.schedule-section__icon{flex-shrink:0;width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:17px}.schedule-section__titles{flex:1;min-width:0}.schedule-section__titles h2{margin:0;font-size:17px;font-weight:800;color:var(--text);letter-spacing:-.02em}.schedule-section__sub{margin:2px 0 0;font-size:12.5px;color:var(--muted)}.schedule-section__count{flex-shrink:0;font-size:11px;font-weight:700;color:var(--muted);background:#ffffffb8;padding:4px 10px;border-radius:999px;border:1px solid rgba(15,23,42,.06)}.schedule-section__body{display:flex;flex-direction:column;gap:4px}.schedule-view-more{display:inline-flex;align-items:center;justify-content:center;gap:6px;width:100%;margin-top:12px;padding:11px 14px;border:1px dashed #c7d2fe;border-radius:12px;background:#ffffffa6;color:var(--primary);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.schedule-view-more:hover{background:#fff;border-color:var(--primary)}.schedule-days{display:flex;flex-direction:column;gap:24px}.schedule-day{display:flex;flex-direction:column;gap:10px}.schedule-day__date{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:0 2px 6px;border-bottom:2px solid var(--border-soft)}.schedule-day--relative .schedule-day__date{border-bottom-color:#f59e0b38}[data-section=today] .schedule-day--relative .schedule-day__date{border-bottom-color:#10b98140}[data-section=today] .schedule-day__label--relative{color:#047857}[data-section=soon] .schedule-day__label--relative{color:#b45309}.schedule-day__label{font-size:14px;font-weight:700;color:var(--text);line-height:1.45}.schedule-day__label--relative{font-size:15px;font-weight:800;color:#b45309;letter-spacing:-.02em}.schedule-section--past .schedule-day__label--relative{color:#64748b}.schedule-section[data-section=nextMonth] .schedule-day__label--relative{color:#1d4ed8}.schedule-section[data-section=later] .schedule-day__label--relative{color:#6d28d9}.schedule-day__sub{font-size:12px;font-weight:500;color:var(--muted);line-height:1.35}.schedule-day__cards{display:flex;flex-direction:column;gap:8px;width:100%}.ev-card{position:relative;display:flex;flex-direction:row;align-items:stretch;gap:0;padding:10px 12px;background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;transition:border-color .15s}.ev-card[data-type=vaccine]{border-color:#ddd6fe}.ev-card[data-type=checkup]{border-color:#bfdbfe}.ev-card[data-type=important]{border-color:#fecaca}.ev-card[data-type=general]{border-color:#a7f3d0}.ev-card--past{opacity:.68;background:#fafbfc}.ev-card__time-col{width:40px;flex-shrink:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.ev-card__hour{font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums;line-height:1.2;text-align:center}.ev-card__accent-bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch;margin-right:10px}.ev-card__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px;padding-right:44px}.ev-card__title{margin:0;font-size:13.5px;font-weight:700;color:var(--text);line-height:1.3}.ev-card__place{margin:0;display:flex;align-items:center;gap:4px;font-size:11.5px;color:var(--muted);line-height:1.4}.ev-card__place i{flex-shrink:0;font-size:11px;color:#94a3b8}.ev-card__content-wrap{margin-top:8px;padding-top:8px;border-top:1px solid var(--border-soft)}.ev-card__content-label{display:block;margin-bottom:3px;font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#94a3b8}.ev-card__content{margin:0;font-size:13px;color:#334155;line-height:1.5;white-space:pre-wrap}.ev-card--past .ev-card__content{color:var(--muted)}.ev-card__actions{position:absolute;top:8px;right:8px;display:flex;gap:2px}.schedule-empty{text-align:center;padding:40px 20px;color:var(--muted)}.schedule-empty i{font-size:40px;color:#cbd5e1;margin-bottom:12px;display:block}.schedule-empty p{margin:0 0 16px;font-size:14px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer}.btn:disabled{opacity:.5;cursor:not-allowed}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca;background:#fef2f2}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon-only{padding:8px;width:36px;height:36px}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:18px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;white-space:nowrap;cursor:pointer;box-shadow:0 8px 24px #5b6cff73}.fab i{flex-shrink:0;font-size:18px}.fab .fab-label{white-space:nowrap;line-height:1.2}@media(max-width:480px){.fab{padding:14px;gap:0}.fab .fab-label{display:none}.fab i{font-size:22px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:16px}.dialog-overlay.dialog-overlay--top{align-items:flex-start;padding:calc(env(safe-area-inset-top,0px) + 20px) 16px 16px}.dialog{background:#fff;border-radius:18px;width:100%;max-width:480px;max-height:90vh;overflow-y:auto;padding:20px;box-shadow:0 20px 50px #0f172a33}.calendar-sheet-backdrop{position:fixed;inset:45px 0 63px;background:#0a0c14a6;z-index:100;display:flex;padding:16px 0;justify-content:stretch}.calendar-sheet{width:100%;max-height:88dvh;background:#f2f2f7;border-top-left-radius:22px;border-top-right-radius:22px;overflow:hidden;display:flex;flex-direction:column;font-family:inherit}.calendar-sheet__handle{width:36px;height:4px;background:#c7cacc;border-radius:2px;margin:10px auto 8px;flex-shrink:0}.calendar-sheet__header{display:flex;align-items:center;justify-content:space-between;padding:0 16px 10px;flex-shrink:0}.calendar-sheet__header-left{display:flex;align-items:center;gap:8px}.calendar-sheet__icon{width:30px;height:30px;border-radius:8px;background:#fff;border:1px solid #e0e0e5;display:flex;align-items:center;justify-content:center}.calendar-sheet__icon i{font-size:15px;color:var(--primary)}.calendar-sheet__title{font-size:16px;font-weight:700;color:#0f172a;letter-spacing:-.01em}.calendar-sheet__close{width:28px;height:28px;border-radius:50%;background:#3c3c431f;border:none;cursor:pointer;font-size:14px;color:#3c3c43;display:flex;align-items:center;justify-content:center;font-family:inherit;transition:background .15s}.calendar-sheet__close:hover{background:#3c3c4333}.calendar-sheet__grid-area{padding:0 14px 8px;flex-shrink:0;touch-action:pan-y}.calendar-mnav{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.calendar-mnav__btn{width:32px;height:32px;border-radius:50%;background:#3c3c431a;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-family:inherit;color:#3c3c43;transition:background .15s}.calendar-mnav__btn i{font-size:13px}.calendar-mnav__btn:hover{background:#3c3c432e}.calendar-mnav__label{text-align:center;transition:opacity .21s,transform .21s}.calendar-mnav__label.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__label.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__center{display:flex;flex-direction:row;align-items:center;justify-content:center;transition:opacity .21s,transform .21s}.calendar-mnav__center.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__center.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__selects{display:flex;align-items:center;gap:6px}.calendar-mnav__select{padding:5px 22px 5px 8px;border:1px solid rgba(60,60,67,.18);border-radius:8px;background:#fff;color:#0f172a;font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%236e6e73' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 6px center}.calendar-mnav__select--year{min-width:70px}.calendar-mnav__select:focus{outline:none;border-color:#5b6cff;box-shadow:0 0 0 3px #5b6cff26}.calendar-mnav__today{display:flex;align-items:center;gap:4px;padding:4px 10px;border:1px solid rgba(16,185,129,.35);border-radius:8px;background:#10b98112;color:#047857;font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.calendar-mnav__today i{font-size:11px}.calendar-mnav__today:hover{background:#10b9811f;border-color:#10b98180}.calendar-mnav__month{display:block;font-size:18px;font-weight:700;color:#0f172a;letter-spacing:-.02em}.calendar-mnav__year{display:block;font-size:12px;color:#6e6e73;font-weight:500;margin-top:1px}.calendar-wd-row{display:grid;grid-template-columns:repeat(7,1fr);margin-bottom:4px}.calendar-wd-row span{text-align:center;font-size:11px;font-weight:600;color:#8e8e93;padding-bottom:4px}.calendar-wd-row span.is-sat{color:#1d6cf6}.calendar-wd-row span.is-sun{color:#e02020}.calendar-day-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;transition:opacity .21s,transform .21s}.calendar-day-grid.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-day-grid.is-sliding-prev{opacity:0;transform:translate(20px)}.cal-cell{background:transparent;border:none;border-radius:8px;height:38px;padding:0;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;transition:background .12s}.cal-cell.is-out-month{opacity:.35}.cal-cell.is-today{background:#5b6cff14}.cal-cell.is-selected{background:#5b6cff}.cal-cell.is-selected .cal-cell__day{color:#fff!important;font-weight:700}.cal-cell__day{font-size:15px;font-weight:400;color:#1c1c1e;line-height:1}.cal-cell__day.is-sat{color:#1d6cf6}.cal-cell__day.is-sun{color:#e02020}.is-today .cal-cell__day{font-weight:700;color:#5b6cff}.cal-cell__dots{display:flex;gap:2px;align-items:center}.cal-cell__dot{display:block;width:4px;height:4px;border-radius:50%;background:var(--primary)}.calendar-sheet__divider{height:1px;background:#3c3c431f;flex-shrink:0}.calendar-sheet__events{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;position:relative}.calendar-sheet__events-head{padding:12px 18px 8px;flex-shrink:0;display:flex;justify-content:space-between;align-items:center}.calendar-sheet__events-date{margin:0;font-size:13px;font-weight:700;color:#1c1c1e;letter-spacing:-.01em}.calendar-sheet__events-count{margin:2px 0 0;font-size:11.5px;color:#8e8e93}.calendar-sheet__events-list{flex:1;overflow-y:auto;padding:0 14px 16px;display:flex;flex-direction:column;gap:6px;overscroll-behavior:contain}.cal-ev-row{display:flex;gap:10px;align-items:stretch;background:#fff;border-radius:14px;padding:11px 14px;border:1px solid #e6e8ef}.cal-ev-row[data-type=vaccine]{border-color:#ddd6fe}.cal-ev-row[data-type=checkup]{border-color:#bfdbfe}.cal-ev-row[data-type=important]{border-color:#fecaca}.cal-ev-row[data-type=general]{border-color:#a7f3d0}.cal-ev-row__time{width:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums}.cal-ev-row__bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch}.cal-ev-row__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px}.cal-ev-row__title{margin:0;font-size:13.5px;font-weight:700;color:#1c1c1e;line-height:1.3}.cal-ev-row__place{margin:0;font-size:11.5px;color:#6e6e73;display:flex;align-items:center;gap:4px}.cal-ev-row__place i{font-size:11px;color:#8e8e93;flex-shrink:0}.cal-empty-day{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px 20px;text-align:center;gap:6px}.cal-empty-day__icon{font-size:28px;color:#c7cacc;margin-bottom:4px}.cal-empty-day__label{margin:0;font-size:14px;font-weight:600;color:#3c3c43}.cal-empty-day__sub{margin:0;font-size:12px;color:#8e8e93}.cal-empty-day__btn{margin-top:10px}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:16px}.dialog-head h2{margin:0;font-size:18px;font-weight:700;display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{border:none;width:36px;height:36px;border-radius:50%;background:#f1f5f9;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:#fee2e2;color:var(--rose)}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.form-grid .field.full{grid-column:1/-1}.place-combobox,.place-combobox .input{width:100%}.place-combobox__menu{margin:0;padding:4px 0;list-style:none;max-height:200px;overflow-y:auto;background:#fff;border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px #0f172a24}.place-combobox__menu--portal{position:fixed;z-index:110}.place-combobox__option{display:block;width:100%;padding:10px 12px;border:none;background:transparent;text-align:left;font-size:14px;font-family:inherit;color:var(--text);cursor:pointer}.place-combobox__option:hover{background:var(--primary-soft);color:var(--primary)}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.input--textarea{resize:vertical;min-height:80px;line-height:1.45}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:18px}.muted{color:var(--muted)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16527
+ static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "21.2.17", type: FeedingScheduleComponent, isStandalone: true, selector: "app-feeding-schedule", inputs: { user: { classPropertyName: "user", publicName: "user", isSignal: true, isRequired: false, transformFunction: null } }, host: { listeners: { "window:scroll": "onPlaceMenuViewportChange()", "window:resize": "onPlaceMenuViewportChange()" } }, ngImport: i0, template: "<div class=\"schedule-page\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n\n <div class=\"schedule-topbar\">\n <button type=\"button\" class=\"btn btn-primary sm\" (click)=\"openCalendarDialog()\">\n <i class=\"pi pi-calendar\"></i>\n L\u1ECBch th\u00E1ng\n </button>\n </div>\n\n @if (!loading() || hasFutureEvents()) {\n @for (section of futureSections(); track trackSection($index, section)) {\n <section\n class=\"schedule-section\"\n [attr.data-section]=\"section.id\"\n [hidden]=\"section.eventCount === 0\"\n >\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"section.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ section.title }}</h2>\n <p class=\"schedule-section__sub\">{{ section.subtitle }}</p>\n </div>\n @if (section.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(section); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: false }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(section)) {\n <button\n type=\"button\"\n class=\"schedule-view-more\"\n (click)=\"toggleSectionExpand(section.id)\"\n >\n @if (!isSectionExpanded(section.id)) {\n Xem th\u00EAm {{ hiddenEventCount(section) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(section.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n }\n\n @if (!loading() && !hasFutureEvents()) {\n <section class=\"schedule-empty\">\n <i class=\"pi pi-calendar-plus\"></i>\n <p>Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n s\u1EAFp t\u1EDBi.</p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </section>\n }\n\n @if (pastSection(); as past) {\n <section class=\"schedule-section schedule-section--past\">\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"past.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ past.title }}</h2>\n @if (past.subtitle) {\n <p class=\"schedule-section__sub\">{{ past.subtitle }}</p>\n }\n </div>\n @if (past.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(past); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: true }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(past)) {\n <button type=\"button\" class=\"schedule-view-more\" (click)=\"toggleSectionExpand(past.id)\">\n @if (!isSectionExpanded(past.id)) {\n Xem th\u00EAm {{ hiddenEventCount(past) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(past.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n\n <ng-template #eventCardTpl let-ev let-isPast=\"isPast\">\n <article\n class=\"ev-card\"\n [class.ev-card--past]=\"isPast\"\n [attr.data-type]=\"getEventTypeClass(ev)\"\n >\n <div class=\"ev-card__time-col\">\n <span class=\"ev-card__hour\">{{ formatTime(ev.time) }}</span>\n </div>\n <div class=\"ev-card__accent-bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"ev-card__body\">\n <h3 class=\"ev-card__title\">{{ ev.title }}</h3>\n @if (ev.place) {\n <p class=\"ev-card__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n <span>{{ ev.place }}</span>\n </p>\n }\n @if (ev.note) {\n <div class=\"ev-card__content-wrap\">\n <span class=\"ev-card__content-label\">N\u1ED9i dung</span>\n <p class=\"ev-card__content\">{{ ev.note }}</p>\n </div>\n }\n </div>\n @if (!isPast && ev.rowIndex) {\n <div class=\"ev-card__actions\">\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only\"\n (click)=\"openEdit(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"S\u1EEDa\"\n aria-label=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only danger\"\n (click)=\"deleteEv(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"Xo\u00E1\"\n aria-label=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n }\n </article>\n </ng-template>\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAdd()\"\n aria-label=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n title=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span class=\"fab-label\">Th\u00EAm s\u1EF1 ki\u1EC7n</span>\n </button>\n\n @if (calendarDialogOpen()) {\n <div class=\"calendar-sheet-backdrop\" (click)=\"closeCalendarDialog()\">\n <div\n class=\"calendar-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Drag handle -->\n <div class=\"calendar-sheet__handle\"></div>\n\n <!-- Header -->\n <div class=\"calendar-sheet__header\">\n <div class=\"calendar-sheet__header-left\">\n <div class=\"calendar-sheet__icon\">\n <i class=\"pi pi-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"calendar-sheet__title\">L\u1ECBch th\u00E1ng</span>\n </div>\n <button\n type=\"button\"\n class=\"calendar-sheet__close\"\n (click)=\"closeCalendarDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <!-- Month nav + grid (swipeable) -->\n <div\n class=\"calendar-sheet__grid-area\"\n (touchstart)=\"onCalendarTouchStart($event)\"\n (touchmove)=\"onCalendarTouchMove($event)\"\n (touchend)=\"onCalendarTouchEnd()\"\n >\n <!-- Month / year nav row -->\n <div class=\"calendar-mnav\">\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarPrevMonth()\"\n aria-label=\"Th\u00E1ng tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n </button>\n\n <div\n class=\"calendar-mnav__center\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n <div class=\"calendar-mnav__selects\">\n <select\n class=\"calendar-mnav__select\"\n [ngModel]=\"calendarCursor().getMonth()\"\n (ngModelChange)=\"calendarChangeMonth(+$event)\"\n aria-label=\"Ch\u1ECDn th\u00E1ng\"\n >\n <option [ngValue]=\"0\">Th\u00E1ng 1</option>\n <option [ngValue]=\"1\">Th\u00E1ng 2</option>\n <option [ngValue]=\"2\">Th\u00E1ng 3</option>\n <option [ngValue]=\"3\">Th\u00E1ng 4</option>\n <option [ngValue]=\"4\">Th\u00E1ng 5</option>\n <option [ngValue]=\"5\">Th\u00E1ng 6</option>\n <option [ngValue]=\"6\">Th\u00E1ng 7</option>\n <option [ngValue]=\"7\">Th\u00E1ng 8</option>\n <option [ngValue]=\"8\">Th\u00E1ng 9</option>\n <option [ngValue]=\"9\">Th\u00E1ng 10</option>\n <option [ngValue]=\"10\">Th\u00E1ng 11</option>\n <option [ngValue]=\"11\">Th\u00E1ng 12</option>\n </select>\n <select\n class=\"calendar-mnav__select calendar-mnav__select--year\"\n [ngModel]=\"calendarCursor().getFullYear()\"\n (ngModelChange)=\"calendarChangeYear(+$event)\"\n aria-label=\"Ch\u1ECDn n\u0103m\"\n >\n @for (year of calendarYearOptions(); track year) {\n <option [ngValue]=\"year\">{{ year }}</option>\n }\n </select>\n @if (!isViewingCurrentMonth()) {\n <button\n type=\"button\"\n class=\"calendar-mnav__today\"\n (click)=\"calendarGoToToday()\"\n title=\"V\u1EC1 h\u00F4m nay\"\n >\n <i class=\"pi pi-home\"></i>\n <span>H\u00F4m nay</span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarNextMonth()\"\n aria-label=\"Th\u00E1ng sau\"\n >\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"calendar-wd-row\">\n @for (wd of calendarWeekdays; track wd; let i = $index) {\n <span [class.is-sat]=\"i === 5\" [class.is-sun]=\"i === 6\">{{ wd }}</span>\n }\n </div>\n\n <!-- Day cells -->\n <div\n class=\"calendar-day-grid\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n @for (c of monthCalendarCells(); track c.dateIso; let i = $index) {\n <button\n type=\"button\"\n class=\"cal-cell\"\n [class.is-out-month]=\"!c.inMonth\"\n [class.is-today]=\"c.isToday\"\n [class.is-selected]=\"selectedCalendarDate() === c.dateIso\"\n (click)=\"selectCalendarDate(c.dateIso)\"\n >\n <span\n class=\"cal-cell__day\"\n [class.is-sat]=\"i % 7 === 5\"\n [class.is-sun]=\"i % 7 === 6\"\n >\n {{ c.day }}\n </span>\n @if (c.eventCount > 0) {\n <span class=\"cal-cell__dots\">\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n @if (c.eventCount > 1) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n @if (c.eventCount > 2) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n </span>\n }\n </button>\n }\n </div>\n </div>\n\n <div class=\"calendar-sheet__divider\"></div>\n\n <!-- Events panel -->\n <div class=\"calendar-sheet__events\">\n <div class=\"calendar-sheet__events-head\">\n <div>\n <p class=\"calendar-sheet__events-date\">\n {{ formatEventDate(selectedCalendarDate()) }}\n </p>\n <p class=\"calendar-sheet__events-count\">\n @if (selectedCalendarEvents().length > 0) {\n {{ selectedCalendarEvents().length }} s\u1EF1 ki\u1EC7n\n } @else {\n Kh\u00F4ng c\u00F3 s\u1EF1 ki\u1EC7n\n }\n </p>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-primary sm\"\n (click)=\"openAddFromCalendar()\"\n [disabled]=\"!selectedCalendarDate()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm\n </button>\n </div>\n\n <div class=\"calendar-sheet__events-list\">\n @for (ev of selectedCalendarEvents(); track trackEvent($index, ev)) {\n <article class=\"cal-ev-row\" [attr.data-type]=\"getEventTypeClass(ev)\">\n <div class=\"cal-ev-row__time\">{{ ev.time }}</div>\n <div class=\"cal-ev-row__bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"cal-ev-row__body\">\n <p class=\"cal-ev-row__title\">{{ ev.title }}</p>\n @if (ev.place) {\n <p class=\"cal-ev-row__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n {{ ev.place }}\n </p>\n }\n </div>\n </article>\n } @empty {\n @if (selectedCalendarDate()) {\n <div class=\"cal-empty-day\">\n <i class=\"pi pi-calendar-plus cal-empty-day__icon\" aria-hidden=\"true\"></i>\n <p class=\"cal-empty-day__label\">Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n</p>\n <p class=\"cal-empty-day__sub\">{{ formatEventDate(selectedCalendarDate()) }}</p>\n <button\n type=\"button\"\n class=\"btn btn-primary sm cal-empty-day__btn\"\n (click)=\"openAddFromCalendar()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </div>\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n\n @if (addOpen()) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"closeAdd()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-plus-circle\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAdd()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (draft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"evd\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateDraft({ time: $event })\"\n name=\"evt\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn s\u1EF1 ki\u1EC7n *</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"VD: Ti\u00EAm ch\u1EE7ng 6in1\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"evti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateDraft({ note: $event })\"\n name=\"evn\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputAdd\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'add', placeInputAdd)\"\n (focus)=\"openPlacePicker('add', placeInputAdd)\"\n (blur)=\"onPlaceBlur('add')\"\n name=\"evp\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAdd()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving()\" (click)=\"submitAdd()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </div>\n </div>\n }\n\n @if (editing(); as ed) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"cancelEdit()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-pencil\"></i> S\u1EEDa s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (editDraft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateEditDraft({ date: $event })\"\n name=\"eed\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateEditDraft({ time: $event })\"\n name=\"eet\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn *</span>\n <input\n type=\"text\"\n class=\"input\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateEditDraft({ title: $event })\"\n name=\"eeti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateEditDraft({ note: $event })\"\n name=\"een\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputEdit\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'edit', placeInputEdit)\"\n (focus)=\"openPlacePicker('edit', placeInputEdit)\"\n (blur)=\"onPlaceBlur('edit')\"\n name=\"eep\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"saving()\"\n (click)=\"submitEdit()\"\n >\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n\n@if (placeMenuView(); as vm) {\n <ul\n class=\"place-combobox__menu place-combobox__menu--portal\"\n role=\"listbox\"\n [style.top.px]=\"vm.pos.top\"\n [style.left.px]=\"vm.pos.left\"\n [style.width.px]=\"vm.pos.width\"\n (mousedown)=\"$event.preventDefault()\"\n >\n @for (place of vm.places; track place) {\n <li>\n <button\n type=\"button\"\n class=\"place-combobox__option\"\n (mousedown)=\"selectPlace(place, vm.ctx)\"\n >\n {{ place }}\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;--surface: #ffffff;--border: #e6e8ef;--border-soft: #eef1f6;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--rose: #ef4444}.schedule-page{max-width:640px;margin:0 auto;padding:8px 16px calc(120px + env(safe-area-inset-bottom,0px))}.schedule-topbar{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin:4px 0 12px}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:12px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.schedule-section{margin-bottom:40px;padding:16px 14px 18px;border-radius:16px;border:1px solid transparent}.schedule-section[hidden]{display:none!important}.schedule-section[data-section=today]{background:linear-gradient(180deg,#ecfdf5,#f7fef9);border-color:#a7f3d0}.schedule-section[data-section=today] .schedule-section__head{border-bottom-color:#a7f3d0}.schedule-section[data-section=soon]{background:linear-gradient(180deg,#fffbeb,#fffdf7);border-color:#fde68a}.schedule-section[data-section=soon] .schedule-section__head{border-bottom-color:#fde68a}.schedule-section[data-section=nextMonth]{background:linear-gradient(180deg,#eff6ff,#f8fbff);border-color:#bfdbfe}.schedule-section[data-section=nextMonth] .schedule-section__head{border-bottom-color:#bfdbfe}.schedule-section[data-section=later]{background:linear-gradient(180deg,#f5f3ff,#faf9ff);border-color:#ddd6fe}.schedule-section[data-section=later] .schedule-section__head{border-bottom-color:#ddd6fe}.schedule-section[data-section=today] .schedule-section__icon{background:linear-gradient(135deg,#bbf7d0,#86efac);color:#15803d}.schedule-section[data-section=soon] .schedule-section__icon{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#b45309}.schedule-section[data-section=nextMonth] .schedule-section__icon{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1d4ed8}.schedule-section[data-section=later] .schedule-section__icon{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:#6d28d9}.schedule-section--past{background:linear-gradient(180deg,#f8fafc,#fcfdfe);border-color:#e2e8f0}.schedule-section--past .schedule-section__head{border-bottom-color:#e2e8f0}.schedule-section--past .schedule-section__icon{background:#f1f5f9;color:var(--muted)}.schedule-section__head{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding:0 2px 14px;border-bottom:2px solid var(--border-soft)}.schedule-section__icon{flex-shrink:0;width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:17px}.schedule-section__titles{flex:1;min-width:0}.schedule-section__titles h2{margin:0;font-size:17px;font-weight:800;color:var(--text);letter-spacing:-.02em}.schedule-section__sub{margin:2px 0 0;font-size:12.5px;color:var(--muted)}.schedule-section__count{flex-shrink:0;font-size:11px;font-weight:700;color:var(--muted);background:#ffffffb8;padding:4px 10px;border-radius:999px;border:1px solid rgba(15,23,42,.06)}.schedule-section__body{display:flex;flex-direction:column;gap:4px}.schedule-view-more{display:inline-flex;align-items:center;justify-content:center;gap:6px;width:100%;margin-top:12px;padding:11px 14px;border:1px dashed #c7d2fe;border-radius:12px;background:#ffffffa6;color:var(--primary);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.schedule-view-more:hover{background:#fff;border-color:var(--primary)}.schedule-days{display:flex;flex-direction:column;gap:24px}.schedule-day{display:flex;flex-direction:column;gap:10px}.schedule-day__date{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:0 2px 6px;border-bottom:2px solid var(--border-soft)}.schedule-day--relative .schedule-day__date{border-bottom-color:#f59e0b38}[data-section=today] .schedule-day--relative .schedule-day__date{border-bottom-color:#10b98140}[data-section=today] .schedule-day__label--relative{color:#047857}[data-section=soon] .schedule-day__label--relative{color:#b45309}.schedule-day__label{font-size:14px;font-weight:700;color:var(--text);line-height:1.45}.schedule-day__label--relative{font-size:15px;font-weight:800;color:#b45309;letter-spacing:-.02em}.schedule-section--past .schedule-day__label--relative{color:#64748b}.schedule-section[data-section=nextMonth] .schedule-day__label--relative{color:#1d4ed8}.schedule-section[data-section=later] .schedule-day__label--relative{color:#6d28d9}.schedule-day__sub{font-size:12px;font-weight:500;color:var(--muted);line-height:1.35}.schedule-day__cards{display:flex;flex-direction:column;gap:8px;width:100%}.schedule-day__cards .ev-card__time-col{margin-right:8px}.ev-card{position:relative;display:flex;flex-direction:row;align-items:stretch;gap:0;padding:10px 12px;background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;transition:border-color .15s}.ev-card[data-type=vaccine]{border-color:#ddd6fe}.ev-card[data-type=checkup]{border-color:#bfdbfe}.ev-card[data-type=important]{border-color:#fecaca}.ev-card[data-type=general]{border-color:#a7f3d0}.ev-card--past{opacity:.68;background:#fafbfc}.ev-card__time-col{width:40px;flex-shrink:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.ev-card__hour{font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums;line-height:1.2;text-align:center}.ev-card__accent-bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch;margin-right:10px}.ev-card__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px;padding-right:44px}.ev-card__title{margin:0;font-size:13.5px;font-weight:700;color:var(--text);line-height:1.3}.ev-card__place{margin:0;display:flex;align-items:center;gap:4px;font-size:11.5px;color:var(--muted);line-height:1.4}.ev-card__place i{flex-shrink:0;font-size:11px;color:#94a3b8}.ev-card__content-wrap{margin-top:8px;padding-top:8px;border-top:1px solid var(--border-soft)}.ev-card__content-label{display:block;margin-bottom:3px;font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#94a3b8}.ev-card__content{margin:0;font-size:13px;color:#334155;line-height:1.5;white-space:pre-wrap}.ev-card--past .ev-card__content{color:var(--muted)}.ev-card__actions{position:absolute;top:8px;right:8px;display:flex;gap:2px}.schedule-empty{text-align:center;padding:40px 20px;color:var(--muted)}.schedule-empty i{font-size:40px;color:#cbd5e1;margin-bottom:12px;display:block}.schedule-empty p{margin:0 0 16px;font-size:14px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer}.btn:disabled{opacity:.5;cursor:not-allowed}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca;background:#fef2f2}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon-only{padding:8px;width:36px;height:36px}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:18px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;white-space:nowrap;cursor:pointer;box-shadow:0 8px 24px #5b6cff73}.fab i{flex-shrink:0;font-size:18px}.fab .fab-label{white-space:nowrap;line-height:1.2}@media(max-width:480px){.fab{padding:14px;gap:0}.fab .fab-label{display:none}.fab i{font-size:22px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:16px}.dialog-overlay.dialog-overlay--top{align-items:flex-start;padding:calc(env(safe-area-inset-top,0px) + 20px) 16px 16px}.dialog{background:#fff;border-radius:18px;width:100%;max-width:480px;max-height:90vh;overflow-y:auto;padding:20px;box-shadow:0 20px 50px #0f172a33}.calendar-sheet-backdrop{position:fixed;inset:45px 0 63px;background:#0a0c14a6;z-index:100;display:flex;padding:16px 0;justify-content:stretch}.calendar-sheet{width:100%;max-height:88dvh;background:#f2f2f7;border-top-left-radius:22px;border-top-right-radius:22px;overflow:hidden;display:flex;flex-direction:column;font-family:inherit}.calendar-sheet__handle{width:36px;height:4px;background:#c7cacc;border-radius:2px;margin:10px auto 8px;flex-shrink:0}.calendar-sheet__header{display:flex;align-items:center;justify-content:space-between;padding:0 16px 10px;flex-shrink:0}.calendar-sheet__header-left{display:flex;align-items:center;gap:8px}.calendar-sheet__icon{width:30px;height:30px;border-radius:8px;background:#fff;border:1px solid #e0e0e5;display:flex;align-items:center;justify-content:center}.calendar-sheet__icon i{font-size:15px;color:var(--primary)}.calendar-sheet__title{font-size:16px;font-weight:700;color:#0f172a;letter-spacing:-.01em}.calendar-sheet__close{width:28px;height:28px;border-radius:50%;background:#3c3c431f;border:none;cursor:pointer;font-size:14px;color:#3c3c43;display:flex;align-items:center;justify-content:center;font-family:inherit;transition:background .15s}.calendar-sheet__close:hover{background:#3c3c4333}.calendar-sheet__grid-area{padding:0 14px 8px;flex-shrink:0;touch-action:pan-y}.calendar-mnav{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.calendar-mnav__btn{width:32px;height:32px;border-radius:50%;background:#3c3c431a;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-family:inherit;color:#3c3c43;transition:background .15s}.calendar-mnav__btn i{font-size:13px}.calendar-mnav__btn:hover{background:#3c3c432e}.calendar-mnav__label{text-align:center;transition:opacity .21s,transform .21s}.calendar-mnav__label.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__label.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__center{display:flex;flex-direction:row;align-items:center;justify-content:center;transition:opacity .21s,transform .21s}.calendar-mnav__center.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__center.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__selects{display:flex;align-items:center;gap:6px}.calendar-mnav__select{padding:5px 22px 5px 8px;border:1px solid rgba(60,60,67,.18);border-radius:8px;background:#fff;color:#0f172a;font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%236e6e73' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 6px center}.calendar-mnav__select--year{min-width:70px}.calendar-mnav__select:focus{outline:none;border-color:#5b6cff;box-shadow:0 0 0 3px #5b6cff26}.calendar-mnav__today{display:flex;align-items:center;gap:4px;padding:4px 10px;border:1px solid rgba(16,185,129,.35);border-radius:8px;background:#10b98112;color:#047857;font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.calendar-mnav__today i{font-size:11px}.calendar-mnav__today:hover{background:#10b9811f;border-color:#10b98180}.calendar-mnav__month{display:block;font-size:18px;font-weight:700;color:#0f172a;letter-spacing:-.02em}.calendar-mnav__year{display:block;font-size:12px;color:#6e6e73;font-weight:500;margin-top:1px}.calendar-wd-row{display:grid;grid-template-columns:repeat(7,1fr);margin-bottom:4px}.calendar-wd-row span{text-align:center;font-size:11px;font-weight:600;color:#8e8e93;padding-bottom:4px}.calendar-wd-row span.is-sat{color:#1d6cf6}.calendar-wd-row span.is-sun{color:#e02020}.calendar-day-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;transition:opacity .21s,transform .21s}.calendar-day-grid.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-day-grid.is-sliding-prev{opacity:0;transform:translate(20px)}.cal-cell{background:transparent;border:none;border-radius:8px;height:38px;padding:0;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;transition:background .12s}.cal-cell.is-out-month{opacity:.35}.cal-cell.is-today{background:#5b6cff14}.cal-cell.is-selected{background:#5b6cff}.cal-cell.is-selected .cal-cell__day{color:#fff!important;font-weight:700}.cal-cell__day{font-size:15px;font-weight:400;color:#1c1c1e;line-height:1}.cal-cell__day.is-sat{color:#1d6cf6}.cal-cell__day.is-sun{color:#e02020}.is-today .cal-cell__day{font-weight:700;color:#5b6cff}.cal-cell__dots{display:flex;gap:2px;align-items:center}.cal-cell__dot{display:block;width:4px;height:4px;border-radius:50%;background:var(--primary)}.calendar-sheet__divider{height:1px;background:#3c3c431f;flex-shrink:0}.calendar-sheet__events{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;position:relative}.calendar-sheet__events-head{padding:12px 18px 8px;flex-shrink:0;display:flex;justify-content:space-between;align-items:center}.calendar-sheet__events-date{margin:0;font-size:13px;font-weight:700;color:#1c1c1e;letter-spacing:-.01em}.calendar-sheet__events-count{margin:2px 0 0;font-size:11.5px;color:#8e8e93}.calendar-sheet__events-list{flex:1;overflow-y:auto;padding:0 14px 16px;display:flex;flex-direction:column;gap:6px;overscroll-behavior:contain}.cal-ev-row{display:flex;gap:10px;align-items:stretch;background:#fff;border-radius:14px;padding:11px 14px;border:1px solid #e6e8ef}.cal-ev-row[data-type=vaccine]{border-color:#ddd6fe}.cal-ev-row[data-type=checkup]{border-color:#bfdbfe}.cal-ev-row[data-type=important]{border-color:#fecaca}.cal-ev-row[data-type=general]{border-color:#a7f3d0}.cal-ev-row__time{width:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums}.cal-ev-row__bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch}.cal-ev-row__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px}.cal-ev-row__title{margin:0;font-size:13.5px;font-weight:700;color:#1c1c1e;line-height:1.3}.cal-ev-row__place{margin:0;font-size:11.5px;color:#6e6e73;display:flex;align-items:center;gap:4px}.cal-ev-row__place i{font-size:11px;color:#8e8e93;flex-shrink:0}.cal-empty-day{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px 20px;text-align:center;gap:6px}.cal-empty-day__icon{font-size:28px;color:#c7cacc;margin-bottom:4px}.cal-empty-day__label{margin:0;font-size:14px;font-weight:600;color:#3c3c43}.cal-empty-day__sub{margin:0;font-size:12px;color:#8e8e93}.cal-empty-day__btn{margin-top:10px}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:16px}.dialog-head h2{margin:0;font-size:18px;font-weight:700;display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{border:none;width:36px;height:36px;border-radius:50%;background:#f1f5f9;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:#fee2e2;color:var(--rose)}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.form-grid .field.full{grid-column:1/-1}.place-combobox,.place-combobox .input{width:100%}.place-combobox__menu{margin:0;padding:4px 0;list-style:none;max-height:200px;overflow-y:auto;background:#fff;border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px #0f172a24}.place-combobox__menu--portal{position:fixed;z-index:110}.place-combobox__option{display:block;width:100%;padding:10px 12px;border:none;background:transparent;text-align:left;font-size:14px;font-family:inherit;color:var(--text);cursor:pointer}.place-combobox__option:hover{background:var(--primary-soft);color:var(--primary)}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.input--textarea{resize:vertical;min-height:80px;line-height:1.45}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:18px}.muted{color:var(--muted)}\n"], dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1$1.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i1$1.NgTemplateOutlet, selector: "[ngTemplateOutlet]", inputs: ["ngTemplateOutletContext", "ngTemplateOutlet", "ngTemplateOutletInjector"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i1.NgSelectOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.ɵNgSelectMultipleOption, selector: "option", inputs: ["ngValue", "value"] }, { kind: "directive", type: i1.DefaultValueAccessor, selector: "input:not([type=checkbox])[formControlName],textarea[formControlName],input:not([type=checkbox])[formControl],textarea[formControl],input:not([type=checkbox])[ngModel],textarea[ngModel],[ngDefaultControl]" }, { kind: "directive", type: i1.SelectControlValueAccessor, selector: "select:not([multiple])[formControlName],select:not([multiple])[formControl],select:not([multiple])[ngModel]", inputs: ["compareWith"] }, { kind: "directive", type: i1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }], changeDetection: i0.ChangeDetectionStrategy.OnPush }); }
16143
16528
  }
16144
16529
  i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "21.2.17", ngImport: i0, type: FeedingScheduleComponent, decorators: [{
16145
16530
  type: Component,
16146
- args: [{ selector: 'app-feeding-schedule', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"schedule-page\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n\n <div class=\"schedule-topbar\">\n <button type=\"button\" class=\"btn btn-primary sm\" (click)=\"openCalendarDialog()\">\n <i class=\"pi pi-calendar\"></i>\n L\u1ECBch th\u00E1ng\n </button>\n </div>\n\n @if (!loading() || hasFutureEvents()) {\n @for (section of futureSections(); track trackSection($index, section)) {\n <section\n class=\"schedule-section\"\n [attr.data-section]=\"section.id\"\n [hidden]=\"section.eventCount === 0\"\n >\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"section.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ section.title }}</h2>\n <p class=\"schedule-section__sub\">{{ section.subtitle }}</p>\n </div>\n @if (section.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(section); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: false }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(section)) {\n <button\n type=\"button\"\n class=\"schedule-view-more\"\n (click)=\"toggleSectionExpand(section.id)\"\n >\n @if (!isSectionExpanded(section.id)) {\n Xem th\u00EAm {{ hiddenEventCount(section) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(section.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n }\n\n @if (!loading() && !hasFutureEvents()) {\n <section class=\"schedule-empty\">\n <i class=\"pi pi-calendar-plus\"></i>\n <p>Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n s\u1EAFp t\u1EDBi.</p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </section>\n }\n\n @if (pastSection(); as past) {\n <section class=\"schedule-section schedule-section--past\">\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"past.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ past.title }}</h2>\n @if (past.subtitle) {\n <p class=\"schedule-section__sub\">{{ past.subtitle }}</p>\n }\n </div>\n @if (past.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(past); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: true }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(past)) {\n <button type=\"button\" class=\"schedule-view-more\" (click)=\"toggleSectionExpand(past.id)\">\n @if (!isSectionExpanded(past.id)) {\n Xem th\u00EAm {{ hiddenEventCount(past) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(past.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n\n <ng-template #eventCardTpl let-ev let-isPast=\"isPast\">\n <article\n class=\"ev-card\"\n [class.ev-card--past]=\"isPast\"\n [attr.data-type]=\"getEventTypeClass(ev)\"\n >\n <div class=\"ev-card__time-col\">\n <span class=\"ev-card__hour\">{{ formatTime(ev.time) }}</span>\n </div>\n <div class=\"ev-card__accent-bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"ev-card__body\">\n <h3 class=\"ev-card__title\">{{ ev.title }}</h3>\n @if (ev.place) {\n <p class=\"ev-card__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n <span>{{ ev.place }}</span>\n </p>\n }\n @if (ev.note) {\n <div class=\"ev-card__content-wrap\">\n <span class=\"ev-card__content-label\">N\u1ED9i dung</span>\n <p class=\"ev-card__content\">{{ ev.note }}</p>\n </div>\n }\n </div>\n @if (!isPast && ev.rowIndex) {\n <div class=\"ev-card__actions\">\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only\"\n (click)=\"openEdit(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"S\u1EEDa\"\n aria-label=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only danger\"\n (click)=\"deleteEv(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"Xo\u00E1\"\n aria-label=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n }\n </article>\n </ng-template>\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAdd()\"\n aria-label=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n title=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span class=\"fab-label\">Th\u00EAm s\u1EF1 ki\u1EC7n</span>\n </button>\n\n @if (calendarDialogOpen()) {\n <div class=\"calendar-sheet-backdrop\" (click)=\"closeCalendarDialog()\">\n <div\n class=\"calendar-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Drag handle -->\n <div class=\"calendar-sheet__handle\"></div>\n\n <!-- Header -->\n <div class=\"calendar-sheet__header\">\n <div class=\"calendar-sheet__header-left\">\n <div class=\"calendar-sheet__icon\">\n <i class=\"pi pi-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"calendar-sheet__title\">L\u1ECBch th\u00E1ng</span>\n </div>\n <button\n type=\"button\"\n class=\"calendar-sheet__close\"\n (click)=\"closeCalendarDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <!-- Month nav + grid (swipeable) -->\n <div\n class=\"calendar-sheet__grid-area\"\n (touchstart)=\"onCalendarTouchStart($event)\"\n (touchmove)=\"onCalendarTouchMove($event)\"\n (touchend)=\"onCalendarTouchEnd()\"\n >\n <!-- Month / year nav row -->\n <div class=\"calendar-mnav\">\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarPrevMonth()\"\n aria-label=\"Th\u00E1ng tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n </button>\n\n <div\n class=\"calendar-mnav__center\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n <div class=\"calendar-mnav__selects\">\n <select\n class=\"calendar-mnav__select\"\n [ngModel]=\"calendarCursor().getMonth()\"\n (ngModelChange)=\"calendarChangeMonth(+$event)\"\n aria-label=\"Ch\u1ECDn th\u00E1ng\"\n >\n <option [ngValue]=\"0\">Th\u00E1ng 1</option>\n <option [ngValue]=\"1\">Th\u00E1ng 2</option>\n <option [ngValue]=\"2\">Th\u00E1ng 3</option>\n <option [ngValue]=\"3\">Th\u00E1ng 4</option>\n <option [ngValue]=\"4\">Th\u00E1ng 5</option>\n <option [ngValue]=\"5\">Th\u00E1ng 6</option>\n <option [ngValue]=\"6\">Th\u00E1ng 7</option>\n <option [ngValue]=\"7\">Th\u00E1ng 8</option>\n <option [ngValue]=\"8\">Th\u00E1ng 9</option>\n <option [ngValue]=\"9\">Th\u00E1ng 10</option>\n <option [ngValue]=\"10\">Th\u00E1ng 11</option>\n <option [ngValue]=\"11\">Th\u00E1ng 12</option>\n </select>\n <select\n class=\"calendar-mnav__select calendar-mnav__select--year\"\n [ngModel]=\"calendarCursor().getFullYear()\"\n (ngModelChange)=\"calendarChangeYear(+$event)\"\n aria-label=\"Ch\u1ECDn n\u0103m\"\n >\n @for (year of calendarYearOptions(); track year) {\n <option [ngValue]=\"year\">{{ year }}</option>\n }\n </select>\n @if (!isViewingCurrentMonth()) {\n <button\n type=\"button\"\n class=\"calendar-mnav__today\"\n (click)=\"calendarGoToToday()\"\n title=\"V\u1EC1 h\u00F4m nay\"\n >\n <i class=\"pi pi-home\"></i>\n <span>H\u00F4m nay</span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarNextMonth()\"\n aria-label=\"Th\u00E1ng sau\"\n >\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"calendar-wd-row\">\n @for (wd of calendarWeekdays; track wd; let i = $index) {\n <span [class.is-sat]=\"i === 5\" [class.is-sun]=\"i === 6\">{{ wd }}</span>\n }\n </div>\n\n <!-- Day cells -->\n <div\n class=\"calendar-day-grid\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n @for (c of monthCalendarCells(); track c.dateIso; let i = $index) {\n <button\n type=\"button\"\n class=\"cal-cell\"\n [class.is-out-month]=\"!c.inMonth\"\n [class.is-today]=\"c.isToday\"\n [class.is-selected]=\"selectedCalendarDate() === c.dateIso\"\n (click)=\"selectCalendarDate(c.dateIso)\"\n >\n <span\n class=\"cal-cell__day\"\n [class.is-sat]=\"i % 7 === 5\"\n [class.is-sun]=\"i % 7 === 6\"\n >\n {{ c.day }}\n </span>\n @if (c.eventCount > 0) {\n <span class=\"cal-cell__dots\">\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n @if (c.eventCount > 1) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n @if (c.eventCount > 2) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n </span>\n }\n </button>\n }\n </div>\n </div>\n\n <div class=\"calendar-sheet__divider\"></div>\n\n <!-- Events panel -->\n <div class=\"calendar-sheet__events\">\n <div class=\"calendar-sheet__events-head\">\n <div>\n <p class=\"calendar-sheet__events-date\">\n {{ formatEventDate(selectedCalendarDate()) }}\n </p>\n <p class=\"calendar-sheet__events-count\">\n @if (selectedCalendarEvents().length > 0) {\n {{ selectedCalendarEvents().length }} s\u1EF1 ki\u1EC7n\n } @else {\n Kh\u00F4ng c\u00F3 s\u1EF1 ki\u1EC7n\n }\n </p>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-primary sm\"\n (click)=\"openAddFromCalendar()\"\n [disabled]=\"!selectedCalendarDate()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm\n </button>\n </div>\n\n <div class=\"calendar-sheet__events-list\">\n @for (ev of selectedCalendarEvents(); track trackEvent($index, ev)) {\n <article class=\"cal-ev-row\" [attr.data-type]=\"getEventTypeClass(ev)\">\n <div class=\"cal-ev-row__time\">{{ ev.time }}</div>\n <div class=\"cal-ev-row__bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"cal-ev-row__body\">\n <p class=\"cal-ev-row__title\">{{ ev.title }}</p>\n @if (ev.place) {\n <p class=\"cal-ev-row__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n {{ ev.place }}\n </p>\n }\n </div>\n </article>\n } @empty {\n @if (selectedCalendarDate()) {\n <div class=\"cal-empty-day\">\n <i class=\"pi pi-calendar-plus cal-empty-day__icon\" aria-hidden=\"true\"></i>\n <p class=\"cal-empty-day__label\">Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n</p>\n <p class=\"cal-empty-day__sub\">{{ formatEventDate(selectedCalendarDate()) }}</p>\n <button\n type=\"button\"\n class=\"btn btn-primary sm cal-empty-day__btn\"\n (click)=\"openAddFromCalendar()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </div>\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n\n @if (addOpen()) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"closeAdd()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-plus-circle\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAdd()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (draft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"evd\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateDraft({ time: $event })\"\n name=\"evt\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn s\u1EF1 ki\u1EC7n *</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"VD: Ti\u00EAm ch\u1EE7ng 6in1\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"evti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateDraft({ note: $event })\"\n name=\"evn\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputAdd\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'add', placeInputAdd)\"\n (focus)=\"openPlacePicker('add', placeInputAdd)\"\n (blur)=\"onPlaceBlur('add')\"\n name=\"evp\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAdd()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving()\" (click)=\"submitAdd()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </div>\n </div>\n }\n\n @if (editing(); as ed) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"cancelEdit()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-pencil\"></i> S\u1EEDa s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (editDraft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateEditDraft({ date: $event })\"\n name=\"eed\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateEditDraft({ time: $event })\"\n name=\"eet\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn *</span>\n <input\n type=\"text\"\n class=\"input\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateEditDraft({ title: $event })\"\n name=\"eeti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateEditDraft({ note: $event })\"\n name=\"een\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputEdit\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'edit', placeInputEdit)\"\n (focus)=\"openPlacePicker('edit', placeInputEdit)\"\n (blur)=\"onPlaceBlur('edit')\"\n name=\"eep\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"saving()\"\n (click)=\"submitEdit()\"\n >\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n\n@if (placeMenuView(); as vm) {\n <ul\n class=\"place-combobox__menu place-combobox__menu--portal\"\n role=\"listbox\"\n [style.top.px]=\"vm.pos.top\"\n [style.left.px]=\"vm.pos.left\"\n [style.width.px]=\"vm.pos.width\"\n (mousedown)=\"$event.preventDefault()\"\n >\n @for (place of vm.places; track place) {\n <li>\n <button\n type=\"button\"\n class=\"place-combobox__option\"\n (mousedown)=\"selectPlace(place, vm.ctx)\"\n >\n {{ place }}\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;--surface: #ffffff;--border: #e6e8ef;--border-soft: #eef1f6;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--rose: #ef4444}.schedule-page{max-width:640px;margin:0 auto;padding:8px 16px calc(120px + env(safe-area-inset-bottom,0px))}.schedule-topbar{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin:4px 0 12px}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:12px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.schedule-section{margin-bottom:40px;padding:16px 14px 18px;border-radius:16px;border:1px solid transparent}.schedule-section[hidden]{display:none!important}.schedule-section[data-section=today]{background:linear-gradient(180deg,#ecfdf5,#f7fef9);border-color:#a7f3d0}.schedule-section[data-section=today] .schedule-section__head{border-bottom-color:#a7f3d0}.schedule-section[data-section=soon]{background:linear-gradient(180deg,#fffbeb,#fffdf7);border-color:#fde68a}.schedule-section[data-section=soon] .schedule-section__head{border-bottom-color:#fde68a}.schedule-section[data-section=nextMonth]{background:linear-gradient(180deg,#eff6ff,#f8fbff);border-color:#bfdbfe}.schedule-section[data-section=nextMonth] .schedule-section__head{border-bottom-color:#bfdbfe}.schedule-section[data-section=later]{background:linear-gradient(180deg,#f5f3ff,#faf9ff);border-color:#ddd6fe}.schedule-section[data-section=later] .schedule-section__head{border-bottom-color:#ddd6fe}.schedule-section[data-section=today] .schedule-section__icon{background:linear-gradient(135deg,#bbf7d0,#86efac);color:#15803d}.schedule-section[data-section=soon] .schedule-section__icon{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#b45309}.schedule-section[data-section=nextMonth] .schedule-section__icon{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1d4ed8}.schedule-section[data-section=later] .schedule-section__icon{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:#6d28d9}.schedule-section--past{background:linear-gradient(180deg,#f8fafc,#fcfdfe);border-color:#e2e8f0}.schedule-section--past .schedule-section__head{border-bottom-color:#e2e8f0}.schedule-section--past .schedule-section__icon{background:#f1f5f9;color:var(--muted)}.schedule-section__head{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding:0 2px 14px;border-bottom:2px solid var(--border-soft)}.schedule-section__icon{flex-shrink:0;width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:17px}.schedule-section__titles{flex:1;min-width:0}.schedule-section__titles h2{margin:0;font-size:17px;font-weight:800;color:var(--text);letter-spacing:-.02em}.schedule-section__sub{margin:2px 0 0;font-size:12.5px;color:var(--muted)}.schedule-section__count{flex-shrink:0;font-size:11px;font-weight:700;color:var(--muted);background:#ffffffb8;padding:4px 10px;border-radius:999px;border:1px solid rgba(15,23,42,.06)}.schedule-section__body{display:flex;flex-direction:column;gap:4px}.schedule-view-more{display:inline-flex;align-items:center;justify-content:center;gap:6px;width:100%;margin-top:12px;padding:11px 14px;border:1px dashed #c7d2fe;border-radius:12px;background:#ffffffa6;color:var(--primary);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.schedule-view-more:hover{background:#fff;border-color:var(--primary)}.schedule-days{display:flex;flex-direction:column;gap:24px}.schedule-day{display:flex;flex-direction:column;gap:10px}.schedule-day__date{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:0 2px 6px;border-bottom:2px solid var(--border-soft)}.schedule-day--relative .schedule-day__date{border-bottom-color:#f59e0b38}[data-section=today] .schedule-day--relative .schedule-day__date{border-bottom-color:#10b98140}[data-section=today] .schedule-day__label--relative{color:#047857}[data-section=soon] .schedule-day__label--relative{color:#b45309}.schedule-day__label{font-size:14px;font-weight:700;color:var(--text);line-height:1.45}.schedule-day__label--relative{font-size:15px;font-weight:800;color:#b45309;letter-spacing:-.02em}.schedule-section--past .schedule-day__label--relative{color:#64748b}.schedule-section[data-section=nextMonth] .schedule-day__label--relative{color:#1d4ed8}.schedule-section[data-section=later] .schedule-day__label--relative{color:#6d28d9}.schedule-day__sub{font-size:12px;font-weight:500;color:var(--muted);line-height:1.35}.schedule-day__cards{display:flex;flex-direction:column;gap:8px;width:100%}.ev-card{position:relative;display:flex;flex-direction:row;align-items:stretch;gap:0;padding:10px 12px;background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;transition:border-color .15s}.ev-card[data-type=vaccine]{border-color:#ddd6fe}.ev-card[data-type=checkup]{border-color:#bfdbfe}.ev-card[data-type=important]{border-color:#fecaca}.ev-card[data-type=general]{border-color:#a7f3d0}.ev-card--past{opacity:.68;background:#fafbfc}.ev-card__time-col{width:40px;flex-shrink:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.ev-card__hour{font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums;line-height:1.2;text-align:center}.ev-card__accent-bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch;margin-right:10px}.ev-card__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px;padding-right:44px}.ev-card__title{margin:0;font-size:13.5px;font-weight:700;color:var(--text);line-height:1.3}.ev-card__place{margin:0;display:flex;align-items:center;gap:4px;font-size:11.5px;color:var(--muted);line-height:1.4}.ev-card__place i{flex-shrink:0;font-size:11px;color:#94a3b8}.ev-card__content-wrap{margin-top:8px;padding-top:8px;border-top:1px solid var(--border-soft)}.ev-card__content-label{display:block;margin-bottom:3px;font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#94a3b8}.ev-card__content{margin:0;font-size:13px;color:#334155;line-height:1.5;white-space:pre-wrap}.ev-card--past .ev-card__content{color:var(--muted)}.ev-card__actions{position:absolute;top:8px;right:8px;display:flex;gap:2px}.schedule-empty{text-align:center;padding:40px 20px;color:var(--muted)}.schedule-empty i{font-size:40px;color:#cbd5e1;margin-bottom:12px;display:block}.schedule-empty p{margin:0 0 16px;font-size:14px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer}.btn:disabled{opacity:.5;cursor:not-allowed}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca;background:#fef2f2}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon-only{padding:8px;width:36px;height:36px}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:18px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;white-space:nowrap;cursor:pointer;box-shadow:0 8px 24px #5b6cff73}.fab i{flex-shrink:0;font-size:18px}.fab .fab-label{white-space:nowrap;line-height:1.2}@media(max-width:480px){.fab{padding:14px;gap:0}.fab .fab-label{display:none}.fab i{font-size:22px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:16px}.dialog-overlay.dialog-overlay--top{align-items:flex-start;padding:calc(env(safe-area-inset-top,0px) + 20px) 16px 16px}.dialog{background:#fff;border-radius:18px;width:100%;max-width:480px;max-height:90vh;overflow-y:auto;padding:20px;box-shadow:0 20px 50px #0f172a33}.calendar-sheet-backdrop{position:fixed;inset:45px 0 63px;background:#0a0c14a6;z-index:100;display:flex;padding:16px 0;justify-content:stretch}.calendar-sheet{width:100%;max-height:88dvh;background:#f2f2f7;border-top-left-radius:22px;border-top-right-radius:22px;overflow:hidden;display:flex;flex-direction:column;font-family:inherit}.calendar-sheet__handle{width:36px;height:4px;background:#c7cacc;border-radius:2px;margin:10px auto 8px;flex-shrink:0}.calendar-sheet__header{display:flex;align-items:center;justify-content:space-between;padding:0 16px 10px;flex-shrink:0}.calendar-sheet__header-left{display:flex;align-items:center;gap:8px}.calendar-sheet__icon{width:30px;height:30px;border-radius:8px;background:#fff;border:1px solid #e0e0e5;display:flex;align-items:center;justify-content:center}.calendar-sheet__icon i{font-size:15px;color:var(--primary)}.calendar-sheet__title{font-size:16px;font-weight:700;color:#0f172a;letter-spacing:-.01em}.calendar-sheet__close{width:28px;height:28px;border-radius:50%;background:#3c3c431f;border:none;cursor:pointer;font-size:14px;color:#3c3c43;display:flex;align-items:center;justify-content:center;font-family:inherit;transition:background .15s}.calendar-sheet__close:hover{background:#3c3c4333}.calendar-sheet__grid-area{padding:0 14px 8px;flex-shrink:0;touch-action:pan-y}.calendar-mnav{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.calendar-mnav__btn{width:32px;height:32px;border-radius:50%;background:#3c3c431a;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-family:inherit;color:#3c3c43;transition:background .15s}.calendar-mnav__btn i{font-size:13px}.calendar-mnav__btn:hover{background:#3c3c432e}.calendar-mnav__label{text-align:center;transition:opacity .21s,transform .21s}.calendar-mnav__label.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__label.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__center{display:flex;flex-direction:row;align-items:center;justify-content:center;transition:opacity .21s,transform .21s}.calendar-mnav__center.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__center.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__selects{display:flex;align-items:center;gap:6px}.calendar-mnav__select{padding:5px 22px 5px 8px;border:1px solid rgba(60,60,67,.18);border-radius:8px;background:#fff;color:#0f172a;font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%236e6e73' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 6px center}.calendar-mnav__select--year{min-width:70px}.calendar-mnav__select:focus{outline:none;border-color:#5b6cff;box-shadow:0 0 0 3px #5b6cff26}.calendar-mnav__today{display:flex;align-items:center;gap:4px;padding:4px 10px;border:1px solid rgba(16,185,129,.35);border-radius:8px;background:#10b98112;color:#047857;font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.calendar-mnav__today i{font-size:11px}.calendar-mnav__today:hover{background:#10b9811f;border-color:#10b98180}.calendar-mnav__month{display:block;font-size:18px;font-weight:700;color:#0f172a;letter-spacing:-.02em}.calendar-mnav__year{display:block;font-size:12px;color:#6e6e73;font-weight:500;margin-top:1px}.calendar-wd-row{display:grid;grid-template-columns:repeat(7,1fr);margin-bottom:4px}.calendar-wd-row span{text-align:center;font-size:11px;font-weight:600;color:#8e8e93;padding-bottom:4px}.calendar-wd-row span.is-sat{color:#1d6cf6}.calendar-wd-row span.is-sun{color:#e02020}.calendar-day-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;transition:opacity .21s,transform .21s}.calendar-day-grid.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-day-grid.is-sliding-prev{opacity:0;transform:translate(20px)}.cal-cell{background:transparent;border:none;border-radius:8px;height:38px;padding:0;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;transition:background .12s}.cal-cell.is-out-month{opacity:.35}.cal-cell.is-today{background:#5b6cff14}.cal-cell.is-selected{background:#5b6cff}.cal-cell.is-selected .cal-cell__day{color:#fff!important;font-weight:700}.cal-cell__day{font-size:15px;font-weight:400;color:#1c1c1e;line-height:1}.cal-cell__day.is-sat{color:#1d6cf6}.cal-cell__day.is-sun{color:#e02020}.is-today .cal-cell__day{font-weight:700;color:#5b6cff}.cal-cell__dots{display:flex;gap:2px;align-items:center}.cal-cell__dot{display:block;width:4px;height:4px;border-radius:50%;background:var(--primary)}.calendar-sheet__divider{height:1px;background:#3c3c431f;flex-shrink:0}.calendar-sheet__events{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;position:relative}.calendar-sheet__events-head{padding:12px 18px 8px;flex-shrink:0;display:flex;justify-content:space-between;align-items:center}.calendar-sheet__events-date{margin:0;font-size:13px;font-weight:700;color:#1c1c1e;letter-spacing:-.01em}.calendar-sheet__events-count{margin:2px 0 0;font-size:11.5px;color:#8e8e93}.calendar-sheet__events-list{flex:1;overflow-y:auto;padding:0 14px 16px;display:flex;flex-direction:column;gap:6px;overscroll-behavior:contain}.cal-ev-row{display:flex;gap:10px;align-items:stretch;background:#fff;border-radius:14px;padding:11px 14px;border:1px solid #e6e8ef}.cal-ev-row[data-type=vaccine]{border-color:#ddd6fe}.cal-ev-row[data-type=checkup]{border-color:#bfdbfe}.cal-ev-row[data-type=important]{border-color:#fecaca}.cal-ev-row[data-type=general]{border-color:#a7f3d0}.cal-ev-row__time{width:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums}.cal-ev-row__bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch}.cal-ev-row__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px}.cal-ev-row__title{margin:0;font-size:13.5px;font-weight:700;color:#1c1c1e;line-height:1.3}.cal-ev-row__place{margin:0;font-size:11.5px;color:#6e6e73;display:flex;align-items:center;gap:4px}.cal-ev-row__place i{font-size:11px;color:#8e8e93;flex-shrink:0}.cal-empty-day{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px 20px;text-align:center;gap:6px}.cal-empty-day__icon{font-size:28px;color:#c7cacc;margin-bottom:4px}.cal-empty-day__label{margin:0;font-size:14px;font-weight:600;color:#3c3c43}.cal-empty-day__sub{margin:0;font-size:12px;color:#8e8e93}.cal-empty-day__btn{margin-top:10px}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:16px}.dialog-head h2{margin:0;font-size:18px;font-weight:700;display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{border:none;width:36px;height:36px;border-radius:50%;background:#f1f5f9;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:#fee2e2;color:var(--rose)}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.form-grid .field.full{grid-column:1/-1}.place-combobox,.place-combobox .input{width:100%}.place-combobox__menu{margin:0;padding:4px 0;list-style:none;max-height:200px;overflow-y:auto;background:#fff;border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px #0f172a24}.place-combobox__menu--portal{position:fixed;z-index:110}.place-combobox__option{display:block;width:100%;padding:10px 12px;border:none;background:transparent;text-align:left;font-size:14px;font-family:inherit;color:var(--text);cursor:pointer}.place-combobox__option:hover{background:var(--primary-soft);color:var(--primary)}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.input--textarea{resize:vertical;min-height:80px;line-height:1.45}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:18px}.muted{color:var(--muted)}\n"] }]
16531
+ args: [{ selector: 'app-feeding-schedule', standalone: true, imports: [CommonModule, FormsModule], changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"schedule-page\">\n @if (errorMsg()) {\n <div class=\"alert error\">\n <i class=\"pi pi-exclamation-circle\"></i>\n {{ errorMsg() }}\n </div>\n }\n @if (successMsg()) {\n <div class=\"alert ok\">\n <i class=\"pi pi-check-circle\"></i>\n {{ successMsg() }}\n </div>\n }\n\n <div class=\"schedule-topbar\">\n <button type=\"button\" class=\"btn btn-primary sm\" (click)=\"openCalendarDialog()\">\n <i class=\"pi pi-calendar\"></i>\n L\u1ECBch th\u00E1ng\n </button>\n </div>\n\n @if (!loading() || hasFutureEvents()) {\n @for (section of futureSections(); track trackSection($index, section)) {\n <section\n class=\"schedule-section\"\n [attr.data-section]=\"section.id\"\n [hidden]=\"section.eventCount === 0\"\n >\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"section.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ section.title }}</h2>\n <p class=\"schedule-section__sub\">{{ section.subtitle }}</p>\n </div>\n @if (section.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(section); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: false }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(section)) {\n <button\n type=\"button\"\n class=\"schedule-view-more\"\n (click)=\"toggleSectionExpand(section.id)\"\n >\n @if (!isSectionExpanded(section.id)) {\n Xem th\u00EAm {{ hiddenEventCount(section) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(section.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n }\n\n @if (!loading() && !hasFutureEvents()) {\n <section class=\"schedule-empty\">\n <i class=\"pi pi-calendar-plus\"></i>\n <p>Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n s\u1EAFp t\u1EDBi.</p>\n <button type=\"button\" class=\"btn btn-primary\" (click)=\"openAdd()\">\n <i class=\"pi pi-plus\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </section>\n }\n\n @if (pastSection(); as past) {\n <section class=\"schedule-section schedule-section--past\">\n <div class=\"schedule-section__head\">\n <span class=\"schedule-section__icon\" aria-hidden=\"true\">\n <i class=\"pi\" [ngClass]=\"past.icon\"></i>\n </span>\n <div class=\"schedule-section__titles\">\n <h2>{{ past.title }}</h2>\n @if (past.subtitle) {\n <p class=\"schedule-section__sub\">{{ past.subtitle }}</p>\n }\n </div>\n @if (past.eventCount; as n) {\n <span class=\"schedule-section__count\">\n {{ formatEventCount(n) }}\n </span>\n }\n </div>\n <div class=\"schedule-section__body\">\n <div class=\"schedule-days\">\n @for (day of visibleDayGroups(past); track trackDay($index, day)) {\n <article class=\"schedule-day schedule-day--relative\">\n <header class=\"schedule-day__date\">\n <span class=\"schedule-day__label schedule-day__label--relative\">\n {{ day.dateLabel }}\n </span>\n @if (day.dateSubLabel) {\n <span class=\"schedule-day__sub\">\n {{ day.dateSubLabel }}\n </span>\n }\n </header>\n <div class=\"schedule-day__cards\">\n @for (ev of day.events; track trackEvent($index, ev)) {\n <ng-container\n [ngTemplateOutlet]=\"eventCardTpl\"\n [ngTemplateOutletContext]=\"{ $implicit: ev, isPast: true }\"\n ></ng-container>\n }\n </div>\n </article>\n }\n </div>\n @if (hasMoreEvents(past)) {\n <button type=\"button\" class=\"schedule-view-more\" (click)=\"toggleSectionExpand(past.id)\">\n @if (!isSectionExpanded(past.id)) {\n Xem th\u00EAm {{ hiddenEventCount(past) }} s\u1EF1 ki\u1EC7n\n <i class=\"pi pi-chevron-down\" aria-hidden=\"true\"></i>\n }\n @if (isSectionExpanded(past.id)) {\n Thu g\u1ECDn\n <i class=\"pi pi-chevron-up\" aria-hidden=\"true\"></i>\n }\n </button>\n }\n </div>\n </section>\n }\n\n <ng-template #eventCardTpl let-ev let-isPast=\"isPast\">\n <article\n class=\"ev-card\"\n [class.ev-card--past]=\"isPast\"\n [attr.data-type]=\"getEventTypeClass(ev)\"\n >\n <div class=\"ev-card__time-col\">\n <span class=\"ev-card__hour\">{{ formatTime(ev.time) }}</span>\n </div>\n <div class=\"ev-card__accent-bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"ev-card__body\">\n <h3 class=\"ev-card__title\">{{ ev.title }}</h3>\n @if (ev.place) {\n <p class=\"ev-card__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n <span>{{ ev.place }}</span>\n </p>\n }\n @if (ev.note) {\n <div class=\"ev-card__content-wrap\">\n <span class=\"ev-card__content-label\">N\u1ED9i dung</span>\n <p class=\"ev-card__content\">{{ ev.note }}</p>\n </div>\n }\n </div>\n @if (!isPast && ev.rowIndex) {\n <div class=\"ev-card__actions\">\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only\"\n (click)=\"openEdit(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"S\u1EEDa\"\n aria-label=\"S\u1EEDa\"\n >\n <i class=\"pi pi-pencil\"></i>\n </button>\n <button\n type=\"button\"\n class=\"btn ghost sm icon-only danger\"\n (click)=\"deleteEv(ev)\"\n [disabled]=\"!isFuture(ev)\"\n title=\"Xo\u00E1\"\n aria-label=\"Xo\u00E1\"\n >\n <i class=\"pi pi-trash\"></i>\n </button>\n </div>\n }\n </article>\n </ng-template>\n\n <button\n type=\"button\"\n class=\"fab\"\n (click)=\"openAdd()\"\n aria-label=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n title=\"Th\u00EAm s\u1EF1 ki\u1EC7n\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n <span class=\"fab-label\">Th\u00EAm s\u1EF1 ki\u1EC7n</span>\n </button>\n\n @if (calendarDialogOpen()) {\n <div class=\"calendar-sheet-backdrop\" (click)=\"closeCalendarDialog()\">\n <div\n class=\"calendar-sheet\"\n role=\"dialog\"\n aria-modal=\"true\"\n (click)=\"$event.stopPropagation()\"\n >\n <!-- Drag handle -->\n <div class=\"calendar-sheet__handle\"></div>\n\n <!-- Header -->\n <div class=\"calendar-sheet__header\">\n <div class=\"calendar-sheet__header-left\">\n <div class=\"calendar-sheet__icon\">\n <i class=\"pi pi-calendar\" aria-hidden=\"true\"></i>\n </div>\n <span class=\"calendar-sheet__title\">L\u1ECBch th\u00E1ng</span>\n </div>\n <button\n type=\"button\"\n class=\"calendar-sheet__close\"\n (click)=\"closeCalendarDialog()\"\n aria-label=\"\u0110\u00F3ng\"\n >\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n\n <!-- Month nav + grid (swipeable) -->\n <div\n class=\"calendar-sheet__grid-area\"\n (touchstart)=\"onCalendarTouchStart($event)\"\n (touchmove)=\"onCalendarTouchMove($event)\"\n (touchend)=\"onCalendarTouchEnd()\"\n >\n <!-- Month / year nav row -->\n <div class=\"calendar-mnav\">\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarPrevMonth()\"\n aria-label=\"Th\u00E1ng tr\u01B0\u1EDBc\"\n >\n <i class=\"pi pi-chevron-left\"></i>\n </button>\n\n <div\n class=\"calendar-mnav__center\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n <div class=\"calendar-mnav__selects\">\n <select\n class=\"calendar-mnav__select\"\n [ngModel]=\"calendarCursor().getMonth()\"\n (ngModelChange)=\"calendarChangeMonth(+$event)\"\n aria-label=\"Ch\u1ECDn th\u00E1ng\"\n >\n <option [ngValue]=\"0\">Th\u00E1ng 1</option>\n <option [ngValue]=\"1\">Th\u00E1ng 2</option>\n <option [ngValue]=\"2\">Th\u00E1ng 3</option>\n <option [ngValue]=\"3\">Th\u00E1ng 4</option>\n <option [ngValue]=\"4\">Th\u00E1ng 5</option>\n <option [ngValue]=\"5\">Th\u00E1ng 6</option>\n <option [ngValue]=\"6\">Th\u00E1ng 7</option>\n <option [ngValue]=\"7\">Th\u00E1ng 8</option>\n <option [ngValue]=\"8\">Th\u00E1ng 9</option>\n <option [ngValue]=\"9\">Th\u00E1ng 10</option>\n <option [ngValue]=\"10\">Th\u00E1ng 11</option>\n <option [ngValue]=\"11\">Th\u00E1ng 12</option>\n </select>\n <select\n class=\"calendar-mnav__select calendar-mnav__select--year\"\n [ngModel]=\"calendarCursor().getFullYear()\"\n (ngModelChange)=\"calendarChangeYear(+$event)\"\n aria-label=\"Ch\u1ECDn n\u0103m\"\n >\n @for (year of calendarYearOptions(); track year) {\n <option [ngValue]=\"year\">{{ year }}</option>\n }\n </select>\n @if (!isViewingCurrentMonth()) {\n <button\n type=\"button\"\n class=\"calendar-mnav__today\"\n (click)=\"calendarGoToToday()\"\n title=\"V\u1EC1 h\u00F4m nay\"\n >\n <i class=\"pi pi-home\"></i>\n <span>H\u00F4m nay</span>\n </button>\n }\n </div>\n </div>\n\n <button\n type=\"button\"\n class=\"calendar-mnav__btn\"\n (click)=\"calendarNextMonth()\"\n aria-label=\"Th\u00E1ng sau\"\n >\n <i class=\"pi pi-chevron-right\"></i>\n </button>\n </div>\n\n <!-- Weekday header -->\n <div class=\"calendar-wd-row\">\n @for (wd of calendarWeekdays; track wd; let i = $index) {\n <span [class.is-sat]=\"i === 5\" [class.is-sun]=\"i === 6\">{{ wd }}</span>\n }\n </div>\n\n <!-- Day cells -->\n <div\n class=\"calendar-day-grid\"\n [class.is-sliding-next]=\"calendarSlideDirection() === 'next'\"\n [class.is-sliding-prev]=\"calendarSlideDirection() === 'prev'\"\n >\n @for (c of monthCalendarCells(); track c.dateIso; let i = $index) {\n <button\n type=\"button\"\n class=\"cal-cell\"\n [class.is-out-month]=\"!c.inMonth\"\n [class.is-today]=\"c.isToday\"\n [class.is-selected]=\"selectedCalendarDate() === c.dateIso\"\n (click)=\"selectCalendarDate(c.dateIso)\"\n >\n <span\n class=\"cal-cell__day\"\n [class.is-sat]=\"i % 7 === 5\"\n [class.is-sun]=\"i % 7 === 6\"\n >\n {{ c.day }}\n </span>\n @if (c.eventCount > 0) {\n <span class=\"cal-cell__dots\">\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n @if (c.eventCount > 1) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n @if (c.eventCount > 2) {\n <span\n class=\"cal-cell__dot\"\n [style.background]=\"\n selectedCalendarDate() === c.dateIso\n ? 'rgba(255,255,255,0.8)'\n : getEventIndicatorColor(c.dateIso)\n \"\n ></span>\n }\n </span>\n }\n </button>\n }\n </div>\n </div>\n\n <div class=\"calendar-sheet__divider\"></div>\n\n <!-- Events panel -->\n <div class=\"calendar-sheet__events\">\n <div class=\"calendar-sheet__events-head\">\n <div>\n <p class=\"calendar-sheet__events-date\">\n {{ formatEventDate(selectedCalendarDate()) }}\n </p>\n <p class=\"calendar-sheet__events-count\">\n @if (selectedCalendarEvents().length > 0) {\n {{ selectedCalendarEvents().length }} s\u1EF1 ki\u1EC7n\n } @else {\n Kh\u00F4ng c\u00F3 s\u1EF1 ki\u1EC7n\n }\n </p>\n </div>\n <button\n type=\"button\"\n class=\"btn btn-primary sm\"\n (click)=\"openAddFromCalendar()\"\n [disabled]=\"!selectedCalendarDate()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm\n </button>\n </div>\n\n <div class=\"calendar-sheet__events-list\">\n @for (ev of selectedCalendarEvents(); track trackEvent($index, ev)) {\n <article class=\"cal-ev-row\" [attr.data-type]=\"getEventTypeClass(ev)\">\n <div class=\"cal-ev-row__time\">{{ ev.time }}</div>\n <div class=\"cal-ev-row__bar\" [style.background]=\"getEventTypeDotColor(ev)\"></div>\n <div class=\"cal-ev-row__body\">\n <p class=\"cal-ev-row__title\">{{ ev.title }}</p>\n @if (ev.place) {\n <p class=\"cal-ev-row__place\">\n <i class=\"pi pi-map-marker\" aria-hidden=\"true\"></i>\n {{ ev.place }}\n </p>\n }\n </div>\n </article>\n } @empty {\n @if (selectedCalendarDate()) {\n <div class=\"cal-empty-day\">\n <i class=\"pi pi-calendar-plus cal-empty-day__icon\" aria-hidden=\"true\"></i>\n <p class=\"cal-empty-day__label\">Ch\u01B0a c\u00F3 s\u1EF1 ki\u1EC7n</p>\n <p class=\"cal-empty-day__sub\">{{ formatEventDate(selectedCalendarDate()) }}</p>\n <button\n type=\"button\"\n class=\"btn btn-primary sm cal-empty-day__btn\"\n (click)=\"openAddFromCalendar()\"\n >\n <i class=\"pi pi-plus\" aria-hidden=\"true\"></i>\n Th\u00EAm s\u1EF1 ki\u1EC7n\n </button>\n </div>\n }\n }\n </div>\n </div>\n </div>\n </div>\n }\n\n @if (addOpen()) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"closeAdd()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-plus-circle\"></i> Th\u00EAm s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"closeAdd()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (draft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateDraft({ date: $event })\"\n name=\"evd\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateDraft({ time: $event })\"\n name=\"evt\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn s\u1EF1 ki\u1EC7n *</span>\n <input\n type=\"text\"\n class=\"input\"\n placeholder=\"VD: Ti\u00EAm ch\u1EE7ng 6in1\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateDraft({ title: $event })\"\n name=\"evti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateDraft({ note: $event })\"\n name=\"evn\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputAdd\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'add', placeInputAdd)\"\n (focus)=\"openPlacePicker('add', placeInputAdd)\"\n (blur)=\"onPlaceBlur('add')\"\n name=\"evp\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"closeAdd()\">Hu\u1EF7</button>\n <button type=\"button\" class=\"btn btn-primary\" [disabled]=\"saving()\" (click)=\"submitAdd()\">\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-save'\"></i>\n {{ saving() ? '\u0110ang l\u01B0u\u2026' : 'L\u01B0u' }}\n </button>\n </div>\n </div>\n </div>\n }\n\n @if (editing(); as ed) {\n <div class=\"dialog-overlay dialog-overlay--top\" (click)=\"cancelEdit()\">\n <div class=\"dialog\" role=\"dialog\" aria-modal=\"true\" (click)=\"$event.stopPropagation()\">\n <div class=\"dialog-head\">\n <div>\n <h2><i class=\"pi pi-pencil\"></i> S\u1EEDa s\u1EF1 ki\u1EC7n</h2>\n </div>\n <button type=\"button\" class=\"dialog-close\" (click)=\"cancelEdit()\" aria-label=\"\u0110\u00F3ng\">\n <i class=\"pi pi-times\"></i>\n </button>\n </div>\n @if (editDraft(); as dr) {\n <div class=\"form-grid\">\n <label class=\"field\">\n <span class=\"label\">Ng\u00E0y *</span>\n <input\n type=\"date\"\n class=\"input\"\n [ngModel]=\"dr.date\"\n (ngModelChange)=\"updateEditDraft({ date: $event })\"\n name=\"eed\"\n />\n </label>\n <label class=\"field\">\n <span class=\"label\">Gi\u1EDD *</span>\n <input\n type=\"time\"\n class=\"input\"\n [ngModel]=\"dr.time\"\n (ngModelChange)=\"updateEditDraft({ time: $event })\"\n name=\"eet\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">T\u00EAn *</span>\n <input\n type=\"text\"\n class=\"input\"\n [ngModel]=\"dr.title\"\n (ngModelChange)=\"updateEditDraft({ title: $event })\"\n name=\"eeti\"\n />\n </label>\n <label class=\"field full\">\n <span class=\"label\">Ghi ch\u00FA</span>\n <textarea\n class=\"input input--textarea\"\n rows=\"3\"\n placeholder=\"Ghi ch\u00FA th\u00EAm\u2026\"\n [ngModel]=\"dr.note\"\n (ngModelChange)=\"updateEditDraft({ note: $event })\"\n name=\"een\"\n ></textarea>\n </label>\n <label class=\"field full field--place\">\n <span class=\"label\">V\u1ECB tr\u00ED</span>\n <div class=\"place-combobox\">\n <input\n #placeInputEdit\n type=\"text\"\n class=\"input\"\n placeholder=\"Ch\u1ECDn ho\u1EB7c nh\u1EADp v\u1ECB tr\u00ED m\u1EDBi\u2026\"\n [ngModel]=\"dr.place\"\n (ngModelChange)=\"onPlaceInput($event, 'edit', placeInputEdit)\"\n (focus)=\"openPlacePicker('edit', placeInputEdit)\"\n (blur)=\"onPlaceBlur('edit')\"\n name=\"eep\"\n autocomplete=\"off\"\n />\n </div>\n </label>\n </div>\n }\n <div class=\"dialog-actions\">\n <button type=\"button\" class=\"btn ghost\" (click)=\"cancelEdit()\">Hu\u1EF7</button>\n <button\n type=\"button\"\n class=\"btn btn-primary\"\n [disabled]=\"saving()\"\n (click)=\"submitEdit()\"\n >\n <i class=\"pi\" [ngClass]=\"saving() ? 'pi-spin pi-spinner' : 'pi-check'\"></i>\n C\u1EADp nh\u1EADt\n </button>\n </div>\n </div>\n </div>\n }\n</div>\n\n@if (placeMenuView(); as vm) {\n <ul\n class=\"place-combobox__menu place-combobox__menu--portal\"\n role=\"listbox\"\n [style.top.px]=\"vm.pos.top\"\n [style.left.px]=\"vm.pos.left\"\n [style.width.px]=\"vm.pos.width\"\n (mousedown)=\"$event.preventDefault()\"\n >\n @for (place of vm.places; track place) {\n <li>\n <button\n type=\"button\"\n class=\"place-combobox__option\"\n (mousedown)=\"selectPlace(place, vm.ctx)\"\n >\n {{ place }}\n </button>\n </li>\n }\n </ul>\n}\n", styles: ["@charset \"UTF-8\";:host{display:block;--surface: #ffffff;--border: #e6e8ef;--border-soft: #eef1f6;--primary: #5b6cff;--primary-soft: #eef0ff;--text: #0f172a;--muted: #64748b;--rose: #ef4444}.schedule-page{max-width:640px;margin:0 auto;padding:8px 16px calc(120px + env(safe-area-inset-bottom,0px))}.schedule-topbar{display:flex;justify-content:flex-end;align-items:center;gap:8px;margin:4px 0 12px}.alert{display:flex;align-items:flex-start;gap:8px;padding:10px 12px;border-radius:12px;font-size:13px;margin-bottom:12px}.alert.error{background:#fef2f2;color:#991b1b;border:1px solid #fecaca}.alert.ok{background:#ecfdf5;color:#065f46;border:1px solid #a7f3d0}.schedule-section{margin-bottom:40px;padding:16px 14px 18px;border-radius:16px;border:1px solid transparent}.schedule-section[hidden]{display:none!important}.schedule-section[data-section=today]{background:linear-gradient(180deg,#ecfdf5,#f7fef9);border-color:#a7f3d0}.schedule-section[data-section=today] .schedule-section__head{border-bottom-color:#a7f3d0}.schedule-section[data-section=soon]{background:linear-gradient(180deg,#fffbeb,#fffdf7);border-color:#fde68a}.schedule-section[data-section=soon] .schedule-section__head{border-bottom-color:#fde68a}.schedule-section[data-section=nextMonth]{background:linear-gradient(180deg,#eff6ff,#f8fbff);border-color:#bfdbfe}.schedule-section[data-section=nextMonth] .schedule-section__head{border-bottom-color:#bfdbfe}.schedule-section[data-section=later]{background:linear-gradient(180deg,#f5f3ff,#faf9ff);border-color:#ddd6fe}.schedule-section[data-section=later] .schedule-section__head{border-bottom-color:#ddd6fe}.schedule-section[data-section=today] .schedule-section__icon{background:linear-gradient(135deg,#bbf7d0,#86efac);color:#15803d}.schedule-section[data-section=soon] .schedule-section__icon{background:linear-gradient(135deg,#fef3c7,#fde68a);color:#b45309}.schedule-section[data-section=nextMonth] .schedule-section__icon{background:linear-gradient(135deg,#dbeafe,#bfdbfe);color:#1d4ed8}.schedule-section[data-section=later] .schedule-section__icon{background:linear-gradient(135deg,#ede9fe,#ddd6fe);color:#6d28d9}.schedule-section--past{background:linear-gradient(180deg,#f8fafc,#fcfdfe);border-color:#e2e8f0}.schedule-section--past .schedule-section__head{border-bottom-color:#e2e8f0}.schedule-section--past .schedule-section__icon{background:#f1f5f9;color:var(--muted)}.schedule-section__head{display:flex;align-items:center;gap:12px;margin-bottom:16px;padding:0 2px 14px;border-bottom:2px solid var(--border-soft)}.schedule-section__icon{flex-shrink:0;width:40px;height:40px;border-radius:12px;display:flex;align-items:center;justify-content:center;font-size:17px}.schedule-section__titles{flex:1;min-width:0}.schedule-section__titles h2{margin:0;font-size:17px;font-weight:800;color:var(--text);letter-spacing:-.02em}.schedule-section__sub{margin:2px 0 0;font-size:12.5px;color:var(--muted)}.schedule-section__count{flex-shrink:0;font-size:11px;font-weight:700;color:var(--muted);background:#ffffffb8;padding:4px 10px;border-radius:999px;border:1px solid rgba(15,23,42,.06)}.schedule-section__body{display:flex;flex-direction:column;gap:4px}.schedule-view-more{display:inline-flex;align-items:center;justify-content:center;gap:6px;width:100%;margin-top:12px;padding:11px 14px;border:1px dashed #c7d2fe;border-radius:12px;background:#ffffffa6;color:var(--primary);font-size:13px;font-weight:700;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.schedule-view-more:hover{background:#fff;border-color:var(--primary)}.schedule-days{display:flex;flex-direction:column;gap:24px}.schedule-day{display:flex;flex-direction:column;gap:10px}.schedule-day__date{display:flex;flex-direction:column;align-items:flex-start;gap:2px;padding:0 2px 6px;border-bottom:2px solid var(--border-soft)}.schedule-day--relative .schedule-day__date{border-bottom-color:#f59e0b38}[data-section=today] .schedule-day--relative .schedule-day__date{border-bottom-color:#10b98140}[data-section=today] .schedule-day__label--relative{color:#047857}[data-section=soon] .schedule-day__label--relative{color:#b45309}.schedule-day__label{font-size:14px;font-weight:700;color:var(--text);line-height:1.45}.schedule-day__label--relative{font-size:15px;font-weight:800;color:#b45309;letter-spacing:-.02em}.schedule-section--past .schedule-day__label--relative{color:#64748b}.schedule-section[data-section=nextMonth] .schedule-day__label--relative{color:#1d4ed8}.schedule-section[data-section=later] .schedule-day__label--relative{color:#6d28d9}.schedule-day__sub{font-size:12px;font-weight:500;color:var(--muted);line-height:1.35}.schedule-day__cards{display:flex;flex-direction:column;gap:8px;width:100%}.schedule-day__cards .ev-card__time-col{margin-right:8px}.ev-card{position:relative;display:flex;flex-direction:row;align-items:stretch;gap:0;padding:10px 12px;background:var(--surface);border:1px solid var(--border);border-radius:14px;overflow:hidden;transition:border-color .15s}.ev-card[data-type=vaccine]{border-color:#ddd6fe}.ev-card[data-type=checkup]{border-color:#bfdbfe}.ev-card[data-type=important]{border-color:#fecaca}.ev-card[data-type=general]{border-color:#a7f3d0}.ev-card--past{opacity:.68;background:#fafbfc}.ev-card__time-col{width:40px;flex-shrink:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px}.ev-card__hour{font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums;line-height:1.2;text-align:center}.ev-card__accent-bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch;margin-right:10px}.ev-card__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px;padding-right:44px}.ev-card__title{margin:0;font-size:13.5px;font-weight:700;color:var(--text);line-height:1.3}.ev-card__place{margin:0;display:flex;align-items:center;gap:4px;font-size:11.5px;color:var(--muted);line-height:1.4}.ev-card__place i{flex-shrink:0;font-size:11px;color:#94a3b8}.ev-card__content-wrap{margin-top:8px;padding-top:8px;border-top:1px solid var(--border-soft)}.ev-card__content-label{display:block;margin-bottom:3px;font-size:10.5px;font-weight:700;letter-spacing:.04em;text-transform:uppercase;color:#94a3b8}.ev-card__content{margin:0;font-size:13px;color:#334155;line-height:1.5;white-space:pre-wrap}.ev-card--past .ev-card__content{color:var(--muted)}.ev-card__actions{position:absolute;top:8px;right:8px;display:flex;gap:2px}.schedule-empty{text-align:center;padding:40px 20px;color:var(--muted)}.schedule-empty i{font-size:40px;color:#cbd5e1;margin-bottom:12px;display:block}.schedule-empty p{margin:0 0 16px;font-size:14px}.btn{display:inline-flex;align-items:center;justify-content:center;gap:6px;padding:8px 14px;border-radius:10px;font-size:14px;font-weight:600;font-family:inherit;border:none;cursor:pointer}.btn:disabled{opacity:.5;cursor:not-allowed}.btn.ghost{background:transparent;color:var(--muted);border:1px solid var(--border)}.btn.ghost.danger:hover:not(:disabled){color:var(--rose);border-color:#fecaca;background:#fef2f2}.btn.sm{padding:6px 8px;font-size:13px}.btn.icon-only{padding:8px;width:36px;height:36px}.btn.btn-primary{background:var(--primary);color:#fff}.btn.btn-primary:hover:not(:disabled){filter:brightness(1.05)}.fab{position:fixed;bottom:calc(80px + env(safe-area-inset-bottom,0px));right:18px;z-index:85;display:inline-flex;align-items:center;gap:10px;padding:14px 22px 14px 18px;background:var(--primary);color:#fff;border:none;border-radius:999px;font-size:15px;font-weight:700;font-family:inherit;white-space:nowrap;cursor:pointer;box-shadow:0 8px 24px #5b6cff73}.fab i{flex-shrink:0;font-size:18px}.fab .fab-label{white-space:nowrap;line-height:1.2}@media(max-width:480px){.fab{padding:14px;gap:0}.fab .fab-label{display:none}.fab i{font-size:22px}}.dialog-overlay{position:fixed;inset:0;background:#0f172a8c;z-index:100;display:flex;align-items:center;justify-content:center;padding:16px}.dialog-overlay.dialog-overlay--top{align-items:flex-start;padding:calc(env(safe-area-inset-top,0px) + 20px) 16px 16px}.dialog{background:#fff;border-radius:18px;width:100%;max-width:480px;max-height:90vh;overflow-y:auto;padding:20px;box-shadow:0 20px 50px #0f172a33}.calendar-sheet-backdrop{position:fixed;inset:45px 0 63px;background:#0a0c14a6;z-index:100;display:flex;padding:16px 0;justify-content:stretch}.calendar-sheet{width:100%;max-height:88dvh;background:#f2f2f7;border-top-left-radius:22px;border-top-right-radius:22px;overflow:hidden;display:flex;flex-direction:column;font-family:inherit}.calendar-sheet__handle{width:36px;height:4px;background:#c7cacc;border-radius:2px;margin:10px auto 8px;flex-shrink:0}.calendar-sheet__header{display:flex;align-items:center;justify-content:space-between;padding:0 16px 10px;flex-shrink:0}.calendar-sheet__header-left{display:flex;align-items:center;gap:8px}.calendar-sheet__icon{width:30px;height:30px;border-radius:8px;background:#fff;border:1px solid #e0e0e5;display:flex;align-items:center;justify-content:center}.calendar-sheet__icon i{font-size:15px;color:var(--primary)}.calendar-sheet__title{font-size:16px;font-weight:700;color:#0f172a;letter-spacing:-.01em}.calendar-sheet__close{width:28px;height:28px;border-radius:50%;background:#3c3c431f;border:none;cursor:pointer;font-size:14px;color:#3c3c43;display:flex;align-items:center;justify-content:center;font-family:inherit;transition:background .15s}.calendar-sheet__close:hover{background:#3c3c4333}.calendar-sheet__grid-area{padding:0 14px 8px;flex-shrink:0;touch-action:pan-y}.calendar-mnav{display:flex;align-items:center;justify-content:space-between;margin-bottom:10px}.calendar-mnav__btn{width:32px;height:32px;border-radius:50%;background:#3c3c431a;border:none;cursor:pointer;display:flex;align-items:center;justify-content:center;font-family:inherit;color:#3c3c43;transition:background .15s}.calendar-mnav__btn i{font-size:13px}.calendar-mnav__btn:hover{background:#3c3c432e}.calendar-mnav__label{text-align:center;transition:opacity .21s,transform .21s}.calendar-mnav__label.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__label.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__center{display:flex;flex-direction:row;align-items:center;justify-content:center;transition:opacity .21s,transform .21s}.calendar-mnav__center.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-mnav__center.is-sliding-prev{opacity:0;transform:translate(20px)}.calendar-mnav__selects{display:flex;align-items:center;gap:6px}.calendar-mnav__select{padding:5px 22px 5px 8px;border:1px solid rgba(60,60,67,.18);border-radius:8px;background:#fff;color:#0f172a;font-size:13px;font-weight:600;font-family:inherit;cursor:pointer;appearance:none;-webkit-appearance:none;background-image:url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='10' height='6' viewBox='0 0 10 6'%3E%3Cpath d='M1 1l4 4 4-4' stroke='%236e6e73' stroke-width='1.5' fill='none' stroke-linecap='round'/%3E%3C/svg%3E\");background-repeat:no-repeat;background-position:right 6px center}.calendar-mnav__select--year{min-width:70px}.calendar-mnav__select:focus{outline:none;border-color:#5b6cff;box-shadow:0 0 0 3px #5b6cff26}.calendar-mnav__today{display:flex;align-items:center;gap:4px;padding:4px 10px;border:1px solid rgba(16,185,129,.35);border-radius:8px;background:#10b98112;color:#047857;font-size:11px;font-weight:600;font-family:inherit;cursor:pointer;transition:background .15s,border-color .15s}.calendar-mnav__today i{font-size:11px}.calendar-mnav__today:hover{background:#10b9811f;border-color:#10b98180}.calendar-mnav__month{display:block;font-size:18px;font-weight:700;color:#0f172a;letter-spacing:-.02em}.calendar-mnav__year{display:block;font-size:12px;color:#6e6e73;font-weight:500;margin-top:1px}.calendar-wd-row{display:grid;grid-template-columns:repeat(7,1fr);margin-bottom:4px}.calendar-wd-row span{text-align:center;font-size:11px;font-weight:600;color:#8e8e93;padding-bottom:4px}.calendar-wd-row span.is-sat{color:#1d6cf6}.calendar-wd-row span.is-sun{color:#e02020}.calendar-day-grid{display:grid;grid-template-columns:repeat(7,1fr);gap:2px;transition:opacity .21s,transform .21s}.calendar-day-grid.is-sliding-next{opacity:0;transform:translate(-20px)}.calendar-day-grid.is-sliding-prev{opacity:0;transform:translate(20px)}.cal-cell{background:transparent;border:none;border-radius:8px;height:38px;padding:0;cursor:pointer;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;transition:background .12s}.cal-cell.is-out-month{opacity:.35}.cal-cell.is-today{background:#5b6cff14}.cal-cell.is-selected{background:#5b6cff}.cal-cell.is-selected .cal-cell__day{color:#fff!important;font-weight:700}.cal-cell__day{font-size:15px;font-weight:400;color:#1c1c1e;line-height:1}.cal-cell__day.is-sat{color:#1d6cf6}.cal-cell__day.is-sun{color:#e02020}.is-today .cal-cell__day{font-weight:700;color:#5b6cff}.cal-cell__dots{display:flex;gap:2px;align-items:center}.cal-cell__dot{display:block;width:4px;height:4px;border-radius:50%;background:var(--primary)}.calendar-sheet__divider{height:1px;background:#3c3c431f;flex-shrink:0}.calendar-sheet__events{flex:1;min-height:0;overflow:hidden;display:flex;flex-direction:column;position:relative}.calendar-sheet__events-head{padding:12px 18px 8px;flex-shrink:0;display:flex;justify-content:space-between;align-items:center}.calendar-sheet__events-date{margin:0;font-size:13px;font-weight:700;color:#1c1c1e;letter-spacing:-.01em}.calendar-sheet__events-count{margin:2px 0 0;font-size:11.5px;color:#8e8e93}.calendar-sheet__events-list{flex:1;overflow-y:auto;padding:0 14px 16px;display:flex;flex-direction:column;gap:6px;overscroll-behavior:contain}.cal-ev-row{display:flex;gap:10px;align-items:stretch;background:#fff;border-radius:14px;padding:11px 14px;border:1px solid #e6e8ef}.cal-ev-row[data-type=vaccine]{border-color:#ddd6fe}.cal-ev-row[data-type=checkup]{border-color:#bfdbfe}.cal-ev-row[data-type=important]{border-color:#fecaca}.cal-ev-row[data-type=general]{border-color:#a7f3d0}.cal-ev-row__time{width:36px;flex-shrink:0;display:flex;align-items:center;justify-content:center;font-size:12.5px;font-weight:700;color:#e02020;font-variant-numeric:tabular-nums}.cal-ev-row__bar{width:2px;border-radius:2px;flex-shrink:0;align-self:stretch}.cal-ev-row__body{flex:1;min-width:0;display:flex;flex-direction:column;justify-content:center;gap:3px}.cal-ev-row__title{margin:0;font-size:13.5px;font-weight:700;color:#1c1c1e;line-height:1.3}.cal-ev-row__place{margin:0;font-size:11.5px;color:#6e6e73;display:flex;align-items:center;gap:4px}.cal-ev-row__place i{font-size:11px;color:#8e8e93;flex-shrink:0}.cal-empty-day{display:flex;flex-direction:column;align-items:center;justify-content:center;padding:24px 16px 20px;text-align:center;gap:6px}.cal-empty-day__icon{font-size:28px;color:#c7cacc;margin-bottom:4px}.cal-empty-day__label{margin:0;font-size:14px;font-weight:600;color:#3c3c43}.cal-empty-day__sub{margin:0;font-size:12px;color:#8e8e93}.cal-empty-day__btn{margin-top:10px}.dialog-head{display:flex;justify-content:space-between;align-items:flex-start;gap:10px;margin-bottom:16px}.dialog-head h2{margin:0;font-size:18px;font-weight:700;display:flex;align-items:center;gap:8px}.dialog-head h2 i{color:var(--primary)}.dialog-close{border:none;width:36px;height:36px;border-radius:50%;background:#f1f5f9;cursor:pointer;color:var(--muted);font-size:14px;display:inline-flex;align-items:center;justify-content:center;flex-shrink:0;transition:background .15s,color .15s}.dialog-close:hover{background:#fee2e2;color:var(--rose)}.form-grid{display:grid;grid-template-columns:1fr 1fr;gap:12px}.form-grid .field.full{grid-column:1/-1}.place-combobox,.place-combobox .input{width:100%}.place-combobox__menu{margin:0;padding:4px 0;list-style:none;max-height:200px;overflow-y:auto;background:#fff;border:1px solid var(--border);border-radius:10px;box-shadow:0 8px 24px #0f172a24}.place-combobox__menu--portal{position:fixed;z-index:110}.place-combobox__option{display:block;width:100%;padding:10px 12px;border:none;background:transparent;text-align:left;font-size:14px;font-family:inherit;color:var(--text);cursor:pointer}.place-combobox__option:hover{background:var(--primary-soft);color:var(--primary)}.field{display:flex;flex-direction:column;gap:4px}.field .label{font-size:12px;font-weight:600;color:var(--muted)}.input{border:1px solid var(--border);border-radius:10px;padding:10px 12px;font-size:15px;font-family:inherit}.input:focus{outline:none;border-color:var(--primary);box-shadow:0 0 0 3px #5b6cff26}.input--textarea{resize:vertical;min-height:80px;line-height:1.45}.dialog-actions{display:flex;justify-content:flex-end;gap:10px;margin-top:18px}.muted{color:var(--muted)}\n"] }]
16147
16532
  }], ctorParameters: () => [], propDecorators: { user: [{ type: i0.Input, args: [{ isSignal: true, alias: "user", required: false }] }], onPlaceMenuViewportChange: [{
16148
16533
  type: HostListener,
16149
16534
  args: ['window:scroll']